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
21 changes: 20 additions & 1 deletion PolyPilot.Tests/UiStatePersistenceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public void UiState_DefaultValues()
Assert.Equal("/", state.CurrentPage);
Assert.Null(state.ActiveSession);
Assert.Equal(20, state.FontSize);
Assert.Empty(state.InputModes);
}

[Fact]
Expand All @@ -25,7 +26,12 @@ public void UiState_RoundTripSerialization()
{
CurrentPage = "/dashboard",
ActiveSession = "my-session",
FontSize = 16
FontSize = 16,
InputModes = new Dictionary<string, string>
{
["my-session"] = "autopilot",
["another-session"] = "plan"
}
};

var json = JsonSerializer.Serialize(state);
Expand All @@ -35,6 +41,8 @@ public void UiState_RoundTripSerialization()
Assert.Equal("/dashboard", restored!.CurrentPage);
Assert.Equal("my-session", restored.ActiveSession);
Assert.Equal(16, restored.FontSize);
Assert.Equal("autopilot", restored.InputModes["my-session"]);
Assert.Equal("plan", restored.InputModes["another-session"]);
}

[Fact]
Expand All @@ -48,6 +56,17 @@ public void UiState_NullActiveSession_Serializes()
Assert.Null(restored!.ActiveSession);
}

[Fact]
public void UiState_LegacyJsonWithoutInputModes_Deserializes()
{
const string legacyJson = """{"CurrentPage":"/dashboard","ActiveSession":"s1","FontSize":20,"ExpandedGrid":false}""";
var restored = JsonSerializer.Deserialize<UiState>(legacyJson);

Assert.NotNull(restored);
Assert.NotNull(restored!.InputModes);
Assert.Empty(restored.InputModes);
}

[Fact]
public void ActiveSessionEntry_RoundTrip()
{
Expand Down
9 changes: 5 additions & 4 deletions PolyPilot/Components/ExpandedSessionView.razor
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,9 @@
</div>
<div class="input-status-bar">
<div class="mode-switcher">
<button class="mode-btn @(!PlanMode ? "active" : "")" @onclick="() => OnSetPlanMode.InvokeAsync(false)">Chat</button>
<button class="mode-btn @(PlanMode ? "active" : "")" @onclick="() => OnSetPlanMode.InvokeAsync(true)">Plan</button>
<button class="mode-btn @(InputMode == "chat" ? "active" : "")" @onclick='() => OnSetInputMode.InvokeAsync("chat")'>Chat</button>
<button class="mode-btn @(InputMode == "plan" ? "active" : "")" @onclick='() => OnSetInputMode.InvokeAsync("plan")'>Plan</button>
<button class="mode-btn @(InputMode == "autopilot" ? "active" : "")" @onclick='() => OnSetInputMode.InvokeAsync("autopilot")'>Autopilot</button>
</div>
<span class="status-sep">·</span>
<span class="log-label" style="font-size:var(--type-footnote)">Log</span>
Expand Down Expand Up @@ -233,7 +234,7 @@
[Parameter] public string Intent { get; set; } = "";
[Parameter] public string? Error { get; set; }
[Parameter] public SessionUsageInfo? UsageInfo { get; set; }
[Parameter] public bool PlanMode { get; set; }
[Parameter] public string InputMode { get; set; } = "chat";
[Parameter] public string CurrentModel { get; set; } = "";
[Parameter] public IReadOnlyList<string> AvailableModels { get; set; } = Array.Empty<string>();
[Parameter] public List<PendingImage>? PendingImages { get; set; }
Expand All @@ -253,7 +254,7 @@
[Parameter] public EventCallback OnClearQueue { get; set; }
[Parameter] public EventCallback<int> OnRemoveQueuedMessage { get; set; }
[Parameter] public EventCallback<int> OnRemovePendingImage { get; set; }
[Parameter] public EventCallback<bool> OnSetPlanMode { get; set; }
[Parameter] public EventCallback<string> OnSetInputMode { get; set; }
[Parameter] public EventCallback<string?> OnSetModel { get; set; }
[Parameter] public EventCallback<int> OnFontSizeChange { get; set; }
[Parameter] public EventCallback OnLoadMore { get; set; }
Expand Down
33 changes: 28 additions & 5 deletions PolyPilot/Components/Pages/Dashboard.razor
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
Intent="@(intentBySession.TryGetValue(session.Name, out var i2) ? i2 : "")"
Error="@(errorBySession.TryGetValue(session.Name, out var e2) ? e2 : null)"
UsageInfo="@(usageBySession.TryGetValue(session.Name, out var u2) ? u2 : null)"
PlanMode="@planModeBySession.GetValueOrDefault(session.Name)"
InputMode="@GetInputMode(session.Name)"
CurrentModel="@GetExpandedModel(session)"
AvailableModels="availableModels"
PendingImages="@(pendingImagesBySession.TryGetValue(session.Name, out var pi2) ? pi2 : null)"
Expand All @@ -97,7 +97,7 @@
OnClearQueue="() => ClearQueue(session.Name)"
OnRemoveQueuedMessage="(idx) => RemoveQueuedMessage(session.Name, idx)"
OnRemovePendingImage="(idx) => RemovePendingImage(session.Name, idx)"
OnSetPlanMode="(enabled) => SetPlanMode(session.Name, enabled)"
OnSetInputMode="(mode) => SetInputMode(session.Name, mode)"
OnSetModel="(model) => SetExpandedModel(session, model)"
OnFontSizeChange="HandleFontSizeChange"
OnLoadMore="() => LoadMoreExpandedMessages(session.Name)"
Expand Down Expand Up @@ -227,7 +227,7 @@
private Dictionary<string, string> intentBySession = new();
private Dictionary<string, string> errorBySession = new();
private Dictionary<string, SessionUsageInfo> usageBySession = new();
private Dictionary<string, bool> planModeBySession = new();
private Dictionary<string, string> inputModeBySession = new();
private Dictionary<string, List<PendingImage>> pendingImagesBySession = new();
private Dictionary<string, string> shellCwdBySession = new();
private Dictionary<string, List<string>> commandHistoryBySession = new();
Expand Down Expand Up @@ -296,6 +296,14 @@
// Restore font size
if (uiState.FontSize >= 12 && uiState.FontSize <= 24)
fontSize = uiState.FontSize;

// Restore per-session input modes (chat/plan/autopilot)
if (uiState.InputModes?.Count > 0)
{
inputModeBySession = uiState.InputModes
.Where(kvp => !string.IsNullOrWhiteSpace(kvp.Key) && IsValidInputMode(kvp.Value))
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
}

// Restore expanded grid state (inverted: isCompactGrid)
isCompactGrid = !uiState.ExpandedGrid;
Expand Down Expand Up @@ -935,8 +943,11 @@
return;
}

if (planModeBySession.GetValueOrDefault(sessionName))
var inputMode = GetInputMode(sessionName);
if (inputMode == "plan")
finalPrompt = $"[[PLAN]] {finalPrompt}";
else if (inputMode == "autopilot")
finalPrompt = $"[[AUTOPILOT]] {finalPrompt}";

var dispatch = await FiestaService.DispatchMentionedWorkAsync(sessionName, finalPrompt);
if (dispatch.MentionsFound)
Expand Down Expand Up @@ -2157,7 +2168,19 @@
}
}

private void SetPlanMode(string sessionName, bool enabled) => planModeBySession[sessionName] = enabled;
private static bool IsValidInputMode(string mode) =>
mode is "chat" or "plan" or "autopilot";

private string GetInputMode(string sessionName) =>
inputModeBySession.TryGetValue(sessionName, out var mode) && IsValidInputMode(mode)
? mode
: "chat";

private void SetInputMode(string sessionName, string mode)
{
inputModeBySession[sessionName] = IsValidInputMode(mode) ? mode : "chat";
CopilotService.SaveUiState("/dashboard", inputModes: inputModeBySession);
}

private void IncreaseFontSize() { if (fontSize < 24) { fontSize += 2; ApplyFontSize(); ShowFontBubble(); } }
private void DecreaseFontSize() { if (fontSize > 12) { fontSize -= 2; ApplyFontSize(); ShowFontBubble(); } }
Expand Down
7 changes: 5 additions & 2 deletions PolyPilot/Services/CopilotService.Persistence.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ or ObjectDisposedException

}

public void SaveUiState(string currentPage, string? activeSession = null, int? fontSize = null, string? selectedModel = null, bool? expandedGrid = null, string? expandedSession = "<<unspecified>>")
public void SaveUiState(string currentPage, string? activeSession = null, int? fontSize = null, string? selectedModel = null, bool? expandedGrid = null, string? expandedSession = "<<unspecified>>", Dictionary<string, string>? inputModes = null)
{
try
{
Expand All @@ -178,7 +178,10 @@ public void SaveUiState(string currentPage, string? activeSession = null, int? f
FontSize = fontSize ?? existing?.FontSize ?? 20,
SelectedModel = selectedModel ?? existing?.SelectedModel,
ExpandedGrid = expandedGrid ?? existing?.ExpandedGrid ?? false,
ExpandedSession = expandedSession == "<<unspecified>>" ? existing?.ExpandedSession : expandedSession
ExpandedSession = expandedSession == "<<unspecified>>" ? existing?.ExpandedSession : expandedSession,
InputModes = inputModes != null
? new Dictionary<string, string>(inputModes)
: existing?.InputModes ?? new Dictionary<string, string>()
};
var json = JsonSerializer.Serialize(state);
File.WriteAllText(UiStateFile, json);
Expand Down
1 change: 1 addition & 0 deletions PolyPilot/Services/CopilotService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1678,6 +1678,7 @@ public class UiState
public string? SelectedModel { get; set; }
public bool ExpandedGrid { get; set; }
public string? ExpandedSession { get; set; }
public Dictionary<string, string> InputModes { get; set; } = new();
}

public class ActiveSessionEntry
Expand Down