feat(keyboard): add clipboard AI skills, hint keywords, and voice session fixes

Idle chips show entities with category icons; a fresh copy surfaces Reply/Summarize/Translate; abort/cancel/empty-tap no longer leave the mic stuck.
This commit is contained in:
Rocky
2026-08-13 15:47:32 +08:00
parent 9f308fadd2
commit 631617ee53
48 changed files with 1627 additions and 345 deletions
+16 -19
View File
@@ -1,34 +1,21 @@
// AIHintPool.swift
// OSGKeyboard · Shared
//
// Builds the idle carousel pool: 100% clipboard cards while eligible,
// otherwise a shuffled mix of non-clipboard local + remote cards.
// Builds the idle carousel pool from local + remote cards. Clipboard
// sentence cards are excluded: the 30s copy window shows skill chips.
import Foundation
public enum AIHintPool: Sendable {
public static func activeCards(
pack: AIHintPack,
clipboardHistoryEnabled: Bool,
newestClipboard: ClipboardHistoryEntry?,
now: Date = Date()
pack: AIHintPack
) -> [AIHintCard] {
let clipboardEligible = clipboardHistoryEnabled
&& newestClipboard.map { ClipboardHistoryPolicy.isEligibleForAIHint($0, now: now) } == true
let clipboardCards = pack.cards.filter(\.requiresClipboard30s)
let regularCards = pack.cards.filter { !$0.requiresClipboard30s }
.filter { !isHistoricalToday($0) }
// Within 30s: only clipboard-related sentences.
if clipboardEligible {
let pool = clipboardCards.isEmpty
? AIHintLocalCatalog.cards(locale: pack.locale).filter(\.requiresClipboard30s)
: clipboardCards
return pool.sorted { $0.priority > $1.priority }
}
// Otherwise: drop clipboard-conditioned cards entirely.
// Clipboard-conditioned sentences no longer rotate in the carousel;
// the 30s window shows skill chips instead. Keep evergreen content
// loaded so leaving the window does not flash a leftover clipboard card.
var merged = regularCards
let localRegular = AIHintLocalCatalog.cards(locale: pack.locale)
.filter { !$0.requiresClipboard30s }
@@ -38,6 +25,16 @@ public enum AIHintPool: Sendable {
return merged.sorted { $0.priority > $1.priority }
}
/// Copy-then-30s window where clipboard skill chips replace the carousel.
public static func isClipboardSkillWindowActive(
clipboardHistoryEnabled: Bool,
newestClipboard: ClipboardHistoryEntry?,
now: Date = Date()
) -> Bool {
clipboardHistoryEnabled
&& newestClipboard.map { ClipboardHistoryPolicy.isEligibleForAIHint($0, now: now) } == true
}
/// Prompt for a tapped card. Clipboard cards fail closed so an expired
/// window can never send an instruction without its material.
public static func resolvePrompt(