mirror of
https://github.com/wu736139669/hapi.git
synced 2026-11-07 01:21:37 +00:00
105 lines
3.2 KiB
Swift
105 lines
3.2 KiB
Swift
import HapiClient
|
|
import HapiProtocol
|
|
import SwiftUI
|
|
import UIKit
|
|
|
|
// MARK: - List identity
|
|
|
|
extension VisibleChatBlock {
|
|
/// Stable list key: block ids are reducer-stable and tool-group ids are
|
|
/// pinned across recomputes via `previousGroups`, so scroll anchoring
|
|
/// and inspection state survive pipeline re-runs.
|
|
var stableId: String {
|
|
switch self {
|
|
case .block(let block): return block.id
|
|
case .toolGroup(let group): return group.id
|
|
}
|
|
}
|
|
|
|
/// Recycling bucket (the Android `contentKind` / web block `kind`).
|
|
var contentKind: String {
|
|
switch self {
|
|
case .block(let block): return block.kind
|
|
case .toolGroup: return "tool-group"
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Media plumbing
|
|
|
|
private struct ChatMediaKey: EnvironmentKey {
|
|
static let defaultValue: GeneratedMediaLoader? = nil
|
|
}
|
|
|
|
extension EnvironmentValues {
|
|
/// The open chat's authenticated image/file loader.
|
|
var chatMedia: GeneratedMediaLoader? {
|
|
get { self[ChatMediaKey.self] }
|
|
set { self[ChatMediaKey.self] = newValue }
|
|
}
|
|
}
|
|
|
|
private struct ChatInteractionsKey: EnvironmentKey {
|
|
static let defaultValue: ChatInteractor? = nil
|
|
}
|
|
|
|
extension EnvironmentValues {
|
|
/// The open chat's interaction engine (A-M3ab), injected down the block
|
|
/// tree so deeply nested tool cards — groups, sidechains — reach the
|
|
/// permission actions and failed-send retry without prop threading. Nil
|
|
/// (previews/tests, read-only embeddings) keeps blocks in their M2
|
|
/// read-only rendering.
|
|
var chatInteractions: ChatInteractor? {
|
|
get { self[ChatInteractionsKey.self] }
|
|
set { self[ChatInteractionsKey.self] = newValue }
|
|
}
|
|
}
|
|
|
|
// MARK: - Dispatcher
|
|
|
|
/// One thread entry: routes a reduced `VisibleChatBlock` to its card — the
|
|
/// SwiftUI analogue of the web block-kind → component mapping and the
|
|
/// Android `ChatBlockCard`. Also used recursively for tool-call children
|
|
/// (sidechain transcripts).
|
|
struct ChatBlockCard: View {
|
|
let block: VisibleChatBlock
|
|
let basePath: String?
|
|
|
|
var body: some View {
|
|
switch block {
|
|
case .toolGroup(let group):
|
|
ToolGroupBlockView(presentation: ToolGroupPresentation(group))
|
|
case .block(let chatBlock):
|
|
ChatSubBlockView(block: chatBlock, basePath: basePath)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Dispatcher for the plain `ChatBlock` union (shared by the thread and by
|
|
/// tool-card children).
|
|
struct ChatSubBlockView: View {
|
|
let block: ChatBlock
|
|
let basePath: String?
|
|
|
|
var body: some View {
|
|
switch block {
|
|
case .userText(let value):
|
|
UserTextBlockView(block: value)
|
|
case .agentText(let value):
|
|
AgentTextBlockView(block: value)
|
|
case .agentReasoning(let value):
|
|
AgentReasoningBlockView(block: value)
|
|
case .agentEvent(let value):
|
|
AgentEventBlockView(block: value)
|
|
case .cliOutput(let value):
|
|
CliOutputBlockView(block: value)
|
|
case .generatedImage(let value):
|
|
GeneratedImageBlockView(block: value)
|
|
case .codexReview(let value):
|
|
CodexReviewBlockView(block: value)
|
|
case .toolCall(let value):
|
|
ToolCallBlockView(block: value, basePath: basePath)
|
|
}
|
|
}
|
|
}
|