mirror of
https://github.com/awizemann/scarf.git
synced 2026-05-10 10:36:35 +00:00
c8208dedb1
- Slash menu: filter at the parent and pass the pre-filtered list to SlashCommandMenu (pure-prefix match, no description fallback). Adds `.id(menuQuery)` to force a fresh view on every query so SwiftUI can't render stale props — this was the cause of "typing /mo still shows /help" (the old description fallback plus a cached child view kept /help pinned regardless of query). - Auto-scroll to bottom when the user submits a message and again when the prompt completes. `.defaultScrollAnchor(.bottom)` handles slow streaming fine, but rapid slash-command responses outran the anchor and left the response off-screen. - Loading state: add `ChatViewModel.isPreparingSession` (true during Starting / Creating / Loading / Reconnecting). While true, the message list swaps its placeholder for a ProgressView — non-blocking, just a view inside the ScrollView. - Center the empty-state placeholder properly: replace `.padding(.vertical, 80)` with Spacers inside `.containerRelativeFrame(.vertical)` so the placeholder sits in the true vertical center of the chat pane at any window size. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
53 lines
2.0 KiB
Swift
53 lines
2.0 KiB
Swift
import SwiftUI
|
|
|
|
struct RichChatView: View {
|
|
@Bindable var richChat: RichChatViewModel
|
|
var onSend: (String) -> Void
|
|
var isEnabled: Bool
|
|
@Environment(HermesFileWatcher.self) private var fileWatcher
|
|
@Environment(ChatViewModel.self) private var chatViewModel
|
|
|
|
/// In ACP mode, events drive updates directly — no DB polling needed.
|
|
private var isACPMode: Bool { chatViewModel.isACPConnected }
|
|
|
|
var body: some View {
|
|
VStack(spacing: 0) {
|
|
SessionInfoBar(
|
|
session: richChat.currentSession,
|
|
isWorking: richChat.isAgentWorking,
|
|
acpInputTokens: richChat.acpInputTokens,
|
|
acpOutputTokens: richChat.acpOutputTokens,
|
|
acpThoughtTokens: richChat.acpThoughtTokens
|
|
)
|
|
Divider()
|
|
|
|
// Always mount RichChatMessageList; empty state lives inside it.
|
|
// Swapping between a ContentUnavailableView and the ScrollView
|
|
// hierarchy on first message caused a full view tree rebuild,
|
|
// which manifests as a white flash.
|
|
RichChatMessageList(
|
|
groups: richChat.messageGroups,
|
|
isWorking: richChat.isAgentWorking,
|
|
isLoadingSession: chatViewModel.isPreparingSession,
|
|
scrollTrigger: richChat.scrollTrigger
|
|
)
|
|
|
|
Divider()
|
|
RichChatInputBar(
|
|
onSend: { text in
|
|
onSend(text)
|
|
},
|
|
isEnabled: isEnabled,
|
|
commands: richChat.availableCommands,
|
|
showCompressButton: richChat.supportsCompress && !richChat.hasBroaderCommandMenu
|
|
)
|
|
}
|
|
// DB polling fallback for terminal mode only — never overwrite ACP messages
|
|
.onChange(of: fileWatcher.lastChangeDate) {
|
|
if !isACPMode, !richChat.hasMessages, richChat.sessionId != nil {
|
|
richChat.scheduleRefresh()
|
|
}
|
|
}
|
|
}
|
|
}
|