Skip to content
Merged
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
1 change: 1 addition & 0 deletions src/MandoCode.Desktop.Tests/MandoCode.Desktop.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
the file-naming convention is a real contract worth pinning; pure System.IO, no WinUI. -->
<Compile Include="..\MandoCode.Desktop\Services\BuiltInBackgrounds.cs" Link="src\BuiltInBackgrounds.cs" />
<Compile Include="..\MandoCode.Desktop\Services\TranscriptJournal.cs" Link="src\TranscriptJournal.cs" />
<Compile Include="..\MandoCode.Desktop\Services\ModelNoticeReplay.cs" Link="src\ModelNoticeReplay.cs" />
<Compile Include="..\MandoCode.Desktop\Services\ConversationLog.cs" Link="src\ConversationLog.cs" />
<!-- History's full-text matching + snippet extraction. Pure (text in, match out); the file reads
and caching live in ConversationTextCache, which is NOT compiled here because it touches
Expand Down
32 changes: 32 additions & 0 deletions src/MandoCode.Desktop.Tests/ModelNoticeReplayTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using MandoCode.Desktop.Services;
using Xunit;

namespace MandoCode.Desktop.Tests;

public class ModelNoticeReplayTests
{
private const string Sizing = "Context window sized to 16k tokens for this model tier (applies from your next message).";
private static string Notice(string text, string kind = "dim") =>
$"<div class=\"notice-card {kind}\"><span class=\"notice-emoji\">ℹ️</span><span class=\"notice-text\">{text}</span></div>";

[Fact]
public void RepeatedRestoresDropAllHistoricalSetupNoticesButKeepConversation()
{
var answer = "<div class=\"assistant\">How can I help?</div>";
var history = new[] { answer, Notice(Sizing), Notice(Sizing), Notice(Sizing),
Notice("Cloud models run on ollama.com and need an active cloud subscription.") };
var replay = history.Where(h => !ModelNoticeReplay.IsTransient(h)).ToArray();
Assert.Equal(new[] { answer }, replay);
Assert.Equal(replay, replay.Where(h => !ModelNoticeReplay.IsTransient(h)));
}

[Fact]
public void QuotedWordingAndActualWarningsArePreserved()
{
Assert.False(ModelNoticeReplay.IsTransient($"<div class=\"assistant\">{Notice(Sizing)}</div>"));
Assert.False(ModelNoticeReplay.IsTransient($"<div class=\"user-echo\">{Sizing}</div>"));
Assert.False(ModelNoticeReplay.IsTransient(Notice(Sizing, "warn")));
Assert.False(ModelNoticeReplay.IsTransient(Notice("The context window is too small for this request.")));
Assert.False(ModelNoticeReplay.IsTransient(Notice("Project root changed to: C:\\project")));
}
}
19 changes: 19 additions & 0 deletions src/MandoCode.Desktop/Services/ModelNoticeReplay.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Text.RegularExpressions;

namespace MandoCode.Desktop.Services;

/// <summary>Old model setup notices describe a prior runtime, not the restored conversation.</summary>
public static class ModelNoticeReplay
{
public static bool IsTransient(string html)
{
// Match only standalone system notices. Keep user messages, assistant explanations,
// and errors even when they quote the same wording.
var match = Regex.Match(html,
"\\A<div class=\"notice-card dim\"><span class=\"notice-emoji\">[^<]*</span><span class=\"notice-text\">([^<]*)</span></div>\\z");
if (!match.Success) return false;
var text = match.Groups[1].Value;
return text == "Cloud models run on ollama.com and need an active cloud subscription."
|| Regex.IsMatch(text, @"\AContext window sized to \d+k tokens for this model tier \(applies from your next message\)\.\z");
}
}
3 changes: 2 additions & 1 deletion src/MandoCode.Desktop/Services/TranscriptHtmlBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ public static bool IsEphemeralStatus(string blockHtml) =>
// Boot/progress narration — true only while it was happening. ("Project root
// changed to: X" is deliberately NOT here: that's a real event, kept as history.)
|| blockHtml.Contains(">Rebuilding the AI session for the new project…<", StringComparison.Ordinal)
|| blockHtml.Contains(">✓ Ready.<", StringComparison.Ordinal);
|| blockHtml.Contains(">✓ Ready.<", StringComparison.Ordinal)
|| ModelNoticeReplay.IsTransient(blockHtml);

/// <summary>A compact status pill — a colored state dot, a bold primary value, and an
/// optional dim qualifier. The dot replaces status emoji: crisp and theme-aware.
Expand Down
6 changes: 4 additions & 2 deletions src/MandoCode.Desktop/ViewModels/ChatController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1583,7 +1583,8 @@ private async Task ApplyModelSwitchAsync(string modelTag)
if (recommendedCtx > 0 && recommendedCtx != _config.ContextLength)
{
_config.ContextLength = recommendedCtx;
_transcript.Append(_html.Dim($"Context window sized to {recommendedCtx / 1024}k tokens for this model tier (applies from your next message)."));
if (!DeferModelAnnouncement)
_transcript.Append(_html.Dim($"Context window sized to {recommendedCtx / 1024}k tokens for this model tier (applies from your next message)."));
}

_busy.Start("Switching model...");
Expand All @@ -1609,7 +1610,8 @@ private async Task ApplyModelSwitchAsync(string modelTag)
if (MandoCodeConfig.IsCloudModel(modelTag) && !_cloudNoticeShown)
{
_cloudNoticeShown = true;
_transcript.Append(_html.Dim("Cloud models run on ollama.com and need an active cloud subscription."));
if (!DeferModelAnnouncement)
_transcript.Append(_html.Dim("Cloud models run on ollama.com and need an active cloud subscription."));
}

// Only mention the cleared context — and offer a snapshot — when there was actually a
Expand Down
Loading