Files
hapi/ios/Hapi/Features/Chat/Blocks/ToolTextContent.swift
T
weishu 6c0ef32350 feat(ios): add native tool inspector and compact tool groups
Move tool output into a live sheet with group navigation and full-content copying. Keep lightweight grouped summaries inline, add agent process pages, and preserve transcript reading position.\n\nAdd real transcript and sheet regression coverage with localized summaries.
2026-09-11 11:30:13 +08:00

75 lines
2.5 KiB
Swift

import HapiUI
import SwiftUI
let toolTextPageSize = 20_000
/// Character boundaries preserve Unicode exactly; concatenating pages is the
/// original payload, including newlines and trailing whitespace.
func toolTextPages(_ text: String) -> [String] {
var pages: [String] = []
var start = text.startIndex
while start < text.endIndex {
let end = text.index(start, offsetBy: toolTextPageSize, limitedBy: text.endIndex) ?? text.endIndex
pages.append(String(text[start..<end]))
start = end
}
return pages
}
struct ToolTextContent: View {
let language: String?
let code: String
var terminal = false
var isError = false
var body: some View {
if code.count <= toolTextPageSize {
if terminal {
TerminalTextView(text: code, isError: isError)
} else {
CodeBlockView(language: language, code: code)
}
} else {
PagedToolText(language: language, code: code, terminal: terminal, isError: isError)
}
}
}
private struct PagedToolText: View {
let language: String?
let code: String
let terminal: Bool
let isError: Bool
@State private var pages: [String] = []
@State private var visiblePages = 1
@Environment(\.hapiPasteboard) private var pasteboard
var body: some View {
LazyVStack(alignment: .leading, spacing: 8) {
Button("Copy full content", systemImage: "doc.on.doc") { pasteboard.copy(code) }
.frame(minHeight: 44)
if pages.isEmpty { ProgressView() }
ForEach(Array(pages.prefix(visiblePages).enumerated()), id: \.offset) { _, page in
if terminal {
TerminalTextView(text: page, isError: isError)
} else {
CodeBlockView(language: language, code: page)
}
}
if visiblePages < pages.count {
Text(String(format: String(localized: "Showing %lld of %lld parts"),
Int64(visiblePages), Int64(pages.count)))
.font(.footnote).foregroundStyle(.secondary)
Button("Load more content") { visiblePages += 1 }
.frame(minHeight: 44)
}
}
.task(id: code) {
let code = code
let next = await Task.detached(priority: .userInitiated) { toolTextPages(code) }.value
guard !Task.isCancelled else { return }
pages = next
}
}
}