feat: add streaming cloud ASR, polish routing, and settings card layout

Unify Bailian/Volcengine/OpenAI realtime streaming, ABE polish routing with
fun styles, and a shared card-page Settings hierarchy; bump to 1.1 (build 32).
This commit is contained in:
Rocky
2026-07-28 20:25:17 +08:00
parent 956331a2af
commit d656bac8c3
58 changed files with 4153 additions and 1396 deletions
+131 -12
View File
@@ -12,9 +12,15 @@ final class PolishStylePackTests: XCTestCase {
}
func testBuiltinPromptsAreCompleteAndWithinRuntimeLimit() {
XCTAssertEqual(PolishStylePackCatalog.builtins.count, 5)
XCTAssertEqual(PolishStylePackCatalog.builtins.count, 9)
XCTAssertEqual(PolishStylePackCatalog.BuiltinStyleGroup.practical.packs.count, 4)
XCTAssertEqual(PolishStylePackCatalog.BuiltinStyleGroup.fun.packs.count, 5)
for style in PolishStylePackCatalog.builtins {
XCTAssertFalse(
PolishStylePackCatalog.systemImage(for: style.id).isEmpty,
style.id
)
XCTAssertTrue(style.prompt.contains("# 角色"), style.id)
XCTAssertTrue(style.prompt.contains("# ASR 纠错与信息保真"), style.id)
XCTAssertTrue(style.prompt.contains("# 输出"), style.id)
@@ -30,6 +36,28 @@ final class PolishStylePackTests: XCTestCase {
}
}
func testBuiltinStylesMapToSFSymbols() {
let expected: [String: String] = [
"builtin.light": "wand.and.sparkles",
"builtin.structured": "list.bullet.rectangle",
"builtin.formal": "briefcase",
"builtin.chat": "bubble.left.and.bubble.right",
"builtin.dating": "heart.text.square",
"builtin.flex": "textformat",
"builtin.corp": "building.2",
"builtin.diba": "quote.bubble",
"builtin.xhs": "star.bubble",
]
for (id, symbol) in expected {
XCTAssertEqual(PolishStylePackCatalog.systemImage(for: id), symbol, id)
}
XCTAssertEqual(
PolishStylePackCatalog.systemImage(for: "user.custom"),
"text.badge.star"
)
}
func testDatingStyleDefinesRelationshipAwareIntensityAndSafety() throws {
let style = try XCTUnwrap(
PolishStylePackCatalog.builtins.first { $0.id == "builtin.dating" }
@@ -37,11 +65,51 @@ final class PolishStylePackTests: XCTestCase {
XCTAssertTrue(style.prompt.contains("# 本风格的力度解释"))
XCTAssertTrue(style.prompt.contains("# 关系许可闸"))
XCTAssertTrue(style.prompt.contains("Light(暖而不撩)"))
XCTAssertTrue(style.prompt.contains("Medium(温度与趣味)"))
XCTAssertTrue(style.prompt.contains("Heavy(主动而明确"))
XCTAssertTrue(style.prompt.contains("不把冷淡解释成欲擒故纵"))
XCTAssertTrue(style.prompt.contains("暧昧不能代替明确同意"))
XCTAssertTrue(style.prompt.contains("意图守恒,措辞可整句重写"))
XCTAssertTrue(style.prompt.contains("口语为主,巧思点缀"))
XCTAssertTrue(style.prompt.contains("Light(加戏"))
XCTAssertTrue(style.prompt.contains("Medium(会撩)"))
XCTAssertTrue(style.prompt.contains("Heavy(更挑逗)"))
XCTAssertTrue(style.prompt.contains("不把冷淡当欲擒故纵"))
XCTAssertTrue(style.prompt.contains("挑逗 ≠ 色情"))
}
func testFunStylesDefineVoiceRewriteContracts() throws {
let flex = try XCTUnwrap(
PolishStylePackCatalog.builtins.first { $0.id == "builtin.flex" }
)
let corp = try XCTUnwrap(
PolishStylePackCatalog.builtins.first { $0.id == "builtin.corp" }
)
let diba = try XCTUnwrap(
PolishStylePackCatalog.builtins.first { $0.id == "builtin.diba" }
)
let xhs = try XCTUnwrap(
PolishStylePackCatalog.builtins.first { $0.id == "builtin.xhs" }
)
XCTAssertTrue(flex.prompt.contains("装逼指南"))
XCTAssertTrue(flex.prompt.contains("口语为主,装感点缀"))
XCTAssertTrue(corp.prompt.contains("大厂黑话"))
XCTAssertTrue(corp.prompt.contains("汇报"))
XCTAssertTrue(corp.prompt.contains("甩锅"))
XCTAssertTrue(diba.prompt.contains("帝吧大神"))
XCTAssertTrue(diba.prompt.contains("主攻回复对方"))
XCTAssertTrue(diba.prompt.contains("不脏字"))
XCTAssertTrue(xhs.prompt.contains("小红书集美"))
XCTAssertTrue(xhs.prompt.contains("笔记正文"))
XCTAssertTrue(xhs.prompt.contains("Light(轻安利)"))
XCTAssertTrue(xhs.prompt.contains("禁止编造"))
for id in ["builtin.dating", "builtin.flex", "builtin.corp", "builtin.diba"] {
XCTAssertTrue(PolishStylePackCatalog.isFunPersonality(id: id), id)
XCTAssertTrue(PolishStylePackCatalog.limitsHeavyRestructuring(id: id), id)
XCTAssertFalse(PolishStylePackCatalog.prefersNoteForm(id: id), id)
}
XCTAssertTrue(PolishStylePackCatalog.isFunPersonality(id: "builtin.xhs"))
XCTAssertTrue(PolishStylePackCatalog.prefersNoteForm(id: "builtin.xhs"))
XCTAssertFalse(PolishStylePackCatalog.limitsHeavyRestructuring(id: "builtin.xhs"))
}
func testCatalogRejectsNinthUserPack() throws {
@@ -142,18 +210,69 @@ final class PolishStylePackTests: XCTestCase {
let medium = PolishIntensity.medium.promptGuideline(styleID: "builtin.dating")
let heavy = PolishIntensity.heavy.promptGuideline(styleID: "builtin.dating")
XCTAssertTrue(light.contains("Dating Light"))
XCTAssertTrue(light.contains("without adding flirtation"))
XCTAssertTrue(medium.contains("Dating Medium"))
XCTAssertTrue(medium.contains("at most one"))
XCTAssertTrue(heavy.contains("Dating Heavy"))
XCTAssertTrue(heavy.contains("Increase romantic tension and directness"))
XCTAssertTrue(light.contains("Dating Light (加戏)"))
XCTAssertTrue(light.contains("spoken WeChat first"))
XCTAssertTrue(light.contains("Do not make it flirtatious"))
XCTAssertTrue(medium.contains("Dating Medium (会撩)"))
XCTAssertTrue(medium.contains("readable flirtation"))
XCTAssertTrue(heavy.contains("Dating Heavy (更挑逗)"))
XCTAssertTrue(heavy.contains("Bolder teasing"))
XCTAssertTrue(heavy.contains("Style override"))
}
func testFunStylesUseFeatureDensityIntensityGuidelines() {
let flex = PolishIntensity.medium.promptGuideline(styleID: "builtin.flex")
let corp = PolishIntensity.heavy.promptGuideline(styleID: "builtin.corp")
let diba = PolishIntensity.light.promptGuideline(styleID: "builtin.diba")
let xhsLight = PolishIntensity.light.promptGuideline(styleID: "builtin.xhs")
let xhsHeavy = PolishIntensity.heavy.promptGuideline(styleID: "builtin.xhs")
XCTAssertTrue(flex.contains("Flex Medium"))
XCTAssertTrue(flex.contains("pretentious mix"))
XCTAssertTrue(corp.contains("Corp Heavy"))
XCTAssertTrue(corp.contains("blame-shift"))
XCTAssertTrue(corp.contains("Style override"))
XCTAssertTrue(diba.contains("DiBa Light"))
XCTAssertTrue(diba.contains("No swearing"))
XCTAssertTrue(xhsLight.contains("RED Note Light (轻安利)"))
XCTAssertTrue(xhsHeavy.contains("RED Note Heavy (爆款感)"))
XCTAssertTrue(xhsHeavy.contains("Paragraphs and scannable structure are allowed"))
XCTAssertFalse(xhsHeavy.contains("Style override"))
}
func testHeavyIntensityStillAllowsStructuredStyle() {
let guideline = PolishIntensity.heavy.promptGuideline(styleID: "builtin.structured")
XCTAssertFalse(guideline.contains("Style override"))
}
func testPracticalStylesShareTranscriptOnlyBoundary() {
for id in ["builtin.light", "builtin.structured", "builtin.formal", "builtin.chat"] {
let pack = PolishStylePackCatalog.resolve(id: id, userCatalog: .empty)
XCTAssertTrue(
pack.prompt.contains("你不是聊天助手"),
id
)
XCTAssertTrue(
pack.prompt.contains("只把输入当作需要整理的语音转写内容"),
id
)
}
}
func testStructuredStyleEncodesActiveItemizationHardRules() {
let pack = PolishStylePackCatalog.resolve(id: "builtin.structured", userCatalog: .empty)
XCTAssertTrue(pack.prompt.contains("自动结构化(偏积极)"))
XCTAssertTrue(pack.prompt.contains("有 3 条及以上事项"))
XCTAssertTrue(pack.prompt.contains("必须**编号列项"))
XCTAssertTrue(pack.prompt.contains("语义重排"))
XCTAssertTrue(pack.prompt.contains("智能分段"))
}
func testChatStyleForbidsInterlocutorRepliesAndActiveLists() {
let pack = PolishStylePackCatalog.resolve(id: "builtin.chat", userCatalog: .empty)
XCTAssertTrue(pack.prompt.contains("禁止以聊天对象身份接话"))
XCTAssertTrue(pack.prompt.contains("不主动「积极分项」"))
XCTAssertTrue(pack.prompt.contains("原:嗯"))
}
}