Expand onboarding and adaptive keyboard intelligence
Add resilient usage analytics, OOBE gateway flows, clipboard semantic ranking, purchase recovery, style learning, and managed current-information search.
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
// ClipboardSkillSemanticRankerTests.swift
|
||||
// OSGKeyboardTests
|
||||
|
||||
@testable import OSGKeyboardShared
|
||||
import XCTest
|
||||
|
||||
final class ClipboardSkillSemanticRankerTests: XCTestCase {
|
||||
func testForeignQuestionPromotesTranslationAndSourceLanguageReply() {
|
||||
let ranked = rank(
|
||||
text: "Could you send me the final proposal by Friday?",
|
||||
analysis: analysis(language: "en", question: detected())
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
Array(ranked.prefix(3)),
|
||||
[
|
||||
AIClipboardSkillCatalog.translateID,
|
||||
AIClipboardSkillCatalog.replyInSourceLanguageID,
|
||||
AIClipboardSkillCatalog.replyID
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func testInvitationWithDatePromotesCalendarAndBothReplyChoices() {
|
||||
let ranked = rank(
|
||||
text: "今晚七点老地方吃饭,你能来吗?",
|
||||
analysis: analysis(
|
||||
hasDate: true,
|
||||
question: detected(),
|
||||
invitation: detected()
|
||||
)
|
||||
)
|
||||
|
||||
XCTAssertEqual(ranked.first, AIClipboardSkillCatalog.extractEventsID)
|
||||
XCTAssertLessThan(
|
||||
tryIndex(AIClipboardSkillCatalog.acceptInvitationID, in: ranked),
|
||||
tryIndex(AIClipboardSkillCatalog.summarizeID, in: ranked)
|
||||
)
|
||||
XCTAssertLessThan(
|
||||
tryIndex(AIClipboardSkillCatalog.declineInvitationID, in: ranked),
|
||||
tryIndex(AIClipboardSkillCatalog.summarizeID, in: ranked)
|
||||
)
|
||||
}
|
||||
|
||||
func testAddressPromotesNavigation() {
|
||||
let ranked = rank(
|
||||
text: "北京市朝阳区望京街 10 号,到了给我电话。",
|
||||
analysis: analysis(hasAddress: true)
|
||||
)
|
||||
|
||||
XCTAssertEqual(ranked.first, AIClipboardSkillCatalog.navigateID)
|
||||
}
|
||||
|
||||
func testTaskListPromotesTodoAndOrganizationSkills() {
|
||||
let ranked = rank(
|
||||
text: "- 更新报价单\n- 给客户回邮件\n- 周五前提交合同",
|
||||
analysis: analysis(task: detected())
|
||||
)
|
||||
|
||||
XCTAssertEqual(ranked.first, AIClipboardSkillCatalog.extractTodosID)
|
||||
XCTAssertLessThan(
|
||||
tryIndex(AIClipboardSkillCatalog.organizeListID, in: ranked),
|
||||
tryIndex(AIClipboardSkillCatalog.replyID, in: ranked)
|
||||
)
|
||||
XCTAssertLessThan(
|
||||
tryIndex(AIClipboardSkillCatalog.acceptTaskID, in: ranked),
|
||||
tryIndex(AIClipboardSkillCatalog.replyID, in: ranked)
|
||||
)
|
||||
}
|
||||
|
||||
func testAdvisoryComplaintPromotesEmpathyWithoutAutomaticApproval() {
|
||||
let complaint = ClipboardIntentLabel(
|
||||
confidence: 0.82,
|
||||
threshold: 0.6,
|
||||
isDetected: false,
|
||||
isApprovedForAutomaticRouting: false
|
||||
)
|
||||
let ranked = rank(
|
||||
text: "这个问题已经发生三次了,请尽快处理。",
|
||||
analysis: analysis(
|
||||
sentiment: .negative,
|
||||
complaint: complaint
|
||||
)
|
||||
)
|
||||
|
||||
XCTAssertEqual(ranked.first, AIClipboardSkillCatalog.empathyReplyID)
|
||||
XCTAssertEqual(ranked.dropFirst().first, AIClipboardSkillCatalog.askForDetailsID)
|
||||
}
|
||||
|
||||
func testLongTextPromotesSummaryConclusionsAndNotes() {
|
||||
let ranked = rank(
|
||||
text: String(repeating: "这是需要阅读和整理的长文内容。", count: 40),
|
||||
analysis: analysis()
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
Array(ranked.prefix(3)),
|
||||
[
|
||||
AIClipboardSkillCatalog.summarizeID,
|
||||
AIClipboardSkillCatalog.extractConclusionsID,
|
||||
AIClipboardSkillCatalog.saveToNotesID
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func testNoSignalPreservesSavedOrder() {
|
||||
let baseline = [
|
||||
AIClipboardSkillCatalog.businessReplyID,
|
||||
AIClipboardSkillCatalog.translateID,
|
||||
AIClipboardSkillCatalog.replyID
|
||||
]
|
||||
let ranked = ClipboardSkillSemanticRanker.ranked(
|
||||
skills: skills(ids: baseline),
|
||||
sourceText: "好的",
|
||||
analysis: analysis(),
|
||||
uiLanguage: .chinese
|
||||
).map(\.id)
|
||||
|
||||
XCTAssertEqual(ranked, baseline)
|
||||
}
|
||||
|
||||
private func rank(
|
||||
text: String,
|
||||
analysis: ClipboardSemanticAnalysis
|
||||
) -> [String] {
|
||||
ClipboardSkillSemanticRanker.ranked(
|
||||
skills: AIClipboardSkillCatalog.catalog,
|
||||
sourceText: text,
|
||||
analysis: analysis,
|
||||
uiLanguage: .chinese
|
||||
).map(\.id)
|
||||
}
|
||||
|
||||
private func skills(ids: [String]) -> [AIClipboardSkill] {
|
||||
ids.compactMap { AIClipboardSkillCatalog.skill(id: $0) }
|
||||
}
|
||||
|
||||
private func tryIndex(_ id: String, in ids: [String]) -> Int {
|
||||
ids.firstIndex(of: id) ?? Int.max
|
||||
}
|
||||
|
||||
private func detected() -> ClipboardIntentLabel {
|
||||
ClipboardIntentLabel(
|
||||
confidence: 0.95,
|
||||
threshold: 0.6,
|
||||
isDetected: true,
|
||||
isApprovedForAutomaticRouting: true
|
||||
)
|
||||
}
|
||||
|
||||
private func absent() -> ClipboardIntentLabel {
|
||||
ClipboardIntentLabel(
|
||||
confidence: 0,
|
||||
threshold: 1,
|
||||
isDetected: false,
|
||||
isApprovedForAutomaticRouting: false
|
||||
)
|
||||
}
|
||||
|
||||
private func analysis(
|
||||
language: String? = nil,
|
||||
hasDate: Bool = false,
|
||||
hasAddress: Bool = false,
|
||||
sentiment: ClipboardSentimentLabel = .unknown,
|
||||
task: ClipboardIntentLabel? = nil,
|
||||
question: ClipboardIntentLabel? = nil,
|
||||
invitation: ClipboardIntentLabel? = nil,
|
||||
complaint: ClipboardIntentLabel? = nil
|
||||
) -> ClipboardSemanticAnalysis {
|
||||
ClipboardSemanticAnalysis(
|
||||
language: language.map {
|
||||
ClipboardLanguageLabel(identifier: $0, confidence: 0.99)
|
||||
},
|
||||
dates: hasDate
|
||||
? [ClipboardDateLabel(
|
||||
sourceText: "今晚七点",
|
||||
date: Date(),
|
||||
duration: 0,
|
||||
timeZoneIdentifier: nil
|
||||
)]
|
||||
: [],
|
||||
addresses: hasAddress
|
||||
? [ClipboardTextLabel(sourceText: "望京街 10 号")]
|
||||
: [],
|
||||
phoneNumbers: [],
|
||||
urls: [],
|
||||
personNames: [],
|
||||
organizationNames: [],
|
||||
sentiment: sentiment,
|
||||
sentimentConfidence: sentiment == .unknown ? 0 : 0.9,
|
||||
task: task ?? absent(),
|
||||
question: question ?? absent(),
|
||||
invitation: invitation ?? absent(),
|
||||
complaint: complaint ?? absent()
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user