Skip to content

Commit 780ca9e

Browse files
Merge pull request #110 from jamesrochabrun/jroch-session-start
Add session resume option and increase stream timeout
2 parents e89fc7b + d4ab331 commit 780ca9e

File tree

3 files changed

+73
-16
lines changed

3 files changed

+73
-16
lines changed

Sources/ClaudeCodeCore/UI/ChatInputView.swift

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -318,11 +318,6 @@ extension ChatInputView {
318318
.onAppear {
319319
isFocused = true
320320
}
321-
.onChange(of: isFocused) { _, newValue in
322-
if newValue {
323-
xcodeObservationViewModel.restartObservation()
324-
}
325-
}
326321
.onChange(of: triggerFocus) { _, shouldFocus in
327322
if shouldFocus {
328323
isFocused = true

Sources/ClaudeCodeCore/UI/ChatScreen.swift

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,12 @@ public struct ChatScreen: View {
120120
/// Controls the visibility of the delete confirmation dialog
121121
@State private var showDeleteConfirmation = false
122122

123+
/// Controls the visibility of the session options dialog (terminal vs new session)
124+
@State private var showSessionOptions = false
125+
126+
/// Controls the visibility of the session resumed alert
127+
@State private var showResumeAlert = false
128+
123129
/// Global preferences storage for observing default working directory changes
124130
@Environment(GlobalPreferencesStorage.self) var globalPreferences
125131

@@ -190,6 +196,24 @@ public struct ChatScreen: View {
190196
Text("Are you sure you want to clear the conversation? This action cannot be undone.")
191197
}
192198
}
199+
.confirmationDialog("Continue Session", isPresented: $showSessionOptions) {
200+
Button("Resume in New Session") {
201+
resumeInNewSession()
202+
}
203+
Button("Follow on Terminal") {
204+
if let sessionId = viewModel.activeSessionId {
205+
launchTerminalWithSession(sessionId)
206+
}
207+
}
208+
Button("Cancel", role: .cancel) { }
209+
} message: {
210+
Text("Choose how to continue this conversation")
211+
}
212+
.alert("Session Resumed!", isPresented: $showResumeAlert) {
213+
// No buttons - will auto-dismiss
214+
} message: {
215+
Text("You can now continue your conversation")
216+
}
193217
.onChange(of: keyboardManager.capturedText, keyboardTextChanged)
194218
.onChange(of: keyboardManager.shouldFocusTextEditor, focusTextEditorChanged)
195219
.onChange(of: keyboardManager.shouldRefreshObservation, refreshObservationChanged)
@@ -305,14 +329,14 @@ public struct ChatScreen: View {
305329

306330
@ViewBuilder
307331
private var copySessionButton: some View {
308-
if let sessionId = viewModel.activeSessionId {
332+
if viewModel.activeSessionId != nil {
309333
Button(action: {
310-
launchTerminalWithSession(sessionId)
334+
showSessionOptions = true
311335
}) {
312-
Image(systemName: isCopied ? "checkmark" : "terminal")
336+
Image(systemName: isCopied ? "checkmark" : "ellipsis.vertical.bubble")
313337
.font(.title2)
314338
}
315-
.help("Continue in Terminal")
339+
.help("Continue Session")
316340
.disabled(isCopied)
317341
}
318342
}
@@ -404,13 +428,50 @@ public struct ChatScreen: View {
404428
NSPasteboard.general.clearContents()
405429
NSPasteboard.general.setString(text, forType: .string)
406430
#endif
407-
431+
408432
isCopied = true
409-
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
433+
Task { @MainActor in
434+
try? await Task.sleep(for: .seconds(2))
410435
isCopied = false
411436
}
412437
}
413438

439+
private func resumeInNewSession() {
440+
guard let sessionId = viewModel.activeSessionId else { return }
441+
442+
// Get the current working directory
443+
let workingDirectory = viewModel.projectPath
444+
445+
// Clear the current conversation UI (removes all visible messages)
446+
viewModel.clearConversation()
447+
448+
// Keep the session ID active so next message continues this conversation
449+
viewModel.sessionManager.selectSession(id: sessionId)
450+
451+
// Set the working directory
452+
if !workingDirectory.isEmpty {
453+
viewModel.claudeClient.configuration.workingDirectory = workingDirectory
454+
viewModel.projectPath = workingDirectory
455+
viewModel.settingsStorage.setProjectPath(workingDirectory)
456+
}
457+
458+
// Show success alert
459+
showResumeAlert = true
460+
461+
// Auto-dismiss alert after 1.5 seconds
462+
Task { @MainActor in
463+
try? await Task.sleep(for: .seconds(1.5))
464+
showResumeAlert = false
465+
}
466+
467+
// Show checkmark on button
468+
isCopied = true
469+
Task { @MainActor in
470+
try? await Task.sleep(for: .seconds(2))
471+
isCopied = false
472+
}
473+
}
474+
414475
private func launchTerminalWithSession(_ sessionId: String) {
415476
// Use the TerminalLauncher helper to launch Terminal
416477
if let error = TerminalLauncher.launchTerminalWithSession(
@@ -423,7 +484,8 @@ public struct ChatScreen: View {
423484
} else {
424485
// Show success indicator
425486
isCopied = true
426-
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
487+
Task { @MainActor in
488+
try? await Task.sleep(for: .seconds(2))
427489
isCopied = false
428490
}
429491
}

Sources/ClaudeCodeCore/ViewModels/StreamProcessor.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,11 @@ final class StreamProcessor {
104104
var subscription: AnyCancellable?
105105

106106
timeoutTask = Task { [weak self] in
107-
try? await Task.sleep(nanoseconds: 25_000_000_000) // 25 seconds
107+
try? await Task.sleep(nanoseconds: 120_000_000_000) // 120 seconds
108108
if !hasReceivedData && !Task.isCancelled {
109109
guard let self = self else { return }
110-
self.logger.error("Stream timeout - no data received within 25 seconds")
111-
110+
self.logger.error("Stream timeout - no data received within 120 seconds")
111+
112112
// Cancel the stream subscription to prevent completion handler from running
113113
subscription?.cancel()
114114
self.cancellables.removeAll()
@@ -125,7 +125,7 @@ final class StreamProcessor {
125125
self.activeContinuation = nil
126126

127127
// Call error handler and resume continuation
128-
onError?(ClaudeCodeError.timeout(25.0))
128+
onError?(ClaudeCodeError.timeout(120.0))
129129
continuation.resume()
130130
}
131131
}

0 commit comments

Comments
 (0)