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
10 changes: 9 additions & 1 deletion Components/Layout/SessionSidebar.razor
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,19 @@
<div class="sidebar-header">
<h3><svg style="vertical-align:middle" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 8V4H8"/><rect x="2" y="8" width="20" height="8" rx="2"/><path d="M6 16v4"/><path d="M18 16v4"/><circle cx="9" cy="12" r="1" fill="currentColor"/><circle cx="15" cy="12" r="1" fill="currentColor"/></svg> AutoPilot</h3>
<p class="status @(CopilotService.IsInitialized ? "connected" : "disconnected")">
@(CopilotService.IsInitialized ? "● Connected" : "○ Disconnected")
@if (CopilotService.IsInitialized)
{
<text>● @(CopilotService.CurrentMode == ConnectionMode.Persistent ? "Persistent" : "Embedded")</text>
}
else
{
<text>○ Disconnected</text>
}
</p>
<div class="nav-tabs">
<a href="/" class="nav-tab @(currentPage == "/" ? "active" : "")" @onclick='() => { CopilotService.SaveUiState("/"); currentPage = "/"; }'><svg style="vertical-align:middle" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg> Chat</a>
<a href="/dashboard" class="nav-tab @(currentPage == "/dashboard" ? "active" : "")" @onclick='() => { CopilotService.SaveUiState("/dashboard"); currentPage = "/dashboard"; }'><svg style="vertical-align:middle" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="9"/><rect x="14" y="3" width="7" height="5"/><rect x="14" y="12" width="7" height="9"/><rect x="3" y="16" width="7" height="5"/></svg> Dashboard</a>
<a href="/settings" class="nav-tab @(currentPage == "/settings" ? "active" : "")" @onclick='() => { CopilotService.SaveUiState("/settings"); currentPage = "/settings"; }'>⚙️</a>
</div>
</div>

Expand Down
13 changes: 11 additions & 2 deletions Components/Pages/Dashboard.razor
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@

<div class="card-messages">
@{
var lastMessages = session.History.TakeLast(6).ToList();
var lastMessages = session.History.ToList().TakeLast(6).ToList();
}
@if (!lastMessages.Any())
{
Expand Down Expand Up @@ -206,7 +206,16 @@

try
{
_ = CopilotService.SendPromptAsync(sessionName, prompt.Trim());
_ = CopilotService.SendPromptAsync(sessionName, prompt.Trim()).ContinueWith(t =>
{
if (t.IsFaulted)
{
InvokeAsync(() =>
{
Console.WriteLine($"Error sending to {sessionName}: {t.Exception?.InnerException?.Message}");
});
}
});
}
catch (Exception ex)
{
Expand Down
224 changes: 73 additions & 151 deletions Components/Pages/Home.razor

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions Components/Pages/Home.razor.css
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,19 @@
flex-direction: row-reverse;
}

.message.system {
align-self: center;
max-width: 100%;
justify-content: center;
}

.system-text {
font-size: 0.8rem;
color: rgba(255, 255, 255, 0.4);
text-align: center;
padding: 0.25rem 0;
}

.message-avatar {
width: 36px;
height: 36px;
Expand Down Expand Up @@ -142,6 +155,7 @@
.message.assistant .message-text {
background: rgba(255,255,255,0.1);
border-bottom-left-radius: 4px;
color: white;
}

.message.streaming .message-text::after {
Expand All @@ -160,6 +174,7 @@
.message.user .message-time { text-align: right; }

/* === Markdown body (::deep for MarkupString inner content) === */
::deep .markdown-body { color: white; }
::deep .markdown-body p { margin: 0 0 0.5rem 0; }
::deep .markdown-body p:last-child { margin-bottom: 0; }

Expand Down Expand Up @@ -280,6 +295,19 @@

.reasoning-block.collapsed .reasoning-content:not(.preview) { display: none; }

/* === Tool summary (grouped completed tools) === */
.tool-summary {
display: flex;
align-items: center;
gap: 0.4rem;
padding: 0.25rem 0.75rem;
font-size: 0.8rem;
color: rgba(255,255,255,0.4);
max-width: 90%;
}

.tool-summary.has-error { color: rgba(248, 113, 113, 0.6); }

/* === Tool cards === */
.tool-card {
border-radius: 8px;
Expand Down Expand Up @@ -471,6 +499,14 @@
.input-area .input-row button:hover:not(:disabled) { background: #2563eb; }
.input-area .input-row button:disabled { opacity: 0.5; cursor: not-allowed; }

.input-area .input-row .stop-btn {
padding: 0.75rem;
background: #ef4444;
border-radius: 8px;
min-width: auto;
}
.input-area .input-row .stop-btn:hover { background: #dc2626; }

/* === Error bar === */
.error-bar {
display: flex;
Expand Down
162 changes: 162 additions & 0 deletions Components/Pages/Settings.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
@page "/settings"
@using AutoPilot.App.Services
@using AutoPilot.App.Models
@inject CopilotService CopilotService
@inject ServerManager ServerManager
@inject NavigationManager Nav

<div class="settings-page">
<div class="settings-header">
<h2>⚙️ Connection Settings</h2>
</div>

<div class="settings-section">
<h3>Transport Mode</h3>
<div class="mode-cards">
<div class="mode-card @(settings.Mode == ConnectionMode.Embedded ? "selected" : "")"
@onclick="() => SetMode(ConnectionMode.Embedded)">
<div class="mode-icon">🔌</div>
<div class="mode-title">Embedded</div>
<div class="mode-desc">Default. Copilot process dies when app closes.</div>
</div>
<div class="mode-card @(settings.Mode == ConnectionMode.Persistent ? "selected" : "")"
@onclick="() => SetMode(ConnectionMode.Persistent)">
<div class="mode-icon">🏗️</div>
<div class="mode-title">Persistent</div>
<div class="mode-desc">Copilot server survives app restarts. Sessions persist.</div>
</div>
</div>
</div>

@if (settings.Mode == ConnectionMode.Persistent)
{
<div class="settings-section">
<h3>Persistent Server</h3>
<div class="server-controls">
<div class="server-status">
<span class="status-dot @(serverAlive ? "alive" : "dead")"></span>
<span>@(serverAlive ? $"Running on port {settings.Port}" : "Not running")</span>
@if (ServerManager.ServerPid != null && serverAlive)
{
<span class="pid-label">PID @ServerManager.ServerPid</span>
}
</div>
<div class="port-input">
<label>Port:</label>
<input type="number" @bind="settings.Port" min="1024" max="65535" />
</div>
<div class="server-buttons">
@if (!serverAlive)
{
<button class="start-btn" @onclick="StartServer" disabled="@starting">
@(starting ? "⏳ Starting..." : "▶️ Start Server")
</button>
}
else
{
<button class="stop-btn" @onclick="StopServer">⏹️ Stop Server</button>
}
</div>
</div>
</div>
}

<div class="settings-section">
<div class="save-row">
<button class="save-btn" @onclick="SaveAndApply">
💾 Save & Reconnect
</button>
@if (!string.IsNullOrEmpty(statusMessage))
{
<span class="save-status @statusClass">@statusMessage</span>
}
</div>
</div>

<div class="settings-section info">
<h3>Current Connection</h3>
<p><strong>Mode:</strong> @CopilotService.CurrentMode</p>
<p><strong>Status:</strong> @(CopilotService.IsInitialized ? "✅ Connected" : "❌ Disconnected")</p>

<h3>About Transport Modes</h3>
<ul>
<li><strong>Embedded</strong> — Simple, default. Copilot process dies when app closes. All sessions are lost.</li>
<li><strong>Persistent</strong> — App spawns a detached Copilot server on a port. Survives app restarts — sessions reconnect after relaunching.</li>
</ul>
</div>
</div>

@code {
private ConnectionSettings settings = new();
private string? statusMessage;
private string statusClass = "";
private bool serverAlive;
private bool starting;

protected override void OnInitialized()
{
settings = ConnectionSettings.Load();
serverAlive = ServerManager.CheckServerRunning("localhost", settings.Port);
}

private void SetMode(ConnectionMode mode)
{
settings.Mode = mode;
}

private async Task StartServer()
{
starting = true;
StateHasChanged();

var success = await ServerManager.StartServerAsync(settings.Port);
serverAlive = success;
starting = false;

if (success)
statusMessage = $"✅ Server started on port {settings.Port}";
else
statusMessage = "❌ Failed to start server";
statusClass = success ? "success" : "error";
StateHasChanged();
}

private void StopServer()
{
ServerManager.StopServer();
serverAlive = false;
statusMessage = "Server stopped";
statusClass = "";
StateHasChanged();
}

private async Task SaveAndApply()
{
// If switching to Persistent mode, ensure server is running
if (settings.Mode == ConnectionMode.Persistent && !serverAlive)
{
statusMessage = "⚠️ Start the persistent server first";
statusClass = "error";
StateHasChanged();
return;
}

settings.Save();
statusMessage = "Settings saved. Reconnecting...";
statusClass = "";
StateHasChanged();

try
{
await CopilotService.ReconnectAsync(settings);
statusMessage = "✅ Connected!";
statusClass = "success";
}
catch (Exception ex)
{
statusMessage = $"❌ Connection failed: {ex.Message}";
statusClass = "error";
}
StateHasChanged();
}
}
Loading