diff --git a/oAI/ViewModels/ChatViewModel.swift b/oAI/ViewModels/ChatViewModel.swift index 1ec6937..02b09ea 100644 --- a/oAI/ViewModels/ChatViewModel.swift +++ b/oAI/ViewModels/ChatViewModel.swift @@ -360,7 +360,6 @@ Don't narrate future actions ("Let me...") - just use the tools. } func startAutoContinue() { - showSystemMessage("↩ Continuing…") silentContinuePrompt = "Please continue from where you left off." Task { @MainActor in generateAIResponse(to: "", attachments: nil) @@ -1475,9 +1474,8 @@ Don't narrate future actions ("Let me...") - just use the tools. if finalContent.isEmpty { // Some models (observed with Qwen via OpenRouter) stop calling tools // after a long tool-call chain but return no summarizing text at all. - // Surface a placeholder and nudge a follow-up turn instead of a silent blank bubble. + // Silently nudge a follow-up turn instead of showing a placeholder bubble. finishedWithEmptyContent = true - finalContent = "[No response from the model — retrying]" } break } @@ -1566,9 +1564,7 @@ Don't narrate future actions ("Let me...") - just use the tools. // If this was the last iteration, note it if iteration == maxIterations - 1 { hitIterationLimit = true // We're exiting with pending tool calls - finalContent = response.content.isEmpty - ? "[Tool loop reached maximum iterations]" - : response.content + finalContent = response.content } } @@ -1577,41 +1573,57 @@ Don't narrate future actions ("Let me...") - just use the tools. wasCancelled = true } + // If we hit the iteration limit or the model returned no text at all, silently + // nudge a follow-up turn instead of showing a placeholder/blank bubble. + let willAutoContinue = (hitIterationLimit || finishedWithEmptyContent) && !wasCancelled + // Display the final response as an assistant message let responseTime = Date().timeIntervalSince(startTime) - let assistantMessage = Message( - role: .assistant, - content: finalContent, - tokens: totalUsage?.completionTokens, - cost: nil, - timestamp: Date(), - attachments: nil, - responseTime: responseTime, - wasInterrupted: wasCancelled, - modelId: modelId, - generatedImages: finalImages.isEmpty ? nil : finalImages - ) - messages.append(assistantMessage) - - // Calculate cost - if let usage = totalUsage, let model = selectedModel { - let hasPricing = model.pricing.prompt > 0 || model.pricing.completion > 0 - let cost: Double? = hasPricing ? calculateCost(usage: usage, pricing: model.pricing) : nil - if let index = messages.lastIndex(where: { $0.id == assistantMessage.id }) { - messages[index].cost = cost + if willAutoContinue && finalContent.isEmpty { + // Nothing worth showing yet — still record usage/cost for this turn. + if let usage = totalUsage, let model = selectedModel { + let hasPricing = model.pricing.prompt > 0 || model.pricing.completion > 0 + let cost: Double? = hasPricing ? calculateCost(usage: usage, pricing: model.pricing) : nil + sessionStats.addMessage( + inputTokens: usage.promptTokens, + outputTokens: usage.completionTokens, + cost: cost + ) } - sessionStats.addMessage( - inputTokens: usage.promptTokens, - outputTokens: usage.completionTokens, - cost: cost + } else { + let assistantMessage = Message( + role: .assistant, + content: finalContent, + tokens: totalUsage?.completionTokens, + cost: nil, + timestamp: Date(), + attachments: nil, + responseTime: responseTime, + wasInterrupted: wasCancelled, + modelId: modelId, + generatedImages: finalImages.isEmpty ? nil : finalImages ) + messages.append(assistantMessage) + + // Calculate cost + if let usage = totalUsage, let model = selectedModel { + let hasPricing = model.pricing.prompt > 0 || model.pricing.completion > 0 + let cost: Double? = hasPricing ? calculateCost(usage: usage, pricing: model.pricing) : nil + if let index = messages.lastIndex(where: { $0.id == assistantMessage.id }) { + messages[index].cost = cost + } + sessionStats.addMessage( + inputTokens: usage.promptTokens, + outputTokens: usage.completionTokens, + cost: cost + ) + } } isGenerating = false streamingTask = nil - // If we hit the iteration limit, or the model returned no text at all, nudge a follow-up turn - if (hitIterationLimit || finishedWithEmptyContent) && !wasCancelled { + if willAutoContinue { startAutoContinue() }