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:
@@ -12,7 +12,7 @@
|
||||
// │ (transcript preview) │
|
||||
// │ ┊ │
|
||||
// │ ◯ mic (centred) │ ← action cluster:
|
||||
// │ [delete] [ space ] [return] │ mic + bottom row
|
||||
// │ [delete] [ return ] [space] │ mic + bottom row
|
||||
// │ ┊ │
|
||||
// └───────────────────────────────────────────┘
|
||||
|
||||
@@ -191,7 +191,7 @@ public struct KeyboardRootView: View {
|
||||
|
||||
// MARK: - Action cluster
|
||||
|
||||
/// Mic centred above a bottom row: delete · space · return (or swapped).
|
||||
/// Mic centred above a bottom row: delete · smart return · space (or swapped).
|
||||
/// The side cursor-drag pads are SwiftUI layout wrappers around UIKit
|
||||
/// pan recognizers, avoiding SwiftUI gesture delivery issues in
|
||||
/// keyboard extensions.
|
||||
@@ -225,13 +225,13 @@ public struct KeyboardRootView: View {
|
||||
|
||||
HStack(spacing: KeyboardLayoutMetrics.bottomActionSpacing) {
|
||||
if swapKeys {
|
||||
bottomReturnButton(disabled: editingBlocked)
|
||||
bottomSpaceButton(disabled: editingBlocked)
|
||||
bottomReturnButton(disabled: editingBlocked)
|
||||
bottomDeleteButton(disabled: editingBlocked)
|
||||
} else {
|
||||
bottomDeleteButton(disabled: editingBlocked)
|
||||
bottomSpaceButton(disabled: editingBlocked)
|
||||
bottomReturnButton(disabled: editingBlocked)
|
||||
bottomSpaceButton(disabled: editingBlocked)
|
||||
}
|
||||
}
|
||||
.opacity(dragging ? 0 : 1)
|
||||
@@ -265,19 +265,20 @@ public struct KeyboardRootView: View {
|
||||
RectangularToolbarButton(spaceStyle: true, label: "space", disabled: disabled) {
|
||||
state.insertSpace()
|
||||
}
|
||||
.frame(height: KeyboardLayoutMetrics.bottomActionRowHeight)
|
||||
}
|
||||
|
||||
private func bottomReturnButton(disabled: Bool) -> some View {
|
||||
RectangularToolbarButton(systemName: "return", label: "newline", disabled: disabled) {
|
||||
state.insertNewline()
|
||||
}
|
||||
.frame(
|
||||
width: KeyboardLayoutMetrics.bottomActionFixedWidth,
|
||||
height: KeyboardLayoutMetrics.bottomActionRowHeight
|
||||
)
|
||||
}
|
||||
|
||||
private func bottomReturnButton(disabled: Bool) -> some View {
|
||||
let title = ExtL10n.string(state.returnKeyRole.titleKey)
|
||||
return RectangularToolbarButton(title: title, label: title, disabled: disabled) {
|
||||
state.insertNewline()
|
||||
}
|
||||
.frame(height: KeyboardLayoutMetrics.bottomActionRowHeight)
|
||||
}
|
||||
|
||||
/// Option C: block typing keys during the full voice-input pipeline.
|
||||
private var voiceInputBlocksEditing: Bool {
|
||||
switch state.phase {
|
||||
|
||||
@@ -10,6 +10,7 @@ import OSGKeyboardShared
|
||||
|
||||
private enum ToolbarButtonMetrics {
|
||||
static let iconSize: CGFloat = 14
|
||||
static let titleSize: CGFloat = 16
|
||||
static let cornerRadius: CGFloat = 12
|
||||
static let spaceBarCapsuleWidth: CGFloat = 31
|
||||
static let pressScale: CGFloat = 0.94
|
||||
@@ -138,6 +139,7 @@ struct RectangularToolbarButton: View {
|
||||
|
||||
let systemName: String?
|
||||
let spaceStyle: Bool
|
||||
let title: String?
|
||||
let label: String
|
||||
let disabled: Bool
|
||||
let action: () -> Void
|
||||
@@ -145,14 +147,25 @@ struct RectangularToolbarButton: View {
|
||||
init(systemName: String, label: String, disabled: Bool = false, action: @escaping () -> Void) {
|
||||
self.systemName = systemName
|
||||
self.spaceStyle = false
|
||||
self.title = nil
|
||||
self.label = label
|
||||
self.disabled = disabled
|
||||
self.action = action
|
||||
}
|
||||
|
||||
init(title: String, label: String, disabled: Bool = false, action: @escaping () -> Void) {
|
||||
self.systemName = nil
|
||||
self.spaceStyle = false
|
||||
self.label = label
|
||||
self.disabled = disabled
|
||||
self.action = action
|
||||
self.title = title
|
||||
}
|
||||
|
||||
init(spaceStyle: Bool, label: String, disabled: Bool = false, action: @escaping () -> Void) {
|
||||
self.systemName = nil
|
||||
self.spaceStyle = spaceStyle
|
||||
self.title = nil
|
||||
self.label = label
|
||||
self.disabled = disabled
|
||||
self.action = action
|
||||
@@ -170,6 +183,10 @@ struct RectangularToolbarButton: View {
|
||||
Image(systemName: systemName)
|
||||
.font(.system(size: ToolbarButtonMetrics.iconSize, weight: .semibold))
|
||||
.foregroundStyle(palette.textPrimary)
|
||||
} else if let title {
|
||||
Text(title)
|
||||
.font(.system(size: ToolbarButtonMetrics.titleSize, weight: .semibold))
|
||||
.foregroundStyle(palette.textPrimary)
|
||||
}
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
|
||||
Reference in New Issue
Block a user