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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 12 additions & 10 deletions Sources/Fluid/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1845,15 +1845,17 @@ struct ContentView: View {
return self.buildSystemPrompt(appInfo: appInfo, dictationSlot: dictationSlot)
}()

// Dictation enhancement folds the prompt + transcript into a single user
// turn (substituting `${transcript}` when present, otherwise appending
// the transcript after a blank line). Non-dictation callers — the AI
// chat tab specifically — keep the legacy two-message layout where
// the prompt is the system turn and the input is the user turn.
// Dictation enhancement normally sends prompt instructions as the
// system turn and only the tagged transcript as the user turn. Explicit
// `${transcript}` templates keep their legacy single user-message shape.
// Non-dictation callers — the AI chat tab specifically — keep the
// legacy two-message layout where the prompt is the system turn and
// the input is the user turn.
let systemPrompt: String
let userMessageContent: String
if isDictationCall {
systemPrompt = ""
let usesTranscriptTemplate = promptText.contains(SettingsStore.transcriptPlaceholder)
systemPrompt = usesTranscriptTemplate ? "" : promptText
userMessageContent = SettingsStore.renderDictationUserMessage(
promptText: promptText,
transcript: inputText
Expand Down Expand Up @@ -1931,10 +1933,10 @@ struct ContentView: View {
)
}

// Build messages array. For dictation enhancement the whole prompt +
// transcript is folded into a single user message, so we omit the
// (empty) system role. Non-dictation callers keep the legacy
// system + user shape.
// Build messages array. Dictation enhancement uses system + user by
// default, but explicit `${transcript}` templates omit the system role
// because the full template is already the user message. Non-dictation
// callers keep the legacy system + user shape.
var messages: [[String: Any]] = []
if !systemPrompt.isEmpty {
messages.append(["role": "system", "content": systemPrompt])
Expand Down
14 changes: 6 additions & 8 deletions Sources/Fluid/Persistence/SettingsStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1230,18 +1230,16 @@ final class SettingsStore: ObservableObject {
/// when composing the user message for a dictation enhancement call.
static let transcriptPlaceholder = "${transcript}"

/// Compose the user-turn string for a dictation enhancement call by folding
/// the transcript into the prompt template. If the template contains the
/// `${transcript}` placeholder, the placeholder is replaced; otherwise
/// the transcript is appended after a blank line, matching the pre-PR
/// behaviour of sending the transcript as a separate user message.
/// Compose the user-turn string for a dictation enhancement call.
/// If the prompt explicitly contains the `${transcript}` placeholder, keep
/// honoring that full user-message template. Otherwise the prompt belongs
/// in the system turn, and the user turn contains only tagged transcript
/// content.
static func renderDictationUserMessage(promptText: String, transcript: String) -> String {
if promptText.contains(self.transcriptPlaceholder) {
return promptText.replacingOccurrences(of: self.transcriptPlaceholder, with: transcript)
}
let trimmedPrompt = promptText.trimmingCharacters(in: .whitespacesAndNewlines)
if trimmedPrompt.isEmpty { return transcript }
return promptText + "\n\n" + transcript
return "<transcript>\n\(transcript)\n</transcript>"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Update the custom-prompt request-body test for separated roles

For every non-placeholder custom prompt, this return value no longer contains promptText, but testCustomPromptOnly_omitsBasePromptFromEffectivePromptAndRequestBody in LLMClientRequestBodyTests.swift still constructs a request containing only this user message and then asserts that the message contents include profile.prompt. That test is part of the FluidDictationIntegrationTests target and will now fail deterministically; update it to construct the new system-plus-user message layout.

Useful? React with 👍 / 👎.

}

private func defaultPromptResolution(
Expand Down
3 changes: 2 additions & 1 deletion Sources/Fluid/Services/DictationPostProcessingService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,8 @@ final class DictationPostProcessingService {
}

let promptText = settings.effectiveDictationSystemPrompt(for: dictationSlot, appBundleID: nil)
let systemPrompt = ""
let usesTranscriptTemplate = promptText.contains(SettingsStore.transcriptPlaceholder)
let systemPrompt = usesTranscriptTemplate ? "" : promptText
let userMessageContent = SettingsStore.renderDictationUserMessage(
promptText: promptText,
transcript: trimmed
Expand Down
10 changes: 10 additions & 0 deletions Tests/FluidDictationIntegrationTests/DictationE2ETests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1465,6 +1465,16 @@ final class DictationE2ETests: XCTestCase {
}
}

func testDictationUserMessageWrapsTranscriptWithoutPromptInstructions() {
let userMessage = SettingsStore.renderDictationUserMessage(
promptText: "Clean up this transcript and output only the edited text.",
transcript: "hello fluid voice"
)

XCTAssertEqual(userMessage, "<transcript>\nhello fluid voice\n</transcript>")
XCTAssertFalse(userMessage.contains("Clean up this transcript"))
}

Comment on lines +1468 to +1477

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Cover complete message composition

This test exercises only renderDictationUserMessage, so it does not verify that ordinary prompts become system messages or that ${transcript} templates continue to omit the system role. Add coverage for the complete message array in both cases to protect the role-separation behavior this change introduces.

Knowledge Base Used: AI Enhancement Pipeline

Prompt To Fix With AI
This is a comment left during a code review.
Path: Tests/FluidDictationIntegrationTests/DictationE2ETests.swift
Line: 1468-1477

Comment:
**Cover complete message composition**

This test exercises only `renderDictationUserMessage`, so it does not verify that ordinary prompts become system messages or that `${transcript}` templates continue to omit the system role. Add coverage for the complete message array in both cases to protect the role-separation behavior this change introduces.

**Knowledge Base Used:** [AI Enhancement Pipeline](https://app.greptile.com/altic/-/custom-context/knowledge-base/altic-dev/fluidvoice/-/docs/ai-enhancement.md)

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Codex

func testCustomProviderSettingsRoundTripThroughSettingsStore() {
self.withProviderSettingsRestored {
let settings = SettingsStore.shared
Expand Down
Loading