ios: hide raw tool entries in Agent transcript
TestFlight / Build and upload (push) Successful in 1m59s
TestFlight / Build and upload (push) Successful in 1m59s
This commit is contained in:
@@ -203,6 +203,23 @@ struct AgentRoomState: Equatable, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
// Filter only the transcript projection, before windowing. Keep the room's raw
|
||||
// messages and cursors intact for upserts, replay, unread state and activity.
|
||||
struct AgentTranscript {
|
||||
let messages: [Message]
|
||||
let startIndex: Int
|
||||
var displayed: [Message] { Array(messages.dropFirst(startIndex)) }
|
||||
|
||||
init(messages: [Message], startID: String? = nil, limit: Int = 100) {
|
||||
let visible = messages.filter {
|
||||
$0.role != .tool && $0.metadata?.objectValue?["kind"]?.stringValue != "tool_call"
|
||||
}
|
||||
self.messages = visible
|
||||
startIndex = startID.flatMap { id in visible.firstIndex(where: { $0.id == id }) }
|
||||
?? max(0, visible.count - limit)
|
||||
}
|
||||
}
|
||||
|
||||
// The transcript latch is independent of the truthful sidebar/worker activity.
|
||||
struct AgentPresentation {
|
||||
private(set) var suppressed = false
|
||||
|
||||
@@ -22,9 +22,10 @@ struct SybilAgentView: View {
|
||||
}
|
||||
var body: some View {
|
||||
let rendered = room.snapshot
|
||||
let allMessages = room.messages
|
||||
let startIndex = historyStartID.flatMap { id in allMessages.firstIndex(where: { $0.id == id }) } ?? max(0, allMessages.count - historyLimit)
|
||||
let displayed = Array(allMessages.dropFirst(startIndex))
|
||||
let transcript = AgentTranscript(messages: room.messages, startID: historyStartID, limit: historyLimit)
|
||||
let allMessages = transcript.messages
|
||||
let startIndex = transcript.startIndex
|
||||
let displayed = transcript.displayed
|
||||
VStack(spacing: 0) {
|
||||
ScrollViewReader { proxy in
|
||||
ScrollView {
|
||||
@@ -51,13 +52,7 @@ struct SybilAgentView: View {
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 6)
|
||||
}
|
||||
Group {
|
||||
if message.isToolCallLog {
|
||||
AgentToolCard(message: message)
|
||||
} else {
|
||||
AgentMessageRow(message: message, retry: { room.retry(message.id) })
|
||||
}
|
||||
}
|
||||
AgentMessageRow(message: message, retry: { room.retry(message.id) })
|
||||
.background(GeometryReader { geometry in
|
||||
Color.clear.preference(key: AgentRowFrames.self, value: [message.id: geometry.frame(in: .named("agent-scroll"))])
|
||||
})
|
||||
@@ -110,8 +105,8 @@ struct SybilAgentView: View {
|
||||
.map { AgentReadingAnchor(id: $0.key, offset: $0.value.minY) }
|
||||
let anchor = restoreAnchor ?? (!atLatest && !isScrolling ? previousAnchor : nil)
|
||||
if let anchor, let frame = frames[anchor.id], abs(frame.minY - anchor.offset) > 1 {
|
||||
// Restore the exact pixel offset after insertion, tool
|
||||
// expansion or text reflow. Normalize the saved frames to
|
||||
// Restore the exact pixel offset after insertion or text
|
||||
// reflow. Normalize the saved frames to
|
||||
// the requested offset so the next layout cannot undo it.
|
||||
let requested = scrollMetrics.offset + frame.minY - anchor.offset
|
||||
let target = min(max(requested, -scrollMetrics.topInset), max(-scrollMetrics.topInset, scrollMetrics.contentHeight - scrollMetrics.height + scrollMetrics.bottomInset))
|
||||
@@ -260,57 +255,6 @@ private struct AgentMessageRow: View {
|
||||
}
|
||||
}
|
||||
|
||||
struct AgentToolCard: View {
|
||||
var message: Message
|
||||
@State private var expanded = false
|
||||
var body: some View {
|
||||
if let metadata = message.toolCallMetadata {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Button {
|
||||
expanded.toggle()
|
||||
} label: {
|
||||
HStack(spacing: 8) {
|
||||
ToolCallActivityChip(metadata: metadata, fallbackContent: message.content, createdAt: message.createdAt, compactLayout: true)
|
||||
.allowsHitTesting(false)
|
||||
Spacer(minLength: 0)
|
||||
Image(systemName: expanded ? "chevron.down" : "chevron.right")
|
||||
.font(.system(size: 13, weight: .semibold))
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityIdentifier("agent-tool-\(metadata.toolName ?? "tool")")
|
||||
.accessibilityValue(expanded ? "Expanded" : "Collapsed")
|
||||
if expanded {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
if let args = metadata.args { detail("Input", text: pretty(args)) }
|
||||
if let error = metadata.error { detail("Error", text: error) }
|
||||
if let result = metadata.result { detail("Result", text: pretty(result)) }
|
||||
else if let preview = metadata.resultPreview { detail("Result", text: preview) }
|
||||
if metadata.args == nil && metadata.result == nil && metadata.resultPreview == nil && metadata.error == nil {
|
||||
Text(message.content).font(.sybil(.caption))
|
||||
}
|
||||
}.padding(.top, 8).frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
.foregroundStyle(SybilTheme.textMuted)
|
||||
.padding(8)
|
||||
.background(SybilTheme.surface.opacity(0.8), in: RoundedRectangle(cornerRadius: 14))
|
||||
}
|
||||
}
|
||||
private func detail(_ title: String, text: String) -> some View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(title).font(.sybil(.caption, weight: .semibold)).accessibilityIdentifier("agent-tool-\(title.lowercased())")
|
||||
Text(text).font(.system(size: 12, design: .monospaced)).textSelection(.enabled)
|
||||
}
|
||||
}
|
||||
private func pretty(_ value: JSONValue) -> String {
|
||||
if let text = value.stringValue { return text }
|
||||
let encoder = JSONEncoder(); encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
|
||||
return (try? encoder.encode(value)).map { String(decoding: $0, as: UTF8.self) } ?? ""
|
||||
}
|
||||
}
|
||||
|
||||
// Animate both occupied height and content, so incoming rows push older history up.
|
||||
private struct AgentGrowingLayout: Layout {
|
||||
var progress: CGFloat
|
||||
|
||||
Reference in New Issue
Block a user