Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,16 @@ actor NetworkInspectorService {
await purgeUnretainedDisconnectedServers()
}

func sendFeatureOpened(feature: String, serverID: SnapOLinkServerID?) async {
let targetID: SnapOLinkServerID? = if let serverID {
serverID
} else {
serverStates.keys.first { serverStates[$0]?.server.isConnected == true }
}
guard let id = targetID, let connection = serverStates[id]?.connection else { return }
connection.sendFeatureOpened(feature: feature)
}

func serversStream() -> AsyncStream<[SnapOLinkServer]> {
let id = UUID()
return AsyncStream { continuation in
Expand Down Expand Up @@ -345,6 +355,7 @@ actor NetworkInspectorService {
state.server.hello = hello
state.server.schemaVersion = hello.schemaVersion
state.server.isSchemaNewerThanSupported = Self.schemaVersionIsNewerThanSupported(hello.schemaVersion)
state.server.features = Set(hello.features.map(\.id))
state.server.wallClockBase = Date(timeIntervalSince1970: 0)
state.server.lastEventAt = now
serverStates[serverID] = state
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,14 @@ struct NetworkInspectorSidebarList: View {

private extension NetworkInspectorSidebarList {
var statusPlaceholder: String {
if selectedServer?.hasHello == true {
guard let server = selectedServer else {
return "Waiting for connection…"
}

if server.hasHello {
return "No activity for this app yet"
}

return "Waiting for connection…"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,12 @@ final class NetworkInspectorStore: ObservableObject {
}
}

func notifyFeatureOpened(feature: String, serverID: SnapOLinkServerID?) {
Task {
await service.sendFeatureOpened(feature: feature, serverID: serverID)
}
}

func requestViewModel(for id: NetworkInspectorRequestID) -> NetworkInspectorRequestViewModel? {
guard let request = requestLookup[id] else { return nil }
let server = serverLookup[request.serverID]
Expand Down Expand Up @@ -264,6 +270,7 @@ struct NetworkInspectorServerViewModel: Identifiable {
let schemaVersion: Int?
let isSchemaNewerThanSupported: Bool
let hasHello: Bool
let features: Set<String>

init(server: SnapOLinkServer) {
id = server.id
Expand Down Expand Up @@ -292,6 +299,7 @@ struct NetworkInspectorServerViewModel: Identifiable {
}
schemaVersion = server.schemaVersion
isSchemaNewerThanSupported = server.isSchemaNewerThanSupported
features = server.features
}
}

Expand Down
84 changes: 79 additions & 5 deletions snapo-app-mac/Snap-O/NetworkInspector/NetworkInspectorView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,17 +84,18 @@ struct NetworkInspectorView: View {
.onChange(of: selectedServerID) { _, newValue in
if let id = newValue {
store.setRetainedServerIDs(Set([id]))
store.notifyFeatureOpened(feature: "network", serverID: id)
} else {
store.setRetainedServerIDs(Set<SnapOLinkServerID>())
}
}
.onChange(of: store.items.map(\.id)) { _, ids in
reconcileSelection(allIDs: ids, filteredIDs: filteredItems.map(\.id))
.onChange(of: allItemIDs) { _, ids in
reconcileSelection(allIDs: ids, filteredIDs: filteredItemIDs)
}
.onChange(of: filteredItems.map(\.id)) { _, filteredIDs in
reconcileSelection(allIDs: store.items.map(\.id), filteredIDs: filteredIDs)
.onChange(of: filteredItemIDs) { _, filteredIDs in
reconcileSelection(allIDs: allItemIDs, filteredIDs: filteredIDs)
}
.onChange(of: store.servers.map(\.id)) { _, ids in
.onChange(of: serverIDs) { _, ids in
if ids.isEmpty {
selectedServerID = nil
isServerPickerPresented = false
Expand All @@ -105,13 +106,20 @@ struct NetworkInspectorView: View {
selectedServerID = ids.first
}
}
.onChange(of: serverConnectionStates) { _, _ in
let target = selectedServerID ?? store.servers.first?.id
if let id = target {
store.notifyFeatureOpened(feature: "network", serverID: id)
}
}
.onAppear {
if selectedServerID == nil {
selectedServerID = store.servers.first?.id
}

if let id = selectedServerID {
store.setRetainedServerIDs(Set([id]))
store.notifyFeatureOpened(feature: "network", serverID: id)
} else {
store.setRetainedServerIDs(Set<SnapOLinkServerID>())
}
Expand Down Expand Up @@ -146,6 +154,27 @@ struct NetworkInspectorView: View {
}

private extension NetworkInspectorView {
var allItemIDs: [NetworkInspectorItemID] {
store.items.map(\.id)
}

var filteredItemIDs: [NetworkInspectorItemID] {
filteredItems.map(\.id)
}

var serverIDs: [SnapOLinkServerID] {
store.servers.map(\.id)
}

var serverConnectionStates: [ServerConnectionState] {
store.servers.map { ServerConnectionState(id: $0.id, isConnected: $0.isConnected) }
}

struct ServerConnectionState: Equatable {
let id: SnapOLinkServerID
let isConnected: Bool
}

@ViewBuilder var detailContent: some View {
if let selection = selectedItem,
let detail = store.detail(for: selection) {
Expand All @@ -155,6 +184,13 @@ private extension NetworkInspectorView {
}
} else if store.servers.isEmpty {
emptyStateView()
} else if let server = selectedServer,
serverScopedItems.isEmpty,
server.hasHello,
!server.features.contains("network") {
networkFeatureDisabledView(for: server)
} else if serverScopedItems.isEmpty {
waitingForActivityView
} else {
placeholderSelectionView
}
Expand Down Expand Up @@ -198,6 +234,44 @@ private extension NetworkInspectorView {
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}

@ViewBuilder
private func networkFeatureDisabledView(for server: NetworkInspectorServerViewModel) -> some View {
VStack(spacing: 16) {
Image(systemName: "exclamationmark.triangle")
.font(.system(size: 44, weight: .regular))
.foregroundStyle(.secondary)

VStack(spacing: 8) {
Text("Network Inspector in \(server.displayName) not found")
.font(.title3.weight(.semibold))
Text("The server is connected, but the network feature is either not installed or not enabled.")
.font(.body)
.multilineTextAlignment(.center)
.foregroundStyle(.secondary)
}

if let documentationURL = URL(string: "https://github.com/openai/snap-o/blob/main/docs/network-inspector.md") {
Link(destination: documentationURL) {
Text("Read the developer guide")
}
.buttonStyle(.link)
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}

@ViewBuilder private var waitingForActivityView: some View {
VStack(spacing: 12) {
Text("No activity for this app yet")
.font(.title2)
.foregroundStyle(.secondary)
Text("Requests will appear here once the app makes network calls.")
.font(.body)
.foregroundStyle(.secondary)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}

private extension NetworkInspectorView {
Expand Down
Loading