gui/ui/src/components/status-bar.component.ts
Claude 8d3c0fb6d2
Some checks are pending
Security Scan / security (push) Waiting to run
Test / test (push) Waiting to run
feat(gui): implement chat-first UI and display primitives
- Replace provider dashboard with full chat UI (history, model selection, image attachments)
- Add chat settings/history/image queue/tool-call metadata persistence
- Add core://settings and core://store route handling in display package
- Add progressive assistant rendering, collapsible thinking/tool-call blocks
- Add markdown/code rendering with copy actions and lightbox image preview
- Add app mode detection (pkg/display/mode.go)
- Add chat backend coverage (pkg/display/chat_test.go)
- Add chat.service.ts frontend service
- AX sweep across pkg/mcp tools and pkg/window/webview/systray/notification

Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-14 14:36:01 +01:00

122 lines
3.2 KiB
TypeScript

import { Component, Input, OnDestroy, OnInit, computed, inject, signal } from '@angular/core';
import { ChatService } from '../services/chat.service';
@Component({
selector: 'status-bar',
standalone: true,
template: `
<footer class="status-bar" [style.--sidebar-width]="sidebarWidth">
<div class="status-left">
<span class="status-item version">{{ version }}</span>
<span class="status-item">
<i class="fa-regular fa-comments"></i>
{{ conversationCount() }} conversations
</span>
<span class="status-item">
<i class="fa-regular fa-microchip-ai"></i>
{{ activeModel() }}
</span>
</div>
<div class="status-right">
<span class="status-item connection" [class.connected]="!chat.busy()">
<span class="status-dot"></span>
{{ chat.busy() ? 'Streaming' : 'Ready' }}
</span>
<span class="status-item time">{{ time() }}</span>
</div>
</footer>
`,
styles: [
`
.status-bar {
position: fixed;
left: 0;
width: 100%;
bottom: 0;
z-index: 40;
height: 2.75rem;
border-top: 1px solid rgba(255, 255, 255, 0.06);
background: linear-gradient(180deg, rgba(6, 10, 18, 0.88), rgba(6, 10, 18, 0.96));
backdrop-filter: blur(18px);
display: flex;
align-items: center;
justify-content: space-between;
padding-inline: 1rem 1.25rem;
box-shadow: 0 -12px 40px rgba(0, 0, 0, 0.2);
}
@media (min-width: 1024px) {
.status-bar {
left: var(--sidebar-width, 0);
width: calc(100% - var(--sidebar-width, 0));
}
}
.status-left,
.status-right {
display: flex;
align-items: center;
gap: 1rem;
}
.status-item {
font-size: 0.875rem;
color: rgb(168 179 207);
}
.status-item i {
margin-right: 0.25rem;
}
.version {
letter-spacing: 0.08em;
text-transform: uppercase;
}
.status-dot {
display: inline-block;
width: 7px;
height: 7px;
border-radius: 50%;
background: rgb(249 115 22);
margin-right: 0.375rem;
}
.connection.connected .status-dot {
background: rgb(20 184 166);
box-shadow: 0 0 8px rgba(20, 184, 166, 0.4);
}
.time {
font-family: 'JetBrains Mono', 'Fira Code', monospace;
color: rgb(244 247 251);
}
`,
],
})
export class StatusBarComponent implements OnInit, OnDestroy {
@Input() version = 'v0.1.0';
@Input() sidebarWidth = '5rem';
protected readonly chat = inject(ChatService);
protected readonly time = signal('');
protected readonly conversationCount = computed(() => this.chat.conversations().length);
protected readonly activeModel = computed(() => this.chat.selectedModel());
private intervalId: ReturnType<typeof setInterval> | undefined;
ngOnInit(): void {
this.updateTime();
this.intervalId = setInterval(() => this.updateTime(), 1000);
}
ngOnDestroy(): void {
if (this.intervalId) {
clearInterval(this.intervalId);
}
}
private updateTime(): void {
this.time.set(new Date().toLocaleTimeString());
}
}