Tabs: '+' opens brov-chat or term.macOS (max 6, split around the camera); real zsh terminals via SwiftTerm; per-tab chat history and claude session; hover 3 s to close with confirmation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -271,7 +271,10 @@ final class ClaudeService {
|
||||
// MARK: - Claude Code CLI chat (BroV: the user's own `claude`, no API key)
|
||||
|
||||
func chatClaudeCode(query: String, context: PromptContext?, state: AppState) async {
|
||||
let cli = ClaudeCodeCLI.shared
|
||||
// Each brov-chat tab has its own claude conversation and history.
|
||||
let tab = state.activeTabId
|
||||
let cli = ChatTabs.shared.cli(for: tab)
|
||||
func isOnScreen() -> Bool { tab == nil || state.activeTabId == tab }
|
||||
var prompt = query
|
||||
// Context goes with the first message of a conversation, like the API chat.
|
||||
if cli.sessionID == nil, let context {
|
||||
@@ -292,7 +295,7 @@ final class ClaudeService {
|
||||
|
||||
let placeholder = ChatMessage(role: .assistant, content: "")
|
||||
let msgId = placeholder.id
|
||||
state.chatHistory.append(placeholder)
|
||||
if let tab { ChatTabs.shared.append(tab: tab, placeholder) } else { state.chatHistory.append(placeholder) }
|
||||
state.stateOverride = .thinking
|
||||
|
||||
do {
|
||||
@@ -306,17 +309,22 @@ final class ClaudeService {
|
||||
if !visible.isEmpty, state.stateOverride == .thinking {
|
||||
state.stateOverride = nil // hide typing dots on first visible text
|
||||
}
|
||||
if let idx = state.chatHistory.firstIndex(where: { $0.id == msgId }) {
|
||||
if let tab {
|
||||
ChatTabs.shared.update(tab: tab, message: msgId) { $0.content = visible }
|
||||
} else if let idx = state.chatHistory.firstIndex(where: { $0.id == msgId }) {
|
||||
state.chatHistory[idx].content = visible
|
||||
}
|
||||
}
|
||||
let trimmed = final.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if let idx = state.chatHistory.firstIndex(where: { $0.id == msgId }) {
|
||||
if let tab {
|
||||
if trimmed.isEmpty { ChatTabs.shared.remove(tab: tab, message: msgId) }
|
||||
else { ChatTabs.shared.update(tab: tab, message: msgId) { $0.content = trimmed } }
|
||||
} else if let idx = state.chatHistory.firstIndex(where: { $0.id == msgId }) {
|
||||
if trimmed.isEmpty { state.chatHistory.remove(at: idx) }
|
||||
else { state.chatHistory[idx].content = trimmed }
|
||||
}
|
||||
state.stateOverride = nil
|
||||
state.view = .prompt
|
||||
if isOnScreen() && state.view != .terminal { state.view = .prompt }
|
||||
state.claudeNeedsLogin = false
|
||||
NotificationCenter.default.post(name: .triggerEmote, object: BotEmote.happy)
|
||||
} catch is CancellationError {
|
||||
@@ -324,8 +332,9 @@ final class ClaudeService {
|
||||
state.stateOverride = nil
|
||||
} catch {
|
||||
state.chatHistory.removeAll { $0.id == msgId && $0.content.isEmpty }
|
||||
if let tab { ChatTabs.shared.remove(tab: tab, message: msgId) }
|
||||
state.stateOverride = nil
|
||||
await showError(error.localizedDescription, state: state)
|
||||
if isOnScreen() { await showError(error.localizedDescription, state: state) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user