Compare commits
1 Commits
main
...
feat/p2-t2
| Author | SHA1 | Date |
|---|---|---|
|
|
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 connection: SessionConnection? = nil
|
||||
@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>()
|
||||
@EnvironmentObject var appState: AppState
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
// ── Status bar ──────────────────────────────────────────
|
||||
HStack {
|
||||
Text(statusText)
|
||||
.font(.caption.monospaced())
|
||||
.foregroundStyle(.secondary)
|
||||
Spacer()
|
||||
Button("Unpair") {
|
||||
appState.unpair()
|
||||
}
|
||||
.font(.caption)
|
||||
.foregroundStyle(.red)
|
||||
}
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, 6)
|
||||
.background(Color(uiColor: .systemBackground))
|
||||
|
||||
Divider()
|
||||
StatusBar(
|
||||
sessionName: sessionName,
|
||||
connectionStatus: statusText,
|
||||
piState: $currentPiState,
|
||||
onSwitcher: { showSwitcher = true },
|
||||
onUnpair: { appState.unpair() }
|
||||
)
|
||||
|
||||
// ── Terminal ────────────────────────────────────────────
|
||||
TerminalViewRepresentable(controller: terminalVC)
|
||||
|
|
@ -129,6 +123,24 @@ struct MainTerminalView: View {
|
|||
}
|
||||
.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
|
||||
|
||||
let lastSeq: UInt64? = conn.scrollback.sizeBytes > 0
|
||||
|
|
|
|||
Loading…
Reference in New Issue