feat(account): add managed credits and cloud gateway
Introduce optional Apple account-backed credits with scoped gateway access while preserving local and BYOK paths. Refresh assistant behavior, tests, privacy disclosures, docs, and the website for the 2.0 experience.
This commit is contained in:
@@ -14,10 +14,10 @@
|
||||
// • `phase` is a real stored property (no derivation) — the previous
|
||||
// "derive from recordStream" shim locked out every press after the first.
|
||||
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
import Combine
|
||||
import OSGKeyboardShared
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
private final class KeyboardHostingController: UIHostingController<KeyboardSurfaceRoot> {
|
||||
override var preferredScreenEdgesDeferringSystemGestures: UIRectEdge {
|
||||
@@ -994,7 +994,7 @@ public final class KeyboardViewController: UIInputViewController {
|
||||
host.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
host.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
host.view.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
host.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||
host.view.bottomAnchor.constraint(equalTo: view.bottomAnchor)
|
||||
])
|
||||
host.didMove(toParent: self)
|
||||
hosting = host
|
||||
|
||||
@@ -110,7 +110,11 @@ final class AIKeyboardCoordinator {
|
||||
if case .materialUnavailable = resolution {
|
||||
AIAgentShortcutRun.trace("keyboard.submit rejected clipboardUnavailable skill=\(skill.id)")
|
||||
}
|
||||
submitResolvedPrompt(resolution, thinkingEnabled: skill.thinkingEnabled)
|
||||
submitResolvedPrompt(
|
||||
resolution,
|
||||
taskKind: skill.managedGatewayTaskKind,
|
||||
thinkingEnabled: skill.thinkingEnabled
|
||||
)
|
||||
}
|
||||
|
||||
/// Tap an idle hint card: resolve its material, skip the mic, ask the host.
|
||||
@@ -121,7 +125,7 @@ final class AIKeyboardCoordinator {
|
||||
for: card,
|
||||
clipboardText: ClipboardHistoryStore.shared.newestAIHintEligibleEntry()?.text
|
||||
)
|
||||
submitResolvedPrompt(resolution)
|
||||
submitResolvedPrompt(resolution, taskKind: .aiQuestion)
|
||||
}
|
||||
|
||||
private var canAcceptIdleSubmit: Bool {
|
||||
@@ -136,6 +140,7 @@ final class AIKeyboardCoordinator {
|
||||
|
||||
private func submitResolvedPrompt(
|
||||
_ resolution: AIClipboardPrompt.Resolution,
|
||||
taskKind: ManagedGatewayTaskKind,
|
||||
thinkingEnabled: Bool? = nil
|
||||
) {
|
||||
guard case .ready(let prompt) = resolution else {
|
||||
@@ -154,6 +159,7 @@ final class AIKeyboardCoordinator {
|
||||
let disposition = flow.submitAIQuestion(
|
||||
text: prompt,
|
||||
conversationID: conversationID,
|
||||
taskKind: taskKind,
|
||||
thinkingEnabled: thinkingEnabled
|
||||
)
|
||||
if case .rejected(let rejection) = disposition {
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
// (changeCount-driven). Writes accepted text into ClipboardHistoryStore.
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import OSGKeyboardShared
|
||||
import UIKit
|
||||
|
||||
@MainActor
|
||||
protocol ClipboardPasteboardProviding: AnyObject {
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
//
|
||||
// Flow session start, recording, watchdogs, and result delivery handling.
|
||||
|
||||
import UIKit
|
||||
import OSGKeyboardShared
|
||||
import UIKit
|
||||
|
||||
@MainActor
|
||||
final class KeyboardFlowCoordinator {
|
||||
@@ -681,6 +681,7 @@ final class KeyboardFlowCoordinator {
|
||||
func submitAIQuestion(
|
||||
text: String,
|
||||
conversationID: UUID,
|
||||
taskKind: ManagedGatewayTaskKind = .aiQuestion,
|
||||
thinkingEnabled: Bool? = nil
|
||||
) -> FlowUtteranceStartDisposition {
|
||||
let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
@@ -689,6 +690,7 @@ final class KeyboardFlowCoordinator {
|
||||
.aiQuestion(
|
||||
conversationID: conversationID,
|
||||
prefilledQuestion: trimmed,
|
||||
taskKind: taskKind,
|
||||
thinkingEnabled: thinkingEnabled
|
||||
)
|
||||
)
|
||||
@@ -1191,6 +1193,7 @@ final class KeyboardFlowCoordinator {
|
||||
sourceHistoryEntryID: request.sourceHistoryEntryID,
|
||||
sourceHistoryEntryRevision: request.sourceHistoryEntryRevision,
|
||||
aiConversationID: request.aiConversationID,
|
||||
aiTaskKind: request.aiTaskKind,
|
||||
startDeadlineAt: action == .startRecording ? currentStartDeadlineAt : nil,
|
||||
processingDeadlineAt: action == .stopRecording && request.isEdit
|
||||
? Date().timeIntervalSince1970
|
||||
@@ -1898,6 +1901,7 @@ final class KeyboardFlowCoordinator {
|
||||
utteranceMode: .aiQuestion,
|
||||
aiConversationID: currentUtteranceRequest?.aiConversationID,
|
||||
aiQuestionText: text,
|
||||
aiTaskKind: currentUtteranceRequest?.aiTaskKind,
|
||||
aiThinkingEnabled: currentUtteranceRequest?.aiThinkingEnabled,
|
||||
startDeadlineAt: currentStartDeadlineAt
|
||||
)
|
||||
@@ -2034,7 +2038,7 @@ final class KeyboardFlowCoordinator {
|
||||
onAIStreamingAnswer(result.text ?? "", result.utteranceId)
|
||||
return
|
||||
}
|
||||
if (result.status == .partial || result.status == .rawReady),
|
||||
if result.status == .partial || result.status == .rawReady,
|
||||
let partial = result.text,
|
||||
!partial.isEmpty {
|
||||
if result.resolvedUtteranceMode == .aiQuestion,
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
// without re-running LLM polish in the extension. Also tracks the last
|
||||
// voice insertion so the undo button can roll it back safely.
|
||||
|
||||
import UIKit
|
||||
import OSGKeyboardShared
|
||||
import UIKit
|
||||
|
||||
@MainActor
|
||||
final class KeyboardTextInserter {
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
// UIKit grid for the expanded Chinese candidate panel. UILabel cells +
|
||||
// UICollectionView recycling stay far cheaper than hundreds of SwiftUI Buttons.
|
||||
|
||||
import OSGKeyboardShared
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
import OSGKeyboardShared
|
||||
|
||||
struct CandidateExpandGridView: UIViewRepresentable {
|
||||
var candidates: [TypingCandidate]
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
//
|
||||
// Switches between voice and typing surfaces driven by KeyboardState.surface.
|
||||
|
||||
import SwiftUI
|
||||
import OSGKeyboardShared
|
||||
import SwiftUI
|
||||
|
||||
struct KeyboardSurfaceRoot: View {
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
// overlapping fingers commit in press order, delete repeats on down,
|
||||
// Shift holds while that finger owns it.
|
||||
|
||||
import OSGKeyboardShared
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
import OSGKeyboardShared
|
||||
|
||||
struct TypingKeyTouchPad: UIViewRepresentable {
|
||||
var layout: TypingKeyLayout
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
// Typing surface: candidate bar + QWERTY / 123 / symbols.
|
||||
// Top-leading control returns to the voice surface.
|
||||
|
||||
import SwiftUI
|
||||
import OSGKeyboardShared
|
||||
import SwiftUI
|
||||
|
||||
enum TypingLayoutMetrics {
|
||||
// Size decisions live in `TypingSurfaceMetrics` (Shared) so the UIKit
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
// duplicate Localizable.strings variant groups into the main app target only.
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import OSGKeyboardShared
|
||||
import SwiftUI
|
||||
|
||||
enum ExtL10n {
|
||||
private static let table = "Keyboard"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
// Role-based typing haptics (no screen-position mapping). Intensity comes
|
||||
// from Settings → General → Haptics (off / light / strong).
|
||||
|
||||
import UIKit
|
||||
import OSGKeyboardShared
|
||||
import UIKit
|
||||
|
||||
/// Key roles drive distinct Taptic styles — closer to a real keyboard than
|
||||
/// a single buzz for every tap.
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
// Plays the built-in iOS keyboard click sounds so the custom bottom-row
|
||||
// keys (space / return / delete) sound identical to the stock keyboard.
|
||||
|
||||
import UIKit
|
||||
import AudioToolbox
|
||||
import UIKit
|
||||
|
||||
/// 让键盘扩展支持系统点击音。`UIDevice.playInputClick()` 只有在「某个
|
||||
/// 可见的输入视图遵循本协议且返回 true」时才会发声。键盘扩展的根视图
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
// Unified assistant surface: tap for ordinary dictation, hold for AI, and use
|
||||
// the same idle layout for hotwords, clipboard skills, editing, and Send.
|
||||
|
||||
import SwiftUI
|
||||
import OSGKeyboardShared
|
||||
import SwiftUI
|
||||
|
||||
struct AIKeyboardView: View {
|
||||
private enum Layout {
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
//
|
||||
// Clipboard suggestion strip, enable-guide sheet, and history panel.
|
||||
|
||||
import SwiftUI
|
||||
import OSGKeyboardShared
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - Suggestion strip (Doubao-style)
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
// a transient `UIEvent`. SwiftUI draws the shared native key chrome while a
|
||||
// transparent `UIButton` owns touch delivery.
|
||||
|
||||
import OSGKeyboardShared
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
import OSGKeyboardShared
|
||||
|
||||
struct GlobeInputModeButton: UIViewRepresentable {
|
||||
@ObservedObject var state: KeyboardState
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
// Shared top-right input switcher used by both voice and typing surfaces.
|
||||
// The same footprint is replaced by candidates while Chinese is composing.
|
||||
|
||||
import SwiftUI
|
||||
import OSGKeyboardShared
|
||||
import SwiftUI
|
||||
|
||||
private struct KeyboardTabSelectionNamespaceKey: EnvironmentKey {
|
||||
static let defaultValue: Namespace.ID? = nil
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// LastInputEditView.swift
|
||||
// OSGKeyboard · Keyboard Extension
|
||||
|
||||
import SwiftUI
|
||||
import OSGKeyboardShared
|
||||
import SwiftUI
|
||||
|
||||
struct LastInputEditView: View {
|
||||
private enum Layout {
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
//
|
||||
// Bottom-row action keys: repeating delete, space, and return.
|
||||
|
||||
import SwiftUI
|
||||
import OSGKeyboardShared
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - Layout metrics
|
||||
|
||||
|
||||
Reference in New Issue
Block a user