Native macOS counterpart to the web Omega agent, connecting directly to the OpenAI API and TPMJS tool registry (1M+ AI-ready tools). - SwiftUI app targeting macOS 15+ with dark theme - Full agentic loop: auto-discover tools via BM25, stream OpenAI responses, execute tools via remote sandbox, loop up to 10x - SwiftData persistence for conversations, messages, tool runs - Keychain storage for API keys and environment variables - SSE streaming via URLSession.bytes with custom parser - Actor-based services (OpenAIService, TPMJSRegistryService) - NavigationSplitView layout with sidebar + chat detail - MarkdownUI for rendering assistant responses - Settings: API key, model picker, env vars, custom system prompt - Keyboard shortcuts: Cmd+N new chat, Cmd+, settings, Enter send Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
52 lines
1.6 KiB
Swift
52 lines
1.6 KiB
Swift
import SwiftData
|
|
import SwiftUI
|
|
|
|
struct SidebarView: View {
|
|
@Binding var selectedConversation: Conversation?
|
|
let onNewConversation: () -> Void
|
|
|
|
@Query(sort: \Conversation.updatedAt, order: .reverse)
|
|
private var conversations: [Conversation]
|
|
|
|
@Environment(\.modelContext) private var modelContext
|
|
|
|
var body: some View {
|
|
List(selection: $selectedConversation) {
|
|
ForEach(conversations) { conversation in
|
|
ConversationRow(conversation: conversation)
|
|
.tag(conversation)
|
|
.contextMenu {
|
|
Button("Delete", role: .destructive) {
|
|
deleteConversation(conversation)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.listStyle(.sidebar)
|
|
.toolbar {
|
|
ToolbarItem(placement: .automatic) {
|
|
Button(action: onNewConversation) {
|
|
Image(systemName: "plus")
|
|
}
|
|
.help("New Conversation (Cmd+N)")
|
|
}
|
|
}
|
|
.overlay {
|
|
if conversations.isEmpty {
|
|
ContentUnavailableView {
|
|
Label("No Conversations", systemImage: "bubble.left.and.bubble.right")
|
|
} description: {
|
|
Text("Press Cmd+N to start a new conversation")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func deleteConversation(_ conversation: Conversation) {
|
|
if selectedConversation == conversation {
|
|
selectedConversation = nil
|
|
}
|
|
modelContext.delete(conversation)
|
|
try? modelContext.save()
|
|
}
|
|
}
|