Files
Termind/Frameworks/TermindPresentation/AssistantTranscriptEntry.swift
T

107 lines
3.6 KiB
Swift

// SPDX-FileCopyrightText: 2026 Kaede Akino
// SPDX-License-Identifier: GPL-3.0-only
import AgentKit
import Foundation
import TermindApplication
/// Stable rows shared by the macOS and iOS ListViewKit transcript adapters.
nonisolated public struct AssistantTranscriptEntry: Identifiable, Hashable, Sendable {
public enum Kind: Hashable, Sendable {
case empty
case user
case assistant
case tool
case work
case command
case plan
case compaction
case compactionActivity
case failure
case interrupted
}
public enum Content: Hashable, Sendable {
case empty
case user(ChatMessage, canEdit: Bool)
case assistant(ChatMessage, canFork: Bool)
case tool(AgentToolCard)
case work(AgentWorkGroup)
case command(SuggestedCommand)
case plan(AgentPlanCard)
case compaction(AgentCompactionMark)
case compactionActivity(AgentCompactionActivity)
case failure(AgentRunFailureNotice)
case interrupted
}
public let id: UUID
public let content: Content
public let hasLeadingSpacing: Bool
public init(id: UUID, content: Content, hasLeadingSpacing: Bool = false) {
self.id = id
self.content = content
self.hasLeadingSpacing = hasLeadingSpacing
}
private static let emptyID = UUID(uuidString: "00E48921-70E7-493A-9EB3-44727BC2145B")!
private static let interruptedID = UUID(uuidString: "FD8D34B6-E81F-4DC8-91E8-479AD9ACF5CB")!
public static func items(
timeline: [TimelineItem],
isInterrupted: Bool,
canEdit: (UUID) -> Bool,
canFork: (UUID) -> Bool
) -> [Self] {
if timeline.isEmpty { return [.init(id: emptyID, content: .empty)] }
var result = timeline.enumerated().map { index, item in
let content: Content =
switch item {
case .message(let message) where message.role == .user:
.user(message, canEdit: canEdit(message.id))
case .message(let message):
.assistant(message, canFork: canFork(message.id))
case .tool(let card): .tool(card)
case .work(let group): .work(group)
case .command(let command): .command(command)
case .plan(let card): .plan(card)
case .compaction(let mark): .compaction(mark)
case .compactionActivity(let activity): .compactionActivity(activity)
case .failure(let failure): .failure(failure)
}
return Self(id: item.id, content: content, hasLeadingSpacing: index > 0)
}
if isInterrupted {
result.append(.init(id: interruptedID, content: .interrupted, hasLeadingSpacing: true))
}
return result
}
public var presentationIDs: Set<UUID> {
var result: Set<UUID> = [id]
guard case .work(let group) = content else { return result }
for item in group.items {
result.insert(item.id)
if case .tools(let batch) = item { result.formUnion(batch.cards.map(\.id)) }
}
return result
}
public var kind: Kind {
switch content {
case .empty: .empty
case .user: .user
case .assistant: .assistant
case .tool: .tool
case .work: .work
case .command: .command
case .plan: .plan
case .compaction: .compaction
case .compactionActivity: .compactionActivity
case .failure: .failure
case .interrupted: .interrupted
}
}
}