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 toolMessages: [HermesMessage] = []
var filterKind: ToolKind? var filterKind: ToolKind?
var filterSessionId: String?
var selectedEntry: ActivityEntry? var selectedEntry: ActivityEntry?
var sessionPreviews: [String: String] = [:]
var isLoading = true 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] { var filteredActivity: [ActivityEntry] {
let entries = toolMessages.flatMap { message in let entries = toolMessages.flatMap { message in
message.toolCalls.map { call in message.toolCalls.map { call in
@@ -24,10 +35,11 @@ final class ActivityViewModel {
) )
} }
} }
if let filterKind { return entries.filter { entry in
return entries.filter { $0.kind == filterKind } let kindOk = filterKind == nil || entry.kind == filterKind
let sessionOk = filterSessionId == nil || entry.sessionId == filterSessionId
return kindOk && sessionOk
} }
return entries
} }
func load() async { func load() async {
@@ -38,6 +50,7 @@ final class ActivityViewModel {
return return
} }
toolMessages = await dataService.fetchRecentToolCalls(limit: 200) toolMessages = await dataService.fetchRecentToolCalls(limit: 200)
sessionPreviews = await dataService.fetchSessionPreviews(limit: 200)
isLoading = false isLoading = false
} }
@@ -21,6 +21,7 @@ struct ActivityView: View {
} }
private var filterBar: some View { private var filterBar: some View {
HStack(spacing: 12) {
ScrollView(.horizontal, showsIndicators: false) { ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 8) { HStack(spacing: 8) {
FilterChip(label: "All", isSelected: viewModel.filterKind == nil) { FilterChip(label: "All", isSelected: viewModel.filterKind == nil) {
@@ -32,10 +33,25 @@ struct ActivityView: View {
} }
} }
} }
}
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(.horizontal)
.padding(.vertical, 8) .padding(.vertical, 8)
} }
}
private var activityList: some View { private var activityList: some View {
List(selection: Binding( List(selection: Binding(