mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-09 15:36:45 +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
133 lines
4.2 KiB
Swift
133 lines
4.2 KiB
Swift
import Foundation
|
|
|
|
@MainActor
|
|
@Observable
|
|
final class IssueDetailViewModel {
|
|
var issue: Issue
|
|
var comments: [Comment] = []
|
|
var taskRuns: [AgentTask] = []
|
|
var activeTask: AgentTask?
|
|
var taskMessages: [TaskMessage] = []
|
|
var isLoading = false
|
|
var error: String?
|
|
|
|
init(issue: Issue) {
|
|
self.issue = issue
|
|
}
|
|
|
|
func loadAll() async {
|
|
isLoading = true
|
|
do {
|
|
async let commentsResult = APIClient.shared.listComments(issueId: issue.id)
|
|
async let taskRunsResult = APIClient.shared.listTaskRuns(issueId: issue.id)
|
|
async let activeTaskResult = APIClient.shared.getActiveTask(issueId: issue.id)
|
|
|
|
comments = try await commentsResult
|
|
taskRuns = try await taskRunsResult
|
|
activeTask = try await activeTaskResult
|
|
|
|
// Load messages for active task
|
|
if let task = activeTask {
|
|
taskMessages = try await APIClient.shared.listTaskMessages(taskId: task.id)
|
|
}
|
|
} catch {
|
|
self.error = error.localizedDescription
|
|
}
|
|
isLoading = false
|
|
}
|
|
|
|
func updateStatus(_ status: IssueStatus) async {
|
|
do {
|
|
let updated = try await APIClient.shared.updateIssue(issue.id, UpdateIssueRequest(status: status.rawValue))
|
|
issue = updated
|
|
} catch {
|
|
self.error = error.localizedDescription
|
|
}
|
|
}
|
|
|
|
func updatePriority(_ priority: IssuePriority) async {
|
|
do {
|
|
let updated = try await APIClient.shared.updateIssue(issue.id, UpdateIssueRequest(priority: priority.rawValue))
|
|
issue = updated
|
|
} catch {
|
|
self.error = error.localizedDescription
|
|
}
|
|
}
|
|
|
|
func updateAssignee(type: String?, id: String?) async {
|
|
do {
|
|
let updated = try await APIClient.shared.updateIssue(issue.id, UpdateIssueRequest(
|
|
assigneeType: type ?? "",
|
|
assigneeId: id ?? ""
|
|
))
|
|
issue = updated
|
|
} catch {
|
|
self.error = error.localizedDescription
|
|
}
|
|
}
|
|
|
|
func updateTitle(_ title: String) async {
|
|
do {
|
|
let updated = try await APIClient.shared.updateIssue(issue.id, UpdateIssueRequest(title: title))
|
|
issue = updated
|
|
} catch {
|
|
self.error = error.localizedDescription
|
|
}
|
|
}
|
|
|
|
func addComment(_ content: String) async {
|
|
do {
|
|
let comment = try await APIClient.shared.createComment(issueId: issue.id, content: content)
|
|
comments.append(comment)
|
|
} catch {
|
|
self.error = error.localizedDescription
|
|
}
|
|
}
|
|
|
|
func loadTaskMessages(taskId: String) async {
|
|
do {
|
|
taskMessages = try await APIClient.shared.listTaskMessages(taskId: taskId)
|
|
} catch {
|
|
self.error = error.localizedDescription
|
|
}
|
|
}
|
|
|
|
func setupRealtimeUpdates() {
|
|
let issueId = issue.id
|
|
WebSocketClient.shared.on("task:message") { [weak self] event in
|
|
Task { @MainActor in
|
|
guard let self,
|
|
let payload = event.payload["issue_id"] as? String,
|
|
payload == issueId else { return }
|
|
// Reload messages for active task
|
|
if let task = self.activeTask {
|
|
await self.loadTaskMessages(taskId: task.id)
|
|
}
|
|
}
|
|
}
|
|
|
|
WebSocketClient.shared.onPrefix("task") { [weak self] event in
|
|
Task { @MainActor in
|
|
guard let self else { return }
|
|
await self.loadAll()
|
|
}
|
|
}
|
|
|
|
WebSocketClient.shared.onPrefix("comment") { [weak self] event in
|
|
Task { @MainActor in
|
|
guard let self else { return }
|
|
self.comments = (try? await APIClient.shared.listComments(issueId: issueId)) ?? self.comments
|
|
}
|
|
}
|
|
|
|
WebSocketClient.shared.on("issue:updated") { [weak self] event in
|
|
Task { @MainActor in
|
|
guard let self else { return }
|
|
if let updated = try? await APIClient.shared.getIssue(issueId) {
|
|
self.issue = updated
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|