c118996746
- SwiftUI macOS app with C++17 code analysis engine (ObjC++ bridge) - Agentic AI loop: LLM plans → tool calls → execution → feedback loop - 15 agent tools: file ops, terminal, git, xcode build, code intel - 7 persistent terminal tools with background session management - Chat sidebar with agent step rendering and auto-apply - NVIDIA NIM API integration (Llama 3.3 70B default) - OpenAI tool_calls format with prompt-based fallback - Code editor with syntax highlighting and multi-tab support - File tree, console view, terminal view - Git integration and workspace management
166 lines
5.9 KiB
Swift
166 lines
5.9 KiB
Swift
// AgentSidebarView.swift
|
|
// CxIDE — Agent sidebar panel with quick actions, tool browser, and status.
|
|
|
|
import SwiftUI
|
|
|
|
struct AgentSidebarView: View {
|
|
@ObservedObject var viewModel: EditorViewModel
|
|
|
|
private var agent: AgentService { viewModel.agentService }
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 0) {
|
|
// Header
|
|
HStack {
|
|
Image(systemName: "cpu")
|
|
.foregroundColor(.purple)
|
|
Text("CxSwiftAgent")
|
|
.font(.system(size: 11, weight: .semibold))
|
|
Spacer()
|
|
Circle()
|
|
.fill(agent.status.color)
|
|
.frame(width: 8, height: 8)
|
|
}
|
|
.padding(.horizontal, 12)
|
|
.padding(.vertical, 8)
|
|
|
|
Divider()
|
|
|
|
ScrollView {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
statusSection
|
|
quickActionsSection
|
|
toolBrowserSection
|
|
}
|
|
.padding(10)
|
|
}
|
|
}
|
|
.background(VSC.sidebarBg)
|
|
}
|
|
|
|
// MARK: - Status
|
|
|
|
private var statusSection: some View {
|
|
VStack(alignment: .leading, spacing: 6) {
|
|
Label {
|
|
Text(agent.status.displayText)
|
|
.font(.system(size: 10))
|
|
.foregroundColor(agent.status.color)
|
|
} icon: {
|
|
Image(systemName: agent.status.iconName)
|
|
.font(.system(size: 10))
|
|
.foregroundColor(agent.status.color)
|
|
}
|
|
|
|
HStack(spacing: 12) {
|
|
Label("\(agent.toolCount) tools", systemImage: "wrench.fill")
|
|
Label("\(agent.messageCount) msgs", systemImage: "bubble.left.fill")
|
|
}
|
|
.font(.system(size: 9))
|
|
.foregroundColor(.secondary)
|
|
|
|
if agent.sandboxMode {
|
|
Label("Sandbox Mode", systemImage: "lock.shield.fill")
|
|
.font(.system(size: 9))
|
|
.foregroundColor(.orange)
|
|
}
|
|
}
|
|
.padding(8)
|
|
.background(VSC.inputBg)
|
|
.cornerRadius(6)
|
|
}
|
|
|
|
// MARK: - Quick Actions
|
|
|
|
private var quickActionsSection: some View {
|
|
VStack(alignment: .leading, spacing: 6) {
|
|
Text("QUICK ACTIONS")
|
|
.font(.system(size: 9, weight: .bold))
|
|
.foregroundColor(.secondary)
|
|
|
|
LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 6) {
|
|
quickActionButton("Explain", icon: "text.bubble", action: viewModel.agentExplainCode)
|
|
quickActionButton("Review", icon: "eye", action: viewModel.agentReviewCode)
|
|
quickActionButton("Fix", icon: "wrench", action: viewModel.agentFixCode)
|
|
quickActionButton("Tests", icon: "checkmark.seal", action: viewModel.agentGenerateTests)
|
|
quickActionButton("Diagnose", icon: "stethoscope", action: viewModel.agentRunDiagnostics)
|
|
quickActionButton("Security", icon: "lock.shield", action: viewModel.agentSecurityAudit)
|
|
quickActionButton("Workspace", icon: "folder.badge.gearshape", action: viewModel.agentAnalyzeWorkspace)
|
|
quickActionButton("All Tools", icon: "list.bullet", action: viewModel.agentListTools)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func quickActionButton(_ title: String, icon: String, action: @escaping () -> Void) -> some View {
|
|
Button(action: action) {
|
|
VStack(spacing: 3) {
|
|
Image(systemName: icon)
|
|
.font(.system(size: 13))
|
|
Text(title)
|
|
.font(.system(size: 9))
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
.padding(.vertical, 8)
|
|
.background(Color.purple.opacity(0.15))
|
|
.cornerRadius(6)
|
|
}
|
|
.buttonStyle(.plain)
|
|
.disabled(agent.status != .idle)
|
|
}
|
|
|
|
// MARK: - Tool Browser
|
|
|
|
private var toolBrowserSection: some View {
|
|
VStack(alignment: .leading, spacing: 6) {
|
|
Text("TOOLS (\(agent.toolCount))")
|
|
.font(.system(size: 9, weight: .bold))
|
|
.foregroundColor(.secondary)
|
|
|
|
let grouped = Dictionary(grouping: agent.availableTools) { $0.category }
|
|
|
|
ForEach(grouped.keys.sorted(), id: \.self) { category in
|
|
DisclosureGroup {
|
|
ForEach(grouped[category] ?? []) { tool in
|
|
toolRow(tool)
|
|
}
|
|
} label: {
|
|
HStack {
|
|
Text(category)
|
|
.font(.system(size: 10, weight: .medium))
|
|
Spacer()
|
|
Text("\(grouped[category]?.count ?? 0)")
|
|
.font(.system(size: 9))
|
|
.foregroundColor(.secondary)
|
|
}
|
|
}
|
|
.font(.system(size: 10))
|
|
}
|
|
}
|
|
}
|
|
|
|
private func toolRow(_ tool: AgentToolInfo) -> some View {
|
|
Button {
|
|
viewModel.bottomPanel = .agent
|
|
Task { _ = await agent.callTool(name: tool.name) }
|
|
} label: {
|
|
HStack(spacing: 4) {
|
|
Image(systemName: tool.isReadOnly ? "lock.fill" : "play.fill")
|
|
.font(.system(size: 7))
|
|
.foregroundColor(tool.isReadOnly ? .green : .orange)
|
|
VStack(alignment: .leading, spacing: 1) {
|
|
Text(tool.name)
|
|
.font(.system(size: 9, weight: .medium, design: .monospaced))
|
|
Text(tool.description)
|
|
.font(.system(size: 8))
|
|
.foregroundColor(.secondary)
|
|
.lineLimit(1)
|
|
}
|
|
Spacer()
|
|
}
|
|
.padding(.vertical, 3)
|
|
.padding(.horizontal, 4)
|
|
}
|
|
.buttonStyle(.plain)
|
|
}
|
|
}
|