feat(mac): add Qwen3 MLX streaming dictation
Replace the Sherpa offline pipeline with native MLX streaming, resilient model downloads, live transcript previews, and supporting tests and documentation.
This commit is contained in:
Executable
+68
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env bash
|
||||
# Ensures vendored mlx-audio-swift is present and patched for StreamingConfig.context.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
VENDOR="$ROOT/ThirdParty/mlx-audio-swift"
|
||||
PATCH_MARKER="$VENDOR/.osg-context-patch-applied"
|
||||
|
||||
if [[ ! -f "$VENDOR/Package.swift" ]]; then
|
||||
echo "Cloning mlx-audio-swift into ThirdParty/..."
|
||||
git clone --depth 1 https://github.com/Blaizzy/mlx-audio-swift "$VENDOR"
|
||||
fi
|
||||
|
||||
if [[ -f "$PATCH_MARKER" ]]; then
|
||||
echo "mlx-audio-swift context patch already applied."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
python3 <<'PY'
|
||||
from pathlib import Path
|
||||
|
||||
root = Path("ThirdParty/mlx-audio-swift")
|
||||
manifest = root / "Package.swift"
|
||||
types = root / "Sources/MLXAudioSTT/Streaming/StreamingTypes.swift"
|
||||
session = root / "Sources/MLXAudioSTT/Streaming/StreamingInferenceSession.swift"
|
||||
|
||||
# Pin mlx-swift to 0.31.3: 0.31.4+ adds an unconditional swift-argument-parser
|
||||
# dependency (for its `encuda` executable) that breaks offline resolution here.
|
||||
mtext = manifest.read_text()
|
||||
if "mlx-swift.git\", exact:" not in mtext:
|
||||
mtext = mtext.replace(
|
||||
'.package(url: "https://github.com/ml-explore/mlx-swift.git", .upToNextMajor(from: "0.30.6")),',
|
||||
'.package(url: "https://github.com/ml-explore/mlx-swift.git", exact: "0.31.3"),',
|
||||
)
|
||||
manifest.write_text(mtext)
|
||||
|
||||
text = types.read_text()
|
||||
if "public var context: String?" not in text:
|
||||
text = text.replace(
|
||||
" public var language: String?\n",
|
||||
" public var language: String?\n"
|
||||
" /// Optional soft-prompt context (vocabulary / domain hints) injected into the system turn.\n"
|
||||
" public var context: String?\n",
|
||||
)
|
||||
text = text.replace(
|
||||
" language: String? = \"English\",\n temperature: Float = 0.0,",
|
||||
" language: String? = \"English\",\n context: String? = nil,\n temperature: Float = 0.0,",
|
||||
)
|
||||
text = text.replace(
|
||||
" self.language = language\n self.temperature = temperature",
|
||||
" self.language = language\n self.context = context\n self.temperature = temperature",
|
||||
)
|
||||
types.write_text(text)
|
||||
|
||||
text = session.read_text()
|
||||
text = text.replace(
|
||||
" language: params.config.language\n )",
|
||||
" context: params.config.context ?? \"\",\n language: params.config.language\n )",
|
||||
)
|
||||
text = text.replace(
|
||||
" language: config.language\n )",
|
||||
" context: config.context ?? \"\",\n language: config.language\n )",
|
||||
)
|
||||
session.write_text(text)
|
||||
PY
|
||||
|
||||
touch "$PATCH_MARKER"
|
||||
echo "Applied mlx-audio-swift context patch."
|
||||
@@ -35,8 +35,12 @@ if [[ ! -f "$PRECONFIG_LOCAL" ]]; then
|
||||
echo "Edit deepseek in that file before using the local engine's built-in polish."
|
||||
fi
|
||||
|
||||
"$ROOT/Scripts/ensure-mlx-audio-swift.sh"
|
||||
|
||||
xcodegen generate
|
||||
|
||||
"$ROOT/Scripts/patch-spm-local-package.sh"
|
||||
|
||||
if python3 - "$PBXPROJ" <<'PY'
|
||||
import re
|
||||
import sys
|
||||
|
||||
Executable
+60
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env bash
|
||||
# XcodeGen 2.43 omits `package = …` on XCSwiftPackageProductDependency for local
|
||||
# path packages, which makes Xcode show "Missing package product 'MLXAudioSTT'".
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
PBXPROJ="$ROOT/OSGKeyboard.xcodeproj/project.pbxproj"
|
||||
|
||||
if [[ ! -f "$PBXPROJ" ]]; then
|
||||
echo "patch-spm-local-package: $PBXPROJ not found — run xcodegen first." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
python3 - "$PBXPROJ" <<'PY'
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
path = Path(sys.argv[1])
|
||||
text = path.read_text()
|
||||
|
||||
package_ref = re.search(
|
||||
r'(\w+) /\* XCLocalSwiftPackageReference "ThirdParty/mlx-audio-swift" \*/ = \{\n'
|
||||
r'\s*isa = XCLocalSwiftPackageReference;\n'
|
||||
r'\s*relativePath = "ThirdParty/mlx-audio-swift";\n'
|
||||
r'\s*\};',
|
||||
text,
|
||||
)
|
||||
if not package_ref:
|
||||
print("patch-spm-local-package: local package reference not found — skip")
|
||||
sys.exit(0)
|
||||
|
||||
package_id = package_ref.group(1)
|
||||
product_block = re.search(
|
||||
r'(\w+) /\* MLXAudioSTT \*/ = \{\n'
|
||||
r'\s*isa = XCSwiftPackageProductDependency;\n'
|
||||
r'(\s*productName = MLXAudioSTT;\n)'
|
||||
r'\s*\};',
|
||||
text,
|
||||
)
|
||||
if not product_block:
|
||||
print("patch-spm-local-package: MLXAudioSTT product dependency not found — skip")
|
||||
sys.exit(0)
|
||||
|
||||
product_id = product_block.group(1)
|
||||
if f"package = {package_id}" in text:
|
||||
print("patch-spm-local-package: already patched")
|
||||
sys.exit(0)
|
||||
|
||||
replacement = (
|
||||
f"{product_id} /* MLXAudioSTT */ = {{\n"
|
||||
f"\t\t\tisa = XCSwiftPackageProductDependency;\n"
|
||||
f"\t\t\tpackage = {package_id} /* XCLocalSwiftPackageReference \"ThirdParty/mlx-audio-swift\" */;\n"
|
||||
f"\t\t\tproductName = MLXAudioSTT;\n"
|
||||
f"\t\t}};"
|
||||
)
|
||||
text = text[: product_block.start()] + replacement + text[product_block.end() :]
|
||||
path.write_text(text)
|
||||
print("patch-spm-local-package: linked MLXAudioSTT → ThirdParty/mlx-audio-swift")
|
||||
PY
|
||||
Reference in New Issue
Block a user