mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-07 14:26:47 +02:00
SwiftUI app (iOS 17+) with zero third-party dependencies: - Passwordless email authentication with Keychain token storage - Workspace selection and multi-workspace support - Issue list grouped by status with search and filtering - Issue detail with inline status/priority/assignee editing - Comments with threaded display and compose - Agent task runs history and real-time execution log streaming - WebSocket integration for live updates
71 lines
2.3 KiB
Swift
71 lines
2.3 KiB
Swift
import Foundation
|
|
|
|
@MainActor
|
|
@Observable
|
|
final class WorkspaceViewModel {
|
|
var workspaces: [Workspace] = []
|
|
var selectedWorkspace: Workspace?
|
|
var members: [Member] = []
|
|
var agents: [Agent] = []
|
|
var isLoading = false
|
|
var error: String?
|
|
|
|
var hasSelectedWorkspace: Bool {
|
|
selectedWorkspace != nil
|
|
}
|
|
|
|
func loadWorkspaces() async {
|
|
isLoading = true
|
|
error = nil
|
|
do {
|
|
workspaces = try await APIClient.shared.listWorkspaces()
|
|
// Auto-select if there's a saved workspace or only one
|
|
if let savedId = APIClient.shared.workspaceId,
|
|
let saved = workspaces.first(where: { $0.id == savedId }) {
|
|
await selectWorkspace(saved)
|
|
} else if workspaces.count == 1 {
|
|
await selectWorkspace(workspaces[0])
|
|
}
|
|
} catch {
|
|
self.error = error.localizedDescription
|
|
}
|
|
isLoading = false
|
|
}
|
|
|
|
func selectWorkspace(_ workspace: Workspace) async {
|
|
selectedWorkspace = workspace
|
|
APIClient.shared.workspaceId = workspace.id
|
|
WebSocketClient.shared.disconnect()
|
|
WebSocketClient.shared.connect()
|
|
await loadWorkspaceData()
|
|
}
|
|
|
|
func loadWorkspaceData() async {
|
|
guard let workspace = selectedWorkspace else { return }
|
|
do {
|
|
async let membersResult = APIClient.shared.listMembers(workspaceId: workspace.id)
|
|
async let agentsResult = APIClient.shared.listAgents()
|
|
members = try await membersResult
|
|
agents = try await agentsResult
|
|
} catch {
|
|
self.error = error.localizedDescription
|
|
}
|
|
}
|
|
|
|
/// All possible assignees (members + agents)
|
|
var assignees: [Assignee] {
|
|
let memberAssignees = members.map { Assignee.member($0) }
|
|
let agentAssignees = agents.map { Assignee.agent($0) }
|
|
return memberAssignees + agentAssignees
|
|
}
|
|
|
|
func assigneeName(type: String?, id: String?) -> String {
|
|
guard let type, let id else { return "Unassigned" }
|
|
if type == "agent" {
|
|
return agents.first(where: { $0.id == id })?.name ?? "Agent"
|
|
} else {
|
|
return members.first(where: { $0.userId == id })?.displayName ?? "Member"
|
|
}
|
|
}
|
|
}
|