mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 12:56:01 -06:00
e907f412ca
* fix(ios): unify root navigation and restore approval settings routes * fix(ios): align shell cleanup inventories and test entrypoints
203 lines
6.1 KiB
Swift
203 lines
6.1 KiB
Swift
import OpenClawKit
|
|
import OpenClawProtocol
|
|
import SwiftUI
|
|
|
|
struct AgentProTab: View {
|
|
@Environment(NodeAppModel.self) var appModel
|
|
@Environment(\.scenePhase) var scenePhase
|
|
let directRoute: AgentRoute
|
|
let headerSidebarAction: OpenClawSidebarHeaderAction?
|
|
let headerTitle: String
|
|
let openSettings: (() -> Void)?
|
|
@State var overview: AgentOverviewSnapshot?
|
|
@State var overviewErrorText: String?
|
|
@State var overviewLoading: Bool = false
|
|
@State var overviewRefreshGate = AgentOverviewRefreshGate()
|
|
@State var agentRosterFilter: AgentRosterFilter = .all
|
|
@State var agentSearchText = ""
|
|
@State var skillFilter: String = ""
|
|
@State var skillStatusFilter: SkillStatusFilter = .all
|
|
@State var skillMutationBusyKeys: Set<String> = []
|
|
@State var skillMutationErrorText: String?
|
|
@State var skillMutationStatusText: String?
|
|
@State var skillConfigBusyKeys: Set<String> = []
|
|
@State var skillConfigMessages: [String: SkillEditorMessage] = [:]
|
|
@State var skillAPIKeyDrafts: [String: String] = [:]
|
|
@State var skillEditorSelection: SkillEditorSelection?
|
|
@State var clawHubQuery: String = ""
|
|
@State var clawHubResults: [ClawHubSearchResultLite] = []
|
|
@State var clawHubLoading: Bool = false
|
|
@State var clawHubErrorText: String?
|
|
@State var clawHubInstallSlug: String?
|
|
@State var cronActionBusyIDs: Set<String> = []
|
|
@State var pendingCronRuns = AgentAutomationPendingRunRegistry()
|
|
@State var cronActionStatusText: String?
|
|
@State var automationQuery = ""
|
|
@State var automationListFilter: AutomationListFilter = .all
|
|
@State var automationEditorSelection: AutomationEditorSelection?
|
|
|
|
enum AgentRoute: Hashable {
|
|
case agents
|
|
case skills
|
|
case instances
|
|
case cron
|
|
case usage
|
|
case dreaming
|
|
case files
|
|
}
|
|
|
|
enum SkillStatusFilter: String, CaseIterable, Identifiable {
|
|
case all
|
|
case enabled
|
|
case off
|
|
case setup
|
|
case blocked
|
|
|
|
var id: Self {
|
|
self
|
|
}
|
|
|
|
var title: String {
|
|
switch self {
|
|
case .all: String(localized: "All")
|
|
case .enabled: String(localized: "Enabled")
|
|
case .off: String(localized: "Off")
|
|
case .setup: String(localized: "Setup")
|
|
case .blocked: String(localized: "Blocked")
|
|
}
|
|
}
|
|
}
|
|
|
|
enum AgentRosterFilter: String, CaseIterable, Identifiable {
|
|
case all
|
|
case online
|
|
case ready
|
|
|
|
var id: Self {
|
|
self
|
|
}
|
|
|
|
var title: String {
|
|
switch self {
|
|
case .all: String(localized: "All")
|
|
case .online: String(localized: "Online")
|
|
case .ready: String(localized: "Ready")
|
|
}
|
|
}
|
|
|
|
var systemImage: String {
|
|
switch self {
|
|
case .all: "person.2"
|
|
case .online: "antenna.radiowaves.left.and.right"
|
|
case .ready: "checkmark.circle"
|
|
}
|
|
}
|
|
}
|
|
|
|
enum AutomationListFilter: String, CaseIterable, Identifiable {
|
|
case all
|
|
case active
|
|
case paused
|
|
|
|
var id: Self {
|
|
self
|
|
}
|
|
|
|
var title: String {
|
|
switch self {
|
|
case .all: String(localized: "All")
|
|
case .active: String(localized: "Active")
|
|
case .paused: String(localized: "Paused")
|
|
}
|
|
}
|
|
}
|
|
|
|
enum AgentLayout {
|
|
static let cardRadius: CGFloat = OpenClawProMetric.cardRadius
|
|
}
|
|
|
|
enum AgentRosterState: Equatable {
|
|
case online
|
|
case ready
|
|
|
|
var color: Color {
|
|
switch self {
|
|
case .online: OpenClawBrand.ok
|
|
case .ready: OpenClawBrand.info
|
|
}
|
|
}
|
|
}
|
|
|
|
struct SkillEditorSelection: Identifiable {
|
|
let id: String
|
|
}
|
|
|
|
struct AutomationEditorSelection: Identifiable {
|
|
let initialJob: CronJob
|
|
let sourceGatewayID: String
|
|
|
|
var id: String {
|
|
self.initialJob.id
|
|
}
|
|
}
|
|
|
|
struct SkillEditorMessage {
|
|
let kind: Kind
|
|
let text: String
|
|
|
|
enum Kind {
|
|
case success
|
|
case error
|
|
}
|
|
}
|
|
|
|
init(
|
|
directRoute: AgentRoute,
|
|
headerSidebarAction: OpenClawSidebarHeaderAction? = nil,
|
|
headerTitle: String = "Agents",
|
|
openSettings: (() -> Void)? = nil)
|
|
{
|
|
self.directRoute = directRoute
|
|
self.headerSidebarAction = headerSidebarAction
|
|
self.headerTitle = headerTitle
|
|
self.openSettings = openSettings
|
|
}
|
|
|
|
var body: some View {
|
|
self.directDestination(for: self.directRoute)
|
|
.task(id: self.overviewTaskID) {
|
|
await self.refreshOverview(force: false)
|
|
}
|
|
.sheet(item: self.$skillEditorSelection) { selection in
|
|
if let skill = self.skillByKey(selection.id) {
|
|
self.skillEditorSheet(skill)
|
|
} else {
|
|
self.missingSkillEditorSheet
|
|
}
|
|
}
|
|
.sheet(item: self.$automationEditorSelection) { selection in
|
|
AgentAutomationDetailScreen(
|
|
initialJob: selection.initialJob,
|
|
sourceGatewayID: selection.sourceGatewayID,
|
|
pendingRunRegistry: self.pendingCronRuns,
|
|
onRunQueued: { runID, processInstanceID in
|
|
self.reservePendingCronRun(
|
|
jobID: selection.initialJob.id,
|
|
runID: runID,
|
|
processInstanceID: processInstanceID,
|
|
sourceGatewayID: selection.sourceGatewayID)
|
|
},
|
|
onChanged: {
|
|
Task { await self.refreshOverview(force: true) }
|
|
})
|
|
}
|
|
}
|
|
|
|
private func directDestination(for route: AgentRoute) -> some View {
|
|
self.destination(for: route)
|
|
.toolbar(
|
|
route != .agents && self.directHeaderSidebarAction(for: route) != nil ? .hidden : .visible,
|
|
for: .navigationBar)
|
|
}
|
|
}
|