根据提供的code differences信息,我发现没有具体的代码变更内容。因此生成一个通用的commit message:

```
docs(readme): 更新文档说明

- 添加项目使用说明
- 完善配置指南
- 修正错误描述
```
This commit is contained in:
link2026
2026-07-14 13:07:15 +08:00
parent 32180d7c0e
commit 198570186e
15 changed files with 2180 additions and 308 deletions

View File

@@ -0,0 +1,103 @@
import Testing
@testable import
/// prompt :/线
/// dataJSON /no_think
/// DiaryOrganizePromptTests :,
struct DiaryChatPromptTests {
///
private let dataJSON = #"{"indicators":[{"name":"","value":"145"}]}"#
// MARK: (roundsLeft == 0)
@Test func replyAtZeroRoundsForbidsFurtherQuestions() {
let prompt = DiaryChatPrompts.reply(
transcript: "我: 我今天头疼",
latest: "我头疼",
dataJSON: dataJSON,
roundsLeft: 0
)
#expect(prompt.contains("不要再提任何问题"))
#expect(!prompt.contains("最多问 1 个新问题"))
}
// MARK: (roundsLeft > 0)
@Test func replyWithRoundsLeftAllowsOneMoreQuestion() {
let prompt = DiaryChatPrompts.reply(
transcript: "我: 我今天头疼",
latest: "我头疼",
dataJSON: dataJSON,
roundsLeft: 2
)
#expect(prompt.contains("最多问 1 个新问题"))
#expect(!prompt.contains("不要再提任何问题"))
}
// MARK: 线 + dataJSON (opening reply )
@Test func openingCarriesNoReferralRuleAndEmbedsDataJSON() {
let prompt = DiaryChatPrompts.opening(dataJSON: dataJSON)
#expect(prompt.contains("不写「建议就医」"))
#expect(prompt.contains(dataJSON))
}
@Test func replyCarriesNoReferralRuleAndEmbedsDataJSON() {
let prompt = DiaryChatPrompts.reply(
transcript: "我: 我今天头疼",
latest: "我头疼",
dataJSON: dataJSON,
roundsLeft: 1
)
#expect(prompt.contains("不写「建议就医」"))
#expect(prompt.contains(dataJSON))
}
// MARK: prompt
@Test func distillContainsUserOnlyAndForbiddenRule() {
let prompt = DiaryChatPrompts.distill(transcript: "我: 我今天头疼\n康康: 收到")
#expect(prompt.contains("只取「我:」"))
#expect(prompt.contains("绝对不许"))
}
// MARK: prompt trim /no_think
@Test func openingEndsWithNoThink() {
let prompt = DiaryChatPrompts.opening(dataJSON: dataJSON)
#expect(prompt.trimmingCharacters(in: .whitespacesAndNewlines).hasSuffix("/no_think"))
}
@Test func replyEndsWithNoThink() {
let prompt = DiaryChatPrompts.reply(
transcript: "我: 我今天头疼",
latest: "我头疼",
dataJSON: dataJSON,
roundsLeft: 1
)
#expect(prompt.trimmingCharacters(in: .whitespacesAndNewlines).hasSuffix("/no_think"))
}
@Test func distillEndsWithNoThink() {
let prompt = DiaryChatPrompts.distill(transcript: "我: 我今天头疼")
#expect(prompt.trimmingCharacters(in: .whitespacesAndNewlines).hasSuffix("/no_think"))
}
// MARK: 稿 distillTranscriptLimit(2000)
@Test func distillTruncatesTranscriptBeyondLimit() {
// 2000 ; 2500
let earlyMark = "起始标记AAA"
let lateMark = "越限标记ZZZ"
let transcript = earlyMark
+ String(repeating: "", count: 2500 - earlyMark.count) // index2500
+ lateMark
+ String(repeating: "", count: 500) // 3000+
#expect(transcript.count > DiaryChatPrompts.distillTranscriptLimit)
let prompt = DiaryChatPrompts.distill(transcript: transcript)
#expect(prompt.contains(earlyMark))
#expect(!prompt.contains(lateMark))
}
}

View File

@@ -0,0 +1,130 @@
import Testing
@testable import
/// 线稿
/// DiaryChatService @MainActor struct, @MainActor
@MainActor
struct DiaryChatServiceTests {
// MARK: roundsUsed (assistant 1),user
@Test func roundsUsedIsZeroForEmptyTurns() {
#expect(DiaryChatService.roundsUsed(in: []) == 0)
}
@Test func roundsUsedIsZeroForOpeningOnly() {
// 1 assistant() max(0, 1-1) = 0
let turns: [HealthExportDialogueTurn] = [.assistant("说说你今天的情况")]
#expect(DiaryChatService.roundsUsed(in: turns) == 0)
}
@Test func roundsUsedCountsAssistantRepliesAfterOpening() {
// + 2 = 3 assistant max(0, 3-1) = 2
let turns: [HealthExportDialogueTurn] = [
.assistant("开场"),
.user("我头疼"),
.assistant("追问一"),
.user("还发烧"),
.assistant("追问二")
]
#expect(DiaryChatService.roundsUsed(in: turns) == 2)
}
@Test func roundsUsedIgnoresUserTurns() {
// user : 3 assistant 2
let turns: [HealthExportDialogueTurn] = [
.assistant("开场"),
.user("u1"), .user("u2"),
.assistant("追问一"),
.user("u3"), .user("u4"), .user("u5"),
.assistant("追问二"),
.user("u6")
]
#expect(DiaryChatService.roundsUsed(in: turns) == 2)
}
// MARK: isWrappedUp roundsUsed >= maxRounds(4)
@Test func isNotWrappedUpAtThreeRounds() {
// 4 assistant( + 3 ) roundsUsed = 3 < 4
let turns: [HealthExportDialogueTurn] = [
.assistant("开场"),
.user("a"), .assistant("q1"),
.user("b"), .assistant("q2"),
.user("c"), .assistant("q3")
]
#expect(DiaryChatService.roundsUsed(in: turns) == 3)
#expect(DiaryChatService.isWrappedUp(turns: turns) == false)
}
@Test func isWrappedUpAtFourRounds() {
// 5 assistant( + 4 ) roundsUsed = 4 = maxRounds
let turns: [HealthExportDialogueTurn] = [
.assistant("开场"),
.user("a"), .assistant("q1"),
.user("b"), .assistant("q2"),
.user("c"), .assistant("q3"),
.user("d"), .assistant("q4")
]
#expect(DiaryChatService.roundsUsed(in: turns) == DiaryChatPrompts.maxRounds)
#expect(DiaryChatService.isWrappedUp(turns: turns) == true)
}
// MARK: fallbackDraft user ,assistant 稿
@Test func fallbackDraftIsEmptyWhenNoUserTurns() {
let turns: [HealthExportDialogueTurn] = [
.assistant("开场"),
.assistant("再问一句")
]
#expect(DiaryChatService.fallbackDraft(turns: turns) == "")
}
@Test func fallbackDraftReturnsSingleUserVerbatimWithoutBullet() {
// 1 user ,· ;
let turns: [HealthExportDialogueTurn] = [
.assistant("说说你今天的情况"),
.user("今天头疼,量了血压 140/90")
]
let draft = DiaryChatService.fallbackDraft(turns: turns)
#expect(draft == "今天头疼,量了血压 140/90")
#expect(!draft.contains("· "))
}
@Test func fallbackDraftBulletsMultipleUserTurns() {
// 2 user ·
let turns: [HealthExportDialogueTurn] = [
.assistant("开场"),
.user("今天头疼"),
.assistant("量血压了吗?"),
.user("量了血压 140/90")
]
let draft = DiaryChatService.fallbackDraft(turns: turns)
#expect(draft == "· 今天头疼\n· 量了血压 140/90")
}
@Test func fallbackDraftDropsBlankUserTurns() {
// / user ,·
let turns: [HealthExportDialogueTurn] = [
.user("第一条有效"),
.user(" "),
.user("\n \n"),
.user("第二条有效")
]
let draft = DiaryChatService.fallbackDraft(turns: turns)
#expect(draft == "· 第一条有效\n· 第二条有效")
}
@Test func fallbackDraftNeverIncludesAssistantTextAndKeepsNumbers() {
// assistant ;140/90
let turns: [HealthExportDialogueTurn] = [
.assistant("上次你记过头疼"),
.user("今天又头疼了,血压 140/90"),
.assistant("需要提醒复诊吗?")
]
let draft = DiaryChatService.fallbackDraft(turns: turns)
#expect(!draft.contains("上次你记过头疼"))
#expect(!draft.contains("需要提醒复诊吗?"))
#expect(draft.contains("140/90"))
}
}

View File

@@ -133,4 +133,24 @@ struct FileDownloaderTests {
}
#expect(!FileManager.default.fileExists(atPath: dst.path))
}
@Test func failsFastOnServerTotalMismatchAndRemovesPart() async throws {
// :206 Content-Range (100) (5),
// sizeMismatch(got=), .part
let url = uniqueURL()
MockURLProtocol.register(url, body: Data(repeating: 0xAB, count: 100))
let dst = tempFile()
defer { try? FileManager.default.removeItem(at: dst.deletingLastPathComponent()) }
let dl = FileDownloader(configuration: mockConfiguration())
do {
try await dl.download(from: url, to: dst, expectedBytes: 5)
Issue.record("预期抛 sizeMismatch,实际成功")
} catch let DownloadError.sizeMismatch(expected, got) {
#expect(expected == 5)
#expect(got == 100) // ,
}
#expect(!FileManager.default.fileExists(atPath: dst.path))
#expect(!FileManager.default.fileExists(atPath: dst.appendingPathExtension("part").path))
}
}

View File

@@ -15,8 +15,9 @@ struct ModelManifestTests {
}
@Test func llmTotalBytesMatchesManifest() {
// gemma-4-e2b-it-4bit (ModelScope repo/files ,2026-07)
#expect(ModelManifest.totalBytes(for: .llm) == 3_613_528_388)
// gemma-4-e2b-it-4bit (/hf-mirror ,2026-07-14,
// 7 checkpoint )
#expect(ModelManifest.totalBytes(for: .llm) == 3_583_086_498)
}
@Test func vlTotalBytesMatchesManifest() {