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>
68 lines
2.1 KiB
Swift
68 lines
2.1 KiB
Swift
import SwiftData
|
|
import SwiftUI
|
|
|
|
struct ChatView: View {
|
|
@Bindable var conversation: Conversation
|
|
var orchestrator: ChatOrchestrator
|
|
@Environment(\.modelContext) private var modelContext
|
|
|
|
@State private var inputText: String = ""
|
|
|
|
var body: some View {
|
|
VStack(spacing: 0) {
|
|
// Messages
|
|
MessageList(
|
|
conversation: conversation,
|
|
streamingContent: orchestrator.streamingContent,
|
|
isStreaming: orchestrator.isStreaming,
|
|
liveToolCalls: orchestrator.liveToolCalls
|
|
)
|
|
|
|
// Error banner
|
|
if let error = orchestrator.error {
|
|
HStack {
|
|
Image(systemName: "exclamationmark.triangle.fill")
|
|
.foregroundStyle(.red)
|
|
Text(error)
|
|
.font(.callout)
|
|
.foregroundStyle(.red)
|
|
Spacer()
|
|
Button("Dismiss") {
|
|
orchestrator.error = nil
|
|
}
|
|
.buttonStyle(.borderless)
|
|
.font(.callout)
|
|
}
|
|
.padding(.horizontal, 16)
|
|
.padding(.vertical, 8)
|
|
.background(.red.opacity(0.1))
|
|
}
|
|
|
|
// Input bar
|
|
ChatInputBar(
|
|
text: $inputText,
|
|
isStreaming: orchestrator.isStreaming,
|
|
onSend: sendMessage
|
|
)
|
|
}
|
|
.navigationTitle(conversation.displayTitle)
|
|
.toolbar {
|
|
ToolbarItem(placement: .automatic) {
|
|
if orchestrator.isStreaming {
|
|
ProgressView()
|
|
.controlSize(.small)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func sendMessage() {
|
|
let text = inputText.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
guard !text.isEmpty else { return }
|
|
inputText = ""
|
|
|
|
Task {
|
|
await orchestrator.sendMessage(text, conversation: conversation, modelContext: modelContext)
|
|
}
|
|
}
|
|
}
|