feat: macOS architecture, cloud ASR/LLM providers, and 6-step iOS onboarding

- Add macOS menu-bar dictation app with local ASR models (SenseVoice/Qwen3),
  global Option hotkey, and bottom overlay
- Add cloud ASR/LLM providers (Anthropic, Volcengine, Bailian, and more) with
  provider logos, model listing, and connection checks
- Add shared 7-day usage stats UI (UsageStatsCluster / SevenDayUsageChart)
- Add iOS onboarding step 6 for polish LLM setup; hide custom-language-model
  diagnostic toggle behind DEBUG
- Unify iOS onboarding tagline with the macOS brand line ("开口即文字。")
- Rewrite README (Chinese-first, product-oriented) and refresh GitHub Pages
This commit is contained in:
Rocky
2026-07-11 19:10:20 +08:00
parent cdf833935a
commit cc8dd1070a
116 changed files with 6659 additions and 2634 deletions
+77
View File
@@ -347,6 +347,83 @@ final class LLMClientTests: XCTestCase {
/// Local engine pins DeepSeek cloud-provider URL/model in App Group
/// must not leak into the LLM request (regression: Qwen URL + DeepSeek key 401).
func testLLMClientFactoryRoutesAnthropic() {
let client = LLMClientFactory.make(
providerId: "anthropic",
baseURL: "",
apiKey: "sk-test",
model: "claude-sonnet-4-6"
)
XCTAssertTrue(client is AnthropicMessagesClient)
}
func testLLMClientFactoryResolvesGeminiOpenAICompatBaseURL() {
let client = LLMClientFactory.make(
providerId: "gemini",
baseURL: "",
apiKey: "key",
model: "gemini-2.5-flash"
) as! OpenAICompatibleClient
XCTAssertEqual(
client.baseURL,
"https://generativelanguage.googleapis.com/v1beta/openai"
)
}
/// DeepSeek V4 thinking defaults ON server-side; polish must explicitly disable it.
func testDeepSeekPolishDisablesThinkingByDefault() {
var body: [String: Any] = ["model": "deepseek-v4-flash"]
LLMThinkingControl.apply(
to: &body,
providerId: "deepseek",
baseURL: "https://api.deepseek.com/v1",
model: "deepseek-v4-flash",
enabled: false
)
let thinking = body["thinking"] as? [String: Any]
XCTAssertEqual(thinking?["type"] as? String, "disabled")
XCTAssertNil(body["reasoning_effort"], "disabled path must not send reasoning_effort (low→high on DeepSeek)")
}
func testDeepSeekPolishEnablesThinkingWhenToggledOn() {
var body: [String: Any] = ["model": "deepseek-v4-flash"]
LLMThinkingControl.apply(
to: &body,
providerId: "deepseek",
baseURL: "https://api.deepseek.com/v1",
model: "deepseek-v4-flash",
enabled: true
)
let thinking = body["thinking"] as? [String: Any]
XCTAssertEqual(thinking?["type"] as? String, "enabled")
XCTAssertEqual(body["reasoning_effort"] as? String, "high")
}
/// Ordinary OpenAI chat models must not get thinking fields when the toggle is off.
func testOpenAIChatDoesNotInjectThinkingWhenDisabled() {
var body: [String: Any] = ["model": "gpt-4o-mini"]
LLMThinkingControl.apply(
to: &body,
providerId: "openai",
baseURL: "https://api.openai.com/v1",
model: "gpt-4o-mini",
enabled: false
)
XCTAssertNil(body["thinking"])
XCTAssertNil(body["reasoning_effort"])
XCTAssertNil(body["thinking_config"])
}
func testLLMThinkingDefaultIsOffInFreshConfiguration() {
let suiteName = "group.com.osgkeyboard.shared.tests.thinking.\(UUID().uuidString)"
let defaults = UserDefaults(suiteName: suiteName)!
defaults.removePersistentDomain(forName: suiteName)
defer { defaults.removePersistentDomain(forName: suiteName) }
let config = ProviderConfig(defaults: defaults)
XCTAssertFalse(config.llmThinkingEnabled, "cloud polish thinking must default off")
}
func testResolveLLMEndpointUsesPresetWhenProviderPinned() {
let suiteName = "group.com.osgkeyboard.shared.tests.\(UUID().uuidString)"
let defaults = UserDefaults(suiteName: suiteName)!