Add session filter to Activity view

Dropdown in the filter bar lets users scope activity to a single
session or view all. Sessions are labeled with their first user
message preview. Combines with the existing tool-kind filter.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alan Wizemann
2026-03-31 03:03:28 -04:00
parent 0a73aab825
commit 0d38856b3e
2 changed files with 42 additions and 13 deletions
@@ -6,9 +6,20 @@ final class ActivityViewModel {
var toolMessages: [HermesMessage] = []
var filterKind: ToolKind?
var filterSessionId: String?
var selectedEntry: ActivityEntry?
var sessionPreviews: [String: String] = [:]
var isLoading = true
var availableSessions: [(id: String, label: String)] {
var seen = Set<String>()
return toolMessages.compactMap { message in
guard seen.insert(message.sessionId).inserted else { return nil }
let label = sessionPreviews[message.sessionId] ?? message.sessionId
return (id: message.sessionId, label: label)
}
}
var filteredActivity: [ActivityEntry] {
let entries = toolMessages.flatMap { message in
message.toolCalls.map { call in
@@ -24,10 +35,11 @@ final class ActivityViewModel {
)
}
}
if let filterKind {
return entries.filter { $0.kind == filterKind }
return entries.filter { entry in
let kindOk = filterKind == nil || entry.kind == filterKind
let sessionOk = filterSessionId == nil || entry.sessionId == filterSessionId
return kindOk && sessionOk
}
return entries
}
func load() async {
@@ -38,6 +50,7 @@ final class ActivityViewModel {
return
}
toolMessages = await dataService.fetchRecentToolCalls(limit: 200)
sessionPreviews = await dataService.fetchSessionPreviews(limit: 200)
isLoading = false
}
@@ -21,20 +21,36 @@ struct ActivityView: View {
}
private var filterBar: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 8) {
FilterChip(label: "All", isSelected: viewModel.filterKind == nil) {
viewModel.filterKind = nil
}
ForEach(ToolKind.allCases, id: \.rawValue) { kind in
FilterChip(label: kind.rawValue.capitalized, isSelected: viewModel.filterKind == kind) {
viewModel.filterKind = kind
HStack(spacing: 12) {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 8) {
FilterChip(label: "All", isSelected: viewModel.filterKind == nil) {
viewModel.filterKind = nil
}
ForEach(ToolKind.allCases, id: \.rawValue) { kind in
FilterChip(label: kind.rawValue.capitalized, isSelected: viewModel.filterKind == kind) {
viewModel.filterKind = kind
}
}
}
}
.padding(.horizontal)
.padding(.vertical, 8)
Divider()
.frame(height: 16)
Picker(selection: $viewModel.filterSessionId) {
Text("All Sessions").tag(String?.none)
Divider()
ForEach(viewModel.availableSessions, id: \.id) { session in
Text(session.label)
.lineLimit(1)
.tag(String?.some(session.id))
}
} label: {
EmptyView()
}
.frame(maxWidth: 250)
}
.padding(.horizontal)
.padding(.vertical, 8)
}
private var activityList: some View {