mirror of
https://github.com/wu736139669/hapi.git
synced 2026-10-08 19:19:42 +00:00
87 lines
3.7 KiB
Swift
87 lines
3.7 KiB
Swift
import HapiProtocol
|
|
import HapiUI
|
|
import SwiftUI
|
|
|
|
/// The list stays mounted behind its detail destination. Neither live data nor
|
|
/// returning from a detail is a scroll command; only opening and Latest are.
|
|
struct ToolGroupBrowser: View {
|
|
let group: ToolGroupBlock
|
|
let basePath: String?
|
|
let isStale: Bool
|
|
let isReconnecting: Bool
|
|
let selectTool: (String) -> Void
|
|
let close: () -> Void
|
|
@State private var positionedInitially = false
|
|
@Environment(\.hapiTheme) private var theme
|
|
|
|
var body: some View {
|
|
ScrollViewReader { proxy in
|
|
List {
|
|
ForEach(group.tools, id: \.id) { block in
|
|
ToolSummaryRow(presentation: toolSummaryPresentation(block.tool, basePath: basePath),
|
|
state: block.tool.state) {
|
|
selectTool(block.id)
|
|
}
|
|
.accessibilityHint(opensToolProcess(block)
|
|
? String(localized: "View agent process") : String(localized: "View tool details"))
|
|
.accessibilityIdentifier("tool-browser-row-\(block.id)")
|
|
.hapiReadingColumn()
|
|
.listRowInsets(EdgeInsets())
|
|
.listRowBackground(theme.surface)
|
|
.listRowSeparatorTint(theme.divider)
|
|
.id(block.id)
|
|
}
|
|
}
|
|
.listStyle(.plain)
|
|
.scrollContentBackground(.hidden)
|
|
.background(theme.background)
|
|
.accessibilityIdentifier("tool-browser")
|
|
.task {
|
|
guard !positionedInitially, let id = group.tools.last?.id else { return }
|
|
positionedInitially = true
|
|
proxy.scrollTo(id, anchor: .bottom)
|
|
}
|
|
.safeAreaInset(edge: .top, spacing: 0) {
|
|
VStack(alignment: .leading, spacing: 6) {
|
|
Text(ToolGroupPresentation(group).categorySummary)
|
|
.lineLimit(2)
|
|
.foregroundStyle(theme.textSecondary)
|
|
if isStale {
|
|
Label("Showing the last available group. Live group updates are unavailable.", systemImage: "clock.arrow.circlepath")
|
|
} else if isReconnecting {
|
|
Label("Live updates interrupted — reconnecting…", systemImage: "wifi.slash")
|
|
}
|
|
if group.needsOlderHistory {
|
|
Text("Earlier calls aren't loaded. Close this view and load older messages in the conversation.")
|
|
}
|
|
}
|
|
.font(.footnote)
|
|
.hapiReadingColumn()
|
|
.padding(.vertical, 8)
|
|
.background(.bar)
|
|
}
|
|
.safeAreaInset(edge: .bottom, spacing: 0) {
|
|
Button {
|
|
if let id = group.tools.last?.id { proxy.scrollTo(id, anchor: .bottom) }
|
|
} label: {
|
|
Label("Latest tool", systemImage: "arrow.down")
|
|
.frame(maxWidth: .infinity, minHeight: 44)
|
|
}
|
|
.disabled(group.tools.isEmpty)
|
|
.accessibilityIdentifier("tool-browser-latest")
|
|
.hapiReadingColumn()
|
|
.padding(.vertical, 4)
|
|
.background(.bar)
|
|
}
|
|
}
|
|
.navigationTitle(ToolGroupPresentation(group).title)
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbar {
|
|
ToolbarItem(placement: .cancellationAction) {
|
|
Button("Close", systemImage: "xmark", action: close)
|
|
.accessibilityIdentifier("tool-inspector-close")
|
|
}
|
|
}
|
|
}
|
|
}
|