dudu MVP:五端语音输入法初始提交
ci / server (push) Failing after 14s
ci / design-tokens (push) Failing after 11s

- server:Go 网关(WS 流式识别中继/计费配额/微信登录支付 mock/反馈/埋点),gummy provider 已真实联调
- desktop:Tauri 2(全局快捷键 push-to-talk/浮层/托盘/设置/登录购买/反馈/首启引导)
- android:Compose 主 App + IME(键盘内录音直传)
- ios:App + 键盘扩展(1A spike 实证键盘内不可录音,走 deep link 听写)
- design/design-pipeline:设计系统 + token 导出 iOS/Android 主题
- doc:前后端设计文档(HTML);web:官网宣传页;todo:任务看板

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-12 00:38:37 +08:00
commit 40760aa884
252 changed files with 40789 additions and 0 deletions
@@ -0,0 +1,77 @@
// 1A spike AVAudioEngine
//
// engine.start() 1
// setActive/start 0 deep link 14D
import AVFoundation
import UIKit
class KeyboardViewController: UIInputViewController {
private let resultLabel = UILabel()
private let engine = AVAudioEngine()
private var frames: UInt32 = 0
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemGray5
let stack = UIStackView()
stack.axis = .vertical
stack.spacing = 10
stack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stack)
NSLayoutConstraint.activate([
stack.centerXAnchor.constraint(equalTo: view.centerXAnchor),
stack.topAnchor.constraint(equalTo: view.topAnchor, constant: 16),
stack.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -32),
view.heightAnchor.constraint(greaterThanOrEqualToConstant: 220),
])
let info = UILabel()
info.text = "完全访问:\(hasFullAccess ? "已开启" : "未开启(请先开启)")"
info.font = .systemFont(ofSize: 14)
stack.addArrangedSubview(info)
let btn = UIButton(type: .system)
btn.setTitle("测试键盘内录音(1 秒)", for: .normal)
btn.titleLabel?.font = .boldSystemFont(ofSize: 17)
btn.addTarget(self, action: #selector(testMic), for: .touchUpInside)
stack.addArrangedSubview(btn)
resultLabel.text = "结果:未测试"
resultLabel.font = .monospacedSystemFont(ofSize: 14, weight: .regular)
resultLabel.numberOfLines = 0
stack.addArrangedSubview(resultLabel)
let next = UIButton(type: .system)
next.setTitle("🌐 切回系统键盘", for: .normal)
next.addTarget(self, action: #selector(handleInputModeList(from:with:)), for: .allTouchEvents)
stack.addArrangedSubview(next)
}
@objc private func testMic() {
frames = 0
resultLabel.text = "结果:测试中…"
// App /
do {
let session = AVAudioSession.sharedInstance()
try session.setCategory(.playAndRecord, mode: .measurement, options: [.mixWithOthers])
try session.setActive(true)
engine.inputNode.removeTap(onBus: 0)
engine.inputNode.installTap(onBus: 0, bufferSize: 1024, format: nil) { [weak self] buf, _ in
self?.frames += buf.frameLength
}
try engine.start()
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
guard let self else { return }
self.engine.stop()
self.engine.inputNode.removeTap(onBus: 0)
let permission = AVAudioApplication.shared.recordPermission
self.resultLabel.text = self.frames > 0
? "结果:✅ 收到 \(self.frames) 帧 — 键盘内录音可行"
: "结果:❌ 0 帧(权限态:\(permission.rawValue))— 需走降级方案"
}
} catch {
resultLabel.text = "结果:❌ \(error.localizedDescription) — 需走降级方案"
}
}
}