Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions Strand/AI/AICoach.swift
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,18 @@ final class AICoachEngine: ObservableObject {
return Self.defaultSystemPrompt
}

/// The running bundle's language also applies to briefs and replies to English context/history.
var requestSystemPrompt: String {
Self.localizedSystemPrompt(systemPrompt, languageTag: Bundle.main.preferredLocalizations.first ?? "en")
}

static func localizedSystemPrompt(_ prompt: String, languageTag: String) -> String {
let tag = languageTag.trimmingCharacters(in: .whitespacesAndNewlines)
return prompt + "\n\nReply in the app's language (BCP-47: \(tag.isEmpty ? "en" : tag)). "
+ "Use this language even if the context or earlier messages are in another language, "
+ "unless the user explicitly requests a different language."
}

/// The user's stored prompt override, or the default when nothing custom is set. The UI binds its
/// editor to this: writing persists the override; writing a blank string clears it (back to default).
var customSystemPrompt: String {
Expand Down Expand Up @@ -1048,7 +1060,7 @@ final class AICoachEngine: ObservableObject {
try await provider.client.send(
key: key,
model: model,
systemPrompt: systemPrompt,
systemPrompt: requestSystemPrompt,
messages: messages,
session: session
)
Expand All @@ -1065,7 +1077,7 @@ final class AICoachEngine: ObservableObject {
try await provider.client.streamWithImage(
key: key,
model: model,
systemPrompt: systemPrompt,
systemPrompt: requestSystemPrompt,
messages: messages,
inlineImage: inlineImage,
session: session,
Expand Down
19 changes: 19 additions & 0 deletions StrandTests/AICoachPromptAndStressTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,25 @@ final class AICoachPromptAndStressTests: XCTestCase {
XCTAssertFalse(engine.hasCustomSystemPrompt)
}

func testReplyLanguageFollowsAppLanguageWhilePreservingEditedPrompt() {
let engine = makeEngine()
engine.customSystemPrompt = "Coach in two sentences."
XCTAssertEqual(engine.systemPrompt, "Coach in two sentences.")
XCTAssertEqual(engine.requestSystemPrompt, AICoachEngine.localizedSystemPrompt(
engine.systemPrompt, languageTag: Bundle.main.preferredLocalizations.first ?? "en"
))
for tag in ["de", "en", "es", "fr", "it", "pt-PT", "pl", "zh", "de-DE"] {
let prompt = AICoachEngine.localizedSystemPrompt(engine.systemPrompt, languageTag: tag)
XCTAssertEqual(prompt, "Coach in two sentences.\n\nReply in the app's language (BCP-47: \(tag)). "
+ "Use this language even if the context or earlier messages are in another language, "
+ "unless the user explicitly requests a different language.")
}
XCTAssertEqual(
AICoachEngine.localizedSystemPrompt("Coach", languageTag: " \n "),
AICoachEngine.localizedSystemPrompt("Coach", languageTag: "en")
)
}

// MARK: - Feature 2: derived stress line

func testStressIndexSummaryFormatsOneRoundedNumber() {
Expand Down
Loading