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>
98 lines
3.8 KiB
Swift
98 lines
3.8 KiB
Swift
import SwiftUI
|
|
|
|
/// Displays a persisted tool call result from a ToolCallData record.
|
|
/// Collapsible card with monospaced JSON input/output.
|
|
struct JSONToolResultView: View {
|
|
let toolCallData: ToolCallData
|
|
@State private var isExpanded: Bool = false
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 0) {
|
|
// Header
|
|
Button {
|
|
withAnimation(.easeInOut(duration: 0.2)) {
|
|
isExpanded.toggle()
|
|
}
|
|
} label: {
|
|
HStack(spacing: 8) {
|
|
Image(systemName: isError ? "xmark.circle.fill" : "checkmark.circle.fill")
|
|
.foregroundStyle(isError ? .red : .green)
|
|
.font(.caption)
|
|
|
|
Text(toolCallData.toolName)
|
|
.font(.system(.caption, design: .monospaced).bold())
|
|
.foregroundStyle(.primary)
|
|
|
|
Spacer()
|
|
|
|
Image(systemName: "chevron.right")
|
|
.font(.caption2)
|
|
.foregroundStyle(.tertiary)
|
|
.rotationEffect(.degrees(isExpanded ? 90 : 0))
|
|
}
|
|
.padding(.horizontal, 10)
|
|
.padding(.vertical, 8)
|
|
}
|
|
.buttonStyle(.plain)
|
|
|
|
if isExpanded {
|
|
Divider()
|
|
.padding(.horizontal, 10)
|
|
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
// Input
|
|
if let args = toolCallData.args {
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
Text("INPUT")
|
|
.font(.system(size: 9, design: .monospaced))
|
|
.foregroundStyle(.tertiary)
|
|
|
|
Text(args.prettyString)
|
|
.font(.system(size: 11, design: .monospaced))
|
|
.foregroundStyle(.secondary)
|
|
.textSelection(.enabled)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.padding(6)
|
|
.background(.black.opacity(0.15))
|
|
.clipShape(RoundedRectangle(cornerRadius: 4))
|
|
}
|
|
}
|
|
|
|
// Output
|
|
if let output = toolCallData.output {
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
Text("OUTPUT")
|
|
.font(.system(size: 9, design: .monospaced))
|
|
.foregroundStyle(.tertiary)
|
|
|
|
Text(output.prettyString)
|
|
.font(.system(size: 11, design: .monospaced))
|
|
.foregroundStyle(.secondary)
|
|
.textSelection(.enabled)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.padding(6)
|
|
.background(.black.opacity(0.15))
|
|
.clipShape(RoundedRectangle(cornerRadius: 4))
|
|
.lineLimit(20)
|
|
}
|
|
}
|
|
}
|
|
.padding(10)
|
|
}
|
|
}
|
|
.background(.quaternary.opacity(0.3))
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: 8)
|
|
.strokeBorder(isError ? .red.opacity(0.2) : .green.opacity(0.15), lineWidth: 1)
|
|
)
|
|
.clipShape(RoundedRectangle(cornerRadius: 8))
|
|
}
|
|
|
|
private var isError: Bool {
|
|
if case .object(let obj) = toolCallData.output,
|
|
case .bool(true) = obj["error"] {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
}
|