diff --git a/Wisp/Models/Local/SpriteChat.swift b/Wisp/Models/Local/SpriteChat.swift index 3a7a8b65..25ee3a9c 100644 --- a/Wisp/Models/Local/SpriteChat.swift +++ b/Wisp/Models/Local/SpriteChat.swift @@ -29,6 +29,10 @@ final class SpriteChat { customName ?? "Chat \(chatNumber)" } + var worktreeBranchLabel: String { + worktreeBranch.map { "'\($0)'" } ?? "same worktree" + } + init( spriteName: String, chatNumber: Int, diff --git a/Wisp/ViewModels/SpriteChatListViewModel.swift b/Wisp/ViewModels/SpriteChatListViewModel.swift index a9354e61..f53ca7bd 100644 --- a/Wisp/ViewModels/SpriteChatListViewModel.swift +++ b/Wisp/ViewModels/SpriteChatListViewModel.swift @@ -36,20 +36,21 @@ final class SpriteChatListViewModel { } @discardableResult - func createChat(modelContext: ModelContext) -> SpriteChat { + func createChat(inheritingWorktreeFrom source: SpriteChat? = nil, modelContext: ModelContext) -> SpriteChat { let maxNumber = chats.map(\.chatNumber).max() ?? 0 - - let workingDirectory = UserDefaults.standard.string(forKey: "workingDirectory_\(spriteName)") ?? "/home/sprite/project" - + let workingDirectory = source?.workingDirectory + ?? UserDefaults.standard.string(forKey: "workingDirectory_\(spriteName)") + ?? "/home/sprite/project" let chat = SpriteChat( spriteName: spriteName, chatNumber: maxNumber + 1, workingDirectory: workingDirectory, spriteCreatedAt: spriteCreatedAt ) + chat.worktreePath = source?.worktreePath + chat.worktreeBranch = source?.worktreeBranch modelContext.insert(chat) try? modelContext.save() - chats.insert(chat, at: 0) activeChatId = chat.id logger.info("Created chat \(chat.chatNumber) for \(self.spriteName)") diff --git a/Wisp/Views/SpriteDetail/Chat/ChatSwitcherSheet.swift b/Wisp/Views/SpriteDetail/Chat/ChatSwitcherSheet.swift index 41645c8f..81c30e71 100644 --- a/Wisp/Views/SpriteDetail/Chat/ChatSwitcherSheet.swift +++ b/Wisp/Views/SpriteDetail/Chat/ChatSwitcherSheet.swift @@ -119,11 +119,34 @@ struct ChatSwitcherSheet: View { Button("Done") { dismiss() } } ToolbarItem(placement: .primaryAction) { - Button { - viewModel.createChat(modelContext: modelContext) - dismiss() - } label: { - Image(systemName: "plus") + if let activeChat = viewModel.activeChat, activeChat.worktreePath != nil { + let branchLabel = activeChat.worktreeBranchLabel + Menu { + Button { + viewModel.createChat(inheritingWorktreeFrom: activeChat, modelContext: modelContext) + dismiss() + } label: { + Label("New Chat in \(branchLabel)", systemImage: "arrow.branch") + } + Button { + viewModel.createChat(modelContext: modelContext) + dismiss() + } label: { + Label("New Chat", systemImage: "square.and.pencil") + } + } label: { + Image(systemName: "plus") + } primaryAction: { + viewModel.createChat(modelContext: modelContext) + dismiss() + } + } else { + Button { + viewModel.createChat(modelContext: modelContext) + dismiss() + } label: { + Image(systemName: "plus") + } } } } diff --git a/Wisp/Views/SpriteDetail/SpriteDetailView.swift b/Wisp/Views/SpriteDetail/SpriteDetailView.swift index d9f57e9d..1272be84 100644 --- a/Wisp/Views/SpriteDetail/SpriteDetailView.swift +++ b/Wisp/Views/SpriteDetail/SpriteDetailView.swift @@ -54,6 +54,39 @@ struct SpriteDetailView: View { ) } + @ViewBuilder + private var newChatButton: some View { + if let activeChat = chatListViewModel.activeChat, activeChat.worktreePath != nil { + let branchLabel = activeChat.worktreeBranchLabel + Menu { + Button { + let chat = chatListViewModel.createChat(inheritingWorktreeFrom: activeChat, modelContext: modelContext) + switchToChat(chat) + } label: { + Label("New Chat in \(branchLabel)", systemImage: "arrow.branch") + } + Button { + let chat = chatListViewModel.createChat(modelContext: modelContext) + switchToChat(chat) + } label: { + Label("New Chat", systemImage: "square.and.pencil") + } + } label: { + Image(systemName: "square.and.pencil") + } primaryAction: { + let chat = chatListViewModel.createChat(modelContext: modelContext) + switchToChat(chat) + } + } else { + Button { + let chat = chatListViewModel.createChat(modelContext: modelContext) + switchToChat(chat) + } label: { + Image(systemName: "square.and.pencil") + } + } + } + private var regularLayout: some View { HStack(spacing: 0) { SpriteNavigationPanel( @@ -64,7 +97,13 @@ struct SpriteDetailView: View { let chat = chatListViewModel.createChat(modelContext: modelContext) selectedTab = .chat switchToChat(chat) - } + }, + onCreateChatInWorktree: chatListViewModel.activeChat?.worktreePath != nil ? { + guard let activeChat = chatListViewModel.activeChat else { return } + let chat = chatListViewModel.createChat(inheritingWorktreeFrom: activeChat, modelContext: modelContext) + selectedTab = .chat + switchToChat(chat) + } : nil ) .frame(width: 260) @@ -190,12 +229,7 @@ struct SpriteDetailView: View { } } - Button { - let chat = chatListViewModel.createChat(modelContext: modelContext) - switchToChat(chat) - } label: { - Image(systemName: "square.and.pencil") - } + newChatButton } } } else if selectedTab == .overview { diff --git a/Wisp/Views/SpriteDetail/SpriteNavigationPanel.swift b/Wisp/Views/SpriteDetail/SpriteNavigationPanel.swift index 36998e1a..8ef5fcbe 100644 --- a/Wisp/Views/SpriteDetail/SpriteNavigationPanel.swift +++ b/Wisp/Views/SpriteDetail/SpriteNavigationPanel.swift @@ -12,6 +12,7 @@ struct SpriteNavigationPanel: View { @Binding var selection: SpriteNavSelection? let chatListViewModel: SpriteChatListViewModel let onCreateChat: () -> Void + var onCreateChatInWorktree: (() -> Void)? = nil @Environment(SpritesAPIClient.self) private var apiClient @Environment(ChatSessionManager.self) private var chatSessionManager @Environment(\.modelContext) private var modelContext @@ -46,11 +47,27 @@ struct SpriteNavigationPanel: View { .tag(SpriteNavSelection.chat(chat.id)) } } - Button(action: onCreateChat) { - Label("New Chat", systemImage: "square.and.pencil") + if let onCreateChatInWorktree { + let branchLabel = chatListViewModel.activeChat?.worktreeBranchLabel ?? "same worktree" + Menu { + Button(action: onCreateChatInWorktree) { + Label("New Chat in \(branchLabel)", systemImage: "arrow.branch") + } + Button(action: onCreateChat) { + Label("New Chat", systemImage: "square.and.pencil") + } + } label: { + Label("New Chat", systemImage: "square.and.pencil") + } primaryAction: onCreateChat + .foregroundStyle(.secondary) + .buttonStyle(.borderless) + } else { + Button(action: onCreateChat) { + Label("New Chat", systemImage: "square.and.pencil") + } + .foregroundStyle(.secondary) + .buttonStyle(.borderless) } - .foregroundStyle(.secondary) - .buttonStyle(.borderless) } } .listStyle(.sidebar)