feat: T-2.8 StatusBar component + pi state display
This commit is contained in:
parent
fb56c11a29
commit
b824355cfd
|
|
@ -0,0 +1,99 @@
|
||||||
|
// StatusBar.swift
|
||||||
|
// T-2.8 — Session status bar with pi-state indicator and action buttons.
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
struct StatusBar: View {
|
||||||
|
let sessionName: String
|
||||||
|
let connectionStatus: String // "Connecting…", "Connected", "Disconnected"
|
||||||
|
@Binding var piState: PiState?
|
||||||
|
var onSwitcher: (() -> Void)? = nil
|
||||||
|
var onUnpair: (() -> Void)? = nil
|
||||||
|
var onSettings: (() -> Void)? = nil
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
HStack {
|
||||||
|
// ── Left: session name ──────────────────────────────
|
||||||
|
Text(sessionName.isEmpty ? " " : sessionName)
|
||||||
|
.font(.caption.monospaced())
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.lineLimit(1)
|
||||||
|
.truncationMode(.middle)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
// ── Center: pi state / connection status ────────────
|
||||||
|
stateIndicator
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
// ── Right: icon buttons ─────────────────────────────
|
||||||
|
HStack(spacing: 14) {
|
||||||
|
if onSwitcher != nil {
|
||||||
|
Button {
|
||||||
|
onSwitcher?()
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "list.bullet")
|
||||||
|
.font(.caption)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if onSettings != nil {
|
||||||
|
Button {
|
||||||
|
onSettings?()
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "gear")
|
||||||
|
.font(.caption)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if onUnpair != nil {
|
||||||
|
Button {
|
||||||
|
onUnpair?()
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "x.circle")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.red)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 12)
|
||||||
|
.padding(.vertical, 6)
|
||||||
|
.background(Color(uiColor: .systemBackground))
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - State indicator
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var stateIndicator: some View {
|
||||||
|
if let state = piState {
|
||||||
|
switch state {
|
||||||
|
case .thinking:
|
||||||
|
Text("● thinking")
|
||||||
|
.font(.caption.monospaced())
|
||||||
|
.foregroundStyle(.orange)
|
||||||
|
case .tool:
|
||||||
|
Text("▶ tool")
|
||||||
|
.font(.caption.monospaced())
|
||||||
|
.foregroundStyle(.blue)
|
||||||
|
case .awaitingInput:
|
||||||
|
Text("⏸ awaiting")
|
||||||
|
.font(.caption.monospaced())
|
||||||
|
.foregroundStyle(.yellow)
|
||||||
|
case .idle:
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Text(connectionStatus)
|
||||||
|
.font(.caption.monospaced())
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,28 +12,22 @@ struct MainTerminalView: View {
|
||||||
@State private var terminalVC = TerminalViewController()
|
@State private var terminalVC = TerminalViewController()
|
||||||
@State private var connection: SessionConnection? = nil
|
@State private var connection: SessionConnection? = nil
|
||||||
@State private var statusText = "Connecting…"
|
@State private var statusText = "Connecting…"
|
||||||
|
@State private var currentPiState: PiState? = nil
|
||||||
|
@State private var sessionName = ""
|
||||||
|
@State private var showSwitcher = false
|
||||||
@State private var cancellables = Set<AnyCancellable>()
|
@State private var cancellables = Set<AnyCancellable>()
|
||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var appState: AppState
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
// ── Status bar ──────────────────────────────────────────
|
// ── Status bar ──────────────────────────────────────────
|
||||||
HStack {
|
StatusBar(
|
||||||
Text(statusText)
|
sessionName: sessionName,
|
||||||
.font(.caption.monospaced())
|
connectionStatus: statusText,
|
||||||
.foregroundStyle(.secondary)
|
piState: $currentPiState,
|
||||||
Spacer()
|
onSwitcher: { showSwitcher = true },
|
||||||
Button("Unpair") {
|
onUnpair: { appState.unpair() }
|
||||||
appState.unpair()
|
)
|
||||||
}
|
|
||||||
.font(.caption)
|
|
||||||
.foregroundStyle(.red)
|
|
||||||
}
|
|
||||||
.padding(.horizontal, 12)
|
|
||||||
.padding(.vertical, 6)
|
|
||||||
.background(Color(uiColor: .systemBackground))
|
|
||||||
|
|
||||||
Divider()
|
|
||||||
|
|
||||||
// ── Terminal ────────────────────────────────────────────
|
// ── Terminal ────────────────────────────────────────────
|
||||||
TerminalViewRepresentable(controller: terminalVC)
|
TerminalViewRepresentable(controller: terminalVC)
|
||||||
|
|
@ -129,6 +123,24 @@ struct MainTerminalView: View {
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
// Wire stateEvents → currentPiState
|
||||||
|
conn.stateEvents
|
||||||
|
.compactMap { event -> PiState? in
|
||||||
|
if case .state(let s, _, _) = event { return s } else { return nil }
|
||||||
|
}
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { state in currentPiState = state }
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
// Wire stateEvents → sessionName
|
||||||
|
conn.stateEvents
|
||||||
|
.compactMap { event -> String? in
|
||||||
|
if case .sessionMeta(let name, _, _) = event { return name } else { return nil }
|
||||||
|
}
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { name in sessionName = name }
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
connection = conn
|
connection = conn
|
||||||
|
|
||||||
let lastSeq: UInt64? = conn.scrollback.sizeBytes > 0
|
let lastSeq: UInt64? = conn.scrollback.sizeBytes > 0
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue