173 lines
4.8 KiB
Swift
173 lines
4.8 KiB
Swift
//
|
|
// FooterView.swift
|
|
// oAI
|
|
//
|
|
// Footer bar with session summary
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
struct FooterView: View {
|
|
let stats: SessionStats
|
|
|
|
var body: some View {
|
|
HStack(spacing: 20) {
|
|
// Session summary
|
|
HStack(spacing: 16) {
|
|
FooterItem(
|
|
icon: "message",
|
|
label: "Messages",
|
|
value: "\(stats.messageCount)"
|
|
)
|
|
|
|
FooterItem(
|
|
icon: "chart.bar.xaxis",
|
|
label: "Tokens",
|
|
value: "\(stats.totalTokens) (\(stats.totalInputTokens) in, \(stats.totalOutputTokens) out)"
|
|
)
|
|
|
|
FooterItem(
|
|
icon: "dollarsign.circle",
|
|
label: "Cost",
|
|
value: stats.totalCostDisplay
|
|
)
|
|
|
|
// Git sync status (if enabled)
|
|
if SettingsService.shared.syncEnabled && SettingsService.shared.syncAutoSave {
|
|
SyncStatusFooter()
|
|
}
|
|
}
|
|
|
|
Spacer()
|
|
|
|
// Shortcuts hint
|
|
#if os(macOS)
|
|
Text("⌘M Model • ⌘K Clear • ⌘S Stats")
|
|
.font(.caption2)
|
|
.foregroundColor(.oaiSecondary)
|
|
#endif
|
|
}
|
|
.padding(.horizontal, 16)
|
|
.padding(.vertical, 8)
|
|
.background(.ultraThinMaterial)
|
|
.overlay(
|
|
Rectangle()
|
|
.fill(Color.oaiBorder.opacity(0.5))
|
|
.frame(height: 1),
|
|
alignment: .top
|
|
)
|
|
}
|
|
}
|
|
|
|
struct FooterItem: View {
|
|
let icon: String
|
|
let label: String
|
|
let value: String
|
|
private let guiSize = SettingsService.shared.guiTextSize
|
|
|
|
var body: some View {
|
|
HStack(spacing: 6) {
|
|
Image(systemName: icon)
|
|
.font(.system(size: guiSize - 2))
|
|
.foregroundColor(.oaiSecondary)
|
|
|
|
Text(label + ":")
|
|
.font(.system(size: guiSize - 2))
|
|
.foregroundColor(.oaiSecondary)
|
|
|
|
Text(value)
|
|
.font(.system(size: guiSize - 2, weight: .medium))
|
|
.foregroundColor(.oaiPrimary)
|
|
}
|
|
}
|
|
}
|
|
|
|
struct SyncStatusFooter: View {
|
|
private let gitSync = GitSyncService.shared
|
|
private let settings = SettingsService.shared
|
|
private let guiSize = SettingsService.shared.guiTextSize
|
|
@State private var syncText = "Not Synced"
|
|
@State private var syncColor: Color = .secondary
|
|
|
|
var body: some View {
|
|
HStack(spacing: 6) {
|
|
Image(systemName: "arrow.triangle.2.circlepath")
|
|
.font(.system(size: guiSize - 2))
|
|
.foregroundColor(syncColor)
|
|
|
|
Text(syncText)
|
|
.font(.system(size: guiSize - 2, weight: .medium))
|
|
.foregroundColor(syncColor)
|
|
}
|
|
.onAppear {
|
|
updateSyncStatus()
|
|
}
|
|
.onChange(of: gitSync.syncStatus.lastSyncTime) {
|
|
updateSyncStatus()
|
|
}
|
|
.onChange(of: gitSync.syncStatus.isCloned) {
|
|
updateSyncStatus()
|
|
}
|
|
.onChange(of: gitSync.lastSyncError) {
|
|
updateSyncStatus()
|
|
}
|
|
.onChange(of: gitSync.isSyncing) {
|
|
updateSyncStatus()
|
|
}
|
|
.onChange(of: settings.syncConfigured) {
|
|
updateSyncStatus()
|
|
}
|
|
}
|
|
|
|
private func updateSyncStatus() {
|
|
if let error = gitSync.lastSyncError {
|
|
syncText = "Sync Error"
|
|
syncColor = .red
|
|
} else if gitSync.isSyncing {
|
|
syncText = "Syncing..."
|
|
syncColor = .orange
|
|
} else if let lastSync = gitSync.syncStatus.lastSyncTime {
|
|
syncText = "Last Sync: \(timeAgo(lastSync))"
|
|
syncColor = .green
|
|
} else if gitSync.syncStatus.isCloned {
|
|
syncText = "Sync: Ready"
|
|
syncColor = .secondary
|
|
} else if settings.syncConfigured {
|
|
syncText = "Sync: Not Initialized"
|
|
syncColor = .orange
|
|
} else {
|
|
syncText = "Sync: Off"
|
|
syncColor = .secondary
|
|
}
|
|
}
|
|
|
|
private func timeAgo(_ date: Date) -> String {
|
|
let seconds = Int(Date().timeIntervalSince(date))
|
|
if seconds < 60 {
|
|
return "just now"
|
|
} else if seconds < 3600 {
|
|
let minutes = seconds / 60
|
|
return "\(minutes)m ago"
|
|
} else if seconds < 86400 {
|
|
let hours = seconds / 3600
|
|
return "\(hours)h ago"
|
|
} else {
|
|
let days = seconds / 86400
|
|
return "\(days)d ago"
|
|
}
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
VStack {
|
|
Spacer()
|
|
FooterView(stats: SessionStats(
|
|
totalInputTokens: 1250,
|
|
totalOutputTokens: 3420,
|
|
totalCost: 0.0152,
|
|
messageCount: 12
|
|
))
|
|
}
|
|
.background(Color.oaiBackground)
|
|
}
|