supposedly better tool call animation
This commit is contained in:
@@ -9,10 +9,23 @@ struct SybilChatTranscriptView: View {
|
||||
var bottomContentInset: CGFloat = 0
|
||||
var bottomPinRequestID: Int = 0
|
||||
|
||||
@State private var hasTrackedToolCallMessages = false
|
||||
@State private var knownToolCallMessageIDs: Set<String> = []
|
||||
|
||||
private let bottomAnchorID = "sybil-chat-transcript-bottom-anchor"
|
||||
private var renderItems: [TranscriptRenderItem] {
|
||||
buildTranscriptRenderItems(from: messages)
|
||||
}
|
||||
private var toolCallMessageIDs: Set<String> {
|
||||
Set(messages.compactMap { $0.toolCallMetadata == nil ? nil : $0.id })
|
||||
}
|
||||
private var enteringToolCallMessageIDs: Set<String> {
|
||||
guard hasTrackedToolCallMessages else { return [] }
|
||||
return toolCallMessageIDs.subtracting(knownToolCallMessageIDs)
|
||||
}
|
||||
private var toolCallMessageIDSignature: String {
|
||||
toolCallMessageIDs.sorted().joined(separator: "|")
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ScrollViewReader { proxy in
|
||||
@@ -31,7 +44,11 @@ struct SybilChatTranscriptView: View {
|
||||
MessageBubble(message: message, isSending: isSending)
|
||||
.frame(maxWidth: .infinity)
|
||||
case let .toolGroup(id, messages):
|
||||
ToolCallStackView(groupID: id, messages: messages)
|
||||
ToolCallStackView(
|
||||
groupID: id,
|
||||
messages: messages,
|
||||
entryAnimationIDs: enteringToolCallMessageIDs
|
||||
)
|
||||
.frame(maxWidth: .infinity)
|
||||
.id(id)
|
||||
}
|
||||
@@ -48,8 +65,12 @@ struct SybilChatTranscriptView: View {
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
.onAppear {
|
||||
syncKnownToolCallMessageIDs()
|
||||
scrollToBottom(with: proxy, animated: false)
|
||||
}
|
||||
.onChange(of: toolCallMessageIDSignature) { _, _ in
|
||||
syncKnownToolCallMessageIDs()
|
||||
}
|
||||
.onChange(of: bottomPinRequestID) { _, _ in
|
||||
scrollToBottom(with: proxy, animated: true)
|
||||
}
|
||||
@@ -67,6 +88,12 @@ struct SybilChatTranscriptView: View {
|
||||
action()
|
||||
}
|
||||
}
|
||||
|
||||
private func syncKnownToolCallMessageIDs() {
|
||||
guard !toolCallMessageIDs.isEmpty else { return }
|
||||
knownToolCallMessageIDs.formUnion(toolCallMessageIDs)
|
||||
hasTrackedToolCallMessages = true
|
||||
}
|
||||
}
|
||||
|
||||
enum TranscriptRenderItem: Identifiable {
|
||||
@@ -216,6 +243,7 @@ private struct ToolCallStackView: View {
|
||||
|
||||
var groupID: String
|
||||
var messages: [Message]
|
||||
var entryAnimationIDs: Set<String>
|
||||
|
||||
@Environment(\.accessibilityReduceMotion) private var reduceMotion
|
||||
@State private var isExpanded = false
|
||||
@@ -262,8 +290,14 @@ private struct ToolCallStackView: View {
|
||||
let layout = layout(for: index)
|
||||
let depth = messages.count - index - 1
|
||||
let isHidden = !isExpanded && depth >= visibleCollapsedLimit
|
||||
let shouldAnimateEntry = entryAnimationIDs.contains(message.id) && !isHidden
|
||||
|
||||
ToolCallStackCard(message: message, cardHeight: cardHeight, compactLayout: true)
|
||||
ToolCallStackCard(
|
||||
message: message,
|
||||
cardHeight: cardHeight,
|
||||
compactLayout: true,
|
||||
animateEntry: shouldAnimateEntry
|
||||
)
|
||||
.frame(width: cardWidth, height: cardHeight, alignment: .topLeading)
|
||||
.scaleEffect(layout.scale, anchor: .topLeading)
|
||||
.opacity(layout.opacity)
|
||||
@@ -362,10 +396,16 @@ private struct ToolCallStackCard: View {
|
||||
var message: Message
|
||||
var cardHeight: CGFloat
|
||||
var compactLayout: Bool
|
||||
var animateEntry: Bool
|
||||
|
||||
@Environment(\.accessibilityReduceMotion) private var reduceMotion
|
||||
@State private var entryAnimationArmed = false
|
||||
@State private var didEnter = false
|
||||
|
||||
private var isPreparingEntry: Bool {
|
||||
(animateEntry || entryAnimationArmed) && !didEnter
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if let metadata = message.toolCallMetadata {
|
||||
@@ -378,12 +418,17 @@ private struct ToolCallStackCard: View {
|
||||
}
|
||||
}
|
||||
.frame(height: cardHeight, alignment: .top)
|
||||
.scaleEffect(didEnter ? 1 : 1.025, anchor: .topLeading)
|
||||
.offset(y: didEnter ? 0 : -8)
|
||||
.rotation3DEffect(.degrees(didEnter ? 0 : 3), axis: (x: 1, y: 0, z: 0), anchor: .top)
|
||||
.opacity(didEnter ? 1 : 0.72)
|
||||
.scaleEffect(isPreparingEntry ? 1.025 : 1, anchor: .topLeading)
|
||||
.offset(y: isPreparingEntry ? -8 : 0)
|
||||
.rotation3DEffect(.degrees(isPreparingEntry ? 3 : 0), axis: (x: 1, y: 0, z: 0), anchor: .top)
|
||||
.opacity(isPreparingEntry ? 0.72 : 1)
|
||||
.onAppear {
|
||||
guard !didEnter else { return }
|
||||
guard !didEnter, !entryAnimationArmed else { return }
|
||||
guard animateEntry else {
|
||||
didEnter = true
|
||||
return
|
||||
}
|
||||
entryAnimationArmed = true
|
||||
if reduceMotion {
|
||||
didEnter = true
|
||||
} else {
|
||||
|
||||
@@ -179,8 +179,8 @@ enum SybilTheme {
|
||||
static var toolCallGradient: LinearGradient {
|
||||
LinearGradient(
|
||||
colors: [
|
||||
Color(red: 0.01, green: 0.15, blue: 0.17).opacity(0.70),
|
||||
Color(red: 0.03, green: 0.09, blue: 0.15).opacity(0.78)
|
||||
Color(red: 0.01, green: 0.15, blue: 0.17),
|
||||
Color(red: 0.03, green: 0.09, blue: 0.15)
|
||||
],
|
||||
startPoint: .leading,
|
||||
endPoint: .trailing
|
||||
@@ -190,8 +190,8 @@ enum SybilTheme {
|
||||
static var runningToolCallGradient: LinearGradient {
|
||||
LinearGradient(
|
||||
colors: [
|
||||
Color(red: 0.30, green: 0.19, blue: 0.04).opacity(0.72),
|
||||
Color(red: 0.09, green: 0.05, blue: 0.17).opacity(0.78)
|
||||
Color(red: 0.30, green: 0.19, blue: 0.04),
|
||||
Color(red: 0.09, green: 0.05, blue: 0.17)
|
||||
],
|
||||
startPoint: .leading,
|
||||
endPoint: .trailing
|
||||
@@ -201,8 +201,8 @@ enum SybilTheme {
|
||||
static var failedToolCallGradient: LinearGradient {
|
||||
LinearGradient(
|
||||
colors: [
|
||||
danger.opacity(0.18),
|
||||
Color(red: 0.15, green: 0.03, blue: 0.07).opacity(0.72)
|
||||
Color(red: 0.27, green: 0.04, blue: 0.10),
|
||||
Color(red: 0.15, green: 0.03, blue: 0.07)
|
||||
],
|
||||
startPoint: .leading,
|
||||
endPoint: .trailing
|
||||
|
||||
Reference in New Issue
Block a user