diff --git a/packages/types/src/message.ts b/packages/types/src/message.ts index 0c87655fc0c..22082939427 100644 --- a/packages/types/src/message.ts +++ b/packages/types/src/message.ts @@ -24,6 +24,7 @@ import { z } from "zod" * - `browser_action_launch`: Permission to open or interact with a browser * - `use_mcp_server`: Permission to use Model Context Protocol (MCP) server functionality * - `auto_approval_max_req_reached`: Auto-approval limit has been reached, manual approval required + * - `temperature_tool_error`: Tool failed due to high temperature setting, asking user to reduce temperature and retry */ export const clineAsks = [ "followup", @@ -38,6 +39,7 @@ export const clineAsks = [ "browser_action_launch", "use_mcp_server", "auto_approval_max_req_reached", + "temperature_tool_error", ] as const export const clineAskSchema = z.enum(clineAsks) diff --git a/src/core/tools/applyDiffTool.ts b/src/core/tools/applyDiffTool.ts index ad4bb0590f8..e4c96552179 100644 --- a/src/core/tools/applyDiffTool.ts +++ b/src/core/tools/applyDiffTool.ts @@ -12,6 +12,7 @@ import { formatResponse } from "../prompts/responses" import { fileExistsAtPath } from "../../utils/fs" import { RecordSource } from "../context-tracking/FileContextTrackerTypes" import { unescapeHtmlEntities } from "../../utils/text-normalization" +import { isTemperatureRelatedError, getTemperatureErrorMessage } from "./utils/temperatureErrorDetection" export async function applyDiffToolLegacy( cline: Task, @@ -133,6 +134,25 @@ export async function applyDiffToolLegacy( await cline.say("diff_error", formattedError) } + // Check if this is a temperature-related error + if (isTemperatureRelatedError("apply_diff", formattedError, cline)) { + const currentTemperature = cline.apiConfiguration?.modelTemperature ?? 0.0 + const temperatureMessage = getTemperatureErrorMessage(currentTemperature) + + // Ask user if they want to reduce temperature and retry + const askMessage = JSON.stringify({ + tool: "apply_diff", + path: getReadablePath(cline.cwd, relPath), + error: formattedError, + temperatureMessage, + currentTemperature, + }) + + await cline.ask("temperature_tool_error", askMessage) + cline.recordToolError("apply_diff", formattedError) + return + } + cline.recordToolError("apply_diff", formattedError) pushToolResult(formattedError) diff --git a/src/core/tools/utils/__tests__/temperatureErrorDetection.test.ts b/src/core/tools/utils/__tests__/temperatureErrorDetection.test.ts new file mode 100644 index 00000000000..4d4a9d5f5a7 --- /dev/null +++ b/src/core/tools/utils/__tests__/temperatureErrorDetection.test.ts @@ -0,0 +1,155 @@ +import { describe, it, expect, vi, beforeEach } from "vitest" +import { isTemperatureRelatedError } from "../temperatureErrorDetection" +import type { Task } from "../../../task/Task" +import type { ProviderSettings } from "@roo-code/types" + +describe("temperatureErrorDetection", () => { + let mockTask: Partial + let mockApiConfiguration: ProviderSettings + + beforeEach(() => { + mockApiConfiguration = { + apiProvider: "anthropic", + apiModelId: "claude-3-5-sonnet-20241022", + modelTemperature: 0.7, + } as ProviderSettings + + mockTask = { + apiConfiguration: mockApiConfiguration, + } + }) + + describe("isTemperatureRelatedError", () => { + it("should return false for non-temperature related errors", () => { + const result = isTemperatureRelatedError("write_to_file", "Permission denied", mockTask as Task) + expect(result).toBe(false) + }) + + it("should return false when temperature is already low (0.2 or below)", () => { + mockApiConfiguration.modelTemperature = 0.2 + const result = isTemperatureRelatedError( + "write_to_file", + "Error: content appears to be truncated", + mockTask as Task, + ) + expect(result).toBe(false) + }) + + it("should return false when temperature is undefined (using default)", () => { + mockApiConfiguration.modelTemperature = undefined + const result = isTemperatureRelatedError( + "write_to_file", + "Error: content appears to be truncated", + mockTask as Task, + ) + expect(result).toBe(false) + }) + + it('should return true for "content appears to be truncated" error with high temperature', () => { + const result = isTemperatureRelatedError( + "write_to_file", + "Error: content appears to be truncated", + mockTask as Task, + ) + expect(result).toBe(true) + }) + + it('should return true for "rest of code unchanged" error with high temperature', () => { + const result = isTemperatureRelatedError( + "apply_diff", + 'Error: Found "// rest of code unchanged" in the content', + mockTask as Task, + ) + expect(result).toBe(true) + }) + + it('should return true for "previous code" error with high temperature', () => { + const result = isTemperatureRelatedError( + "write_to_file", + 'Error: Content contains "// ... previous code ..." placeholder', + mockTask as Task, + ) + expect(result).toBe(true) + }) + + it('should return true for "existing code" error with high temperature', () => { + const result = isTemperatureRelatedError( + "apply_diff", + 'Error: Found "// ... existing code ..." in the diff', + mockTask as Task, + ) + expect(result).toBe(true) + }) + + it('should return true for "keep the rest" error with high temperature', () => { + const result = isTemperatureRelatedError( + "write_to_file", + 'Error: Content includes "// keep the rest of the file" comment', + mockTask as Task, + ) + expect(result).toBe(true) + }) + + it('should return true for "remaining code" error with high temperature', () => { + const result = isTemperatureRelatedError( + "apply_diff", + 'Error: Found "// ... remaining code ..." in the content', + mockTask as Task, + ) + expect(result).toBe(true) + }) + + it("should handle Error objects as well as strings", () => { + const error = new Error("content appears to be truncated") + const result = isTemperatureRelatedError("write_to_file", error, mockTask as Task) + expect(result).toBe(true) + }) + + it("should be case insensitive when checking error patterns", () => { + const result = isTemperatureRelatedError( + "write_to_file", + "ERROR: CONTENT APPEARS TO BE TRUNCATED", + mockTask as Task, + ) + expect(result).toBe(true) + }) + + it("should return false when temperature is 0", () => { + mockApiConfiguration.modelTemperature = 0 + const result = isTemperatureRelatedError( + "write_to_file", + "Error: content appears to be truncated", + mockTask as Task, + ) + expect(result).toBe(false) + }) + + it("should return true when temperature is exactly 0.3", () => { + mockApiConfiguration.modelTemperature = 0.3 + const result = isTemperatureRelatedError( + "write_to_file", + "Error: content appears to be truncated", + mockTask as Task, + ) + expect(result).toBe(true) + }) + + it("should work with apply_diff tool", () => { + const result = isTemperatureRelatedError( + "apply_diff", + "Error: content appears to be truncated", + mockTask as Task, + ) + expect(result).toBe(true) + }) + + it("should return false for other tools even with temperature error patterns", () => { + const result = isTemperatureRelatedError( + "read_file", + "Error: content appears to be truncated", + mockTask as Task, + ) + expect(result).toBe(false) + }) + }) +}) diff --git a/src/core/tools/utils/temperatureErrorDetection.ts b/src/core/tools/utils/temperatureErrorDetection.ts new file mode 100644 index 00000000000..88df143a792 --- /dev/null +++ b/src/core/tools/utils/temperatureErrorDetection.ts @@ -0,0 +1,93 @@ +/** + * Utility functions for detecting temperature-related tool errors + */ + +import { Task } from "../../task/Task" + +/** + * Error patterns that indicate temperature-related issues + */ +const TEMPERATURE_ERROR_PATTERNS = [ + // Direct truncation indicators + /content appears to be truncated/i, + /found comments indicating omitted code/i, + /rest of code unchanged/i, + /previous code/i, + /code omitted/i, + /truncated after \d+ lines/i, + /keep the rest/i, + + // Common AI placeholder patterns when temperature is high + /\/\/\s*\.\.\./, + /\/\*\s*\.\.\.\s*\*\//, + /\[\s*\.\.\.\s*\]/, + /\{\s*\.\.\.\s*\}/, + + // Incomplete content indicators + /incomplete file content/i, + /partial content/i, + /content was cut off/i, +] + +/** + * Tool names that commonly experience temperature-related failures + */ +const TEMPERATURE_SENSITIVE_TOOLS = ["write_to_file", "apply_diff"] + +/** + * Checks if an error is likely caused by high temperature settings + * @param toolName The name of the tool that failed + * @param error The error message or Error object + * @param task The current task instance to check temperature settings + * @returns True if the error appears to be temperature-related + */ +export function isTemperatureRelatedError(toolName: string, error: string | Error, task: Task): boolean { + // Only check for temperature errors on specific tools + if (!TEMPERATURE_SENSITIVE_TOOLS.includes(toolName)) { + return false + } + + // Get current temperature from API configuration + const currentTemperature = task.apiConfiguration?.modelTemperature ?? 0.0 + + // Only consider it a temperature issue if temperature is above 0.2 + if (currentTemperature <= 0.2) { + return false + } + + // Check if the user has customized the temperature (not using default) + // Most providers default to 0.0 or 1.0, so anything else is likely custom + const isCustomTemperature = currentTemperature !== 0.0 && currentTemperature !== 1.0 + + if (!isCustomTemperature) { + return false + } + + // Convert error to string for pattern matching + const errorMessage = typeof error === "string" ? error : error.message || "" + + // Check if error matches any temperature-related patterns + return TEMPERATURE_ERROR_PATTERNS.some((pattern) => pattern.test(errorMessage)) +} + +/** + * Gets a user-friendly message explaining the temperature issue + * @param currentTemperature The current temperature setting + * @returns A message explaining the issue + */ +export function getTemperatureErrorMessage(currentTemperature: number): string { + return ( + `It looks like the tool failed due to your current temperature setting (${currentTemperature.toFixed(1)}). ` + + `Higher temperature values can cause the AI to generate incomplete or malformed outputs. ` + + `Reducing the temperature to 0.2 often resolves these issues.` + ) +} + +/** + * Checks if the temperature can be reduced further + * @param currentTemperature The current temperature setting + * @returns True if temperature can be reduced to 0.2 + */ +export function canReduceTemperature(currentTemperature: number): boolean { + return currentTemperature > 0.2 +} diff --git a/src/core/tools/writeToFileTool.ts b/src/core/tools/writeToFileTool.ts index fd9d158f3f7..7ca8e3af064 100644 --- a/src/core/tools/writeToFileTool.ts +++ b/src/core/tools/writeToFileTool.ts @@ -14,6 +14,7 @@ import { isPathOutsideWorkspace } from "../../utils/pathUtils" import { detectCodeOmission } from "../../integrations/editor/detect-omission" import { unescapeHtmlEntities } from "../../utils/text-normalization" import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types" +import { isTemperatureRelatedError, getTemperatureErrorMessage } from "./utils/temperatureErrorDetection" export async function writeToFileTool( cline: Task, @@ -172,13 +173,30 @@ export async function writeToFileTool( if (cline.diffStrategy) { await cline.diffViewProvider.revertChanges() - pushToolResult( - formatResponse.toolError( - `Content appears to be truncated (file has ${ - newContent.split("\n").length - } lines but was predicted to have ${predictedLineCount} lines), and found comments indicating omitted code (e.g., '// rest of code unchanged', '/* previous code */'). Please provide the complete file content without any omissions if possible, or otherwise use the 'apply_diff' tool to apply the diff to the original file.`, - ), - ) + const errorMessage = `Content appears to be truncated (file has ${ + newContent.split("\n").length + } lines but was predicted to have ${predictedLineCount} lines), and found comments indicating omitted code (e.g., '// rest of code unchanged', '/* previous code */'). Please provide the complete file content without any omissions if possible, or otherwise use the 'apply_diff' tool to apply the diff to the original file.` + + // Check if this is a temperature-related error + if (isTemperatureRelatedError("write_to_file", errorMessage, cline)) { + const currentTemperature = cline.apiConfiguration?.modelTemperature ?? 0.0 + const temperatureMessage = getTemperatureErrorMessage(currentTemperature) + + // Ask user if they want to reduce temperature and retry + const askMessage = JSON.stringify({ + tool: "write_to_file", + path: getReadablePath(cline.cwd, relPath), + error: errorMessage, + temperatureMessage, + currentTemperature, + }) + + await cline.ask("temperature_tool_error", askMessage) + cline.recordToolError("write_to_file", errorMessage) + return + } + + pushToolResult(formatResponse.toolError(errorMessage)) return } else { vscode.window diff --git a/src/core/webview/__tests__/temperatureErrorRetry.test.ts b/src/core/webview/__tests__/temperatureErrorRetry.test.ts new file mode 100644 index 00000000000..0ab6e211052 --- /dev/null +++ b/src/core/webview/__tests__/temperatureErrorRetry.test.ts @@ -0,0 +1,359 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest" +import { ClineProvider } from "../ClineProvider" +import { webviewMessageHandler } from "../webviewMessageHandler" +import { Task } from "../../task/Task" +import { ProviderSettings } from "@roo-code/types" +import { ClineAskResponse, WebviewMessage } from "../../../shared/WebviewMessage" +import * as vscode from "vscode" + +// Mock vscode +vi.mock("vscode", () => ({ + window: { + showErrorMessage: vi.fn(), + showInformationMessage: vi.fn(), + }, + workspace: { + getConfiguration: vi.fn().mockReturnValue({ + get: vi.fn(), + update: vi.fn(), + }), + }, + env: { + uriScheme: "vscode", + language: "en", + }, + ExtensionContext: vi.fn(), + OutputChannel: vi.fn(), + Uri: { + parse: vi.fn(), + }, +})) + +// Mock other dependencies +vi.mock("../../api", () => ({ + buildApiHandler: vi.fn().mockReturnValue({ + getModel: vi.fn().mockReturnValue({ id: "test-model" }), + }), +})) + +vi.mock("../../utils/path", () => ({ + getWorkspacePath: vi.fn().mockReturnValue("/test/workspace"), +})) + +vi.mock("../../i18n", () => ({ + t: vi.fn((key: string) => key), + changeLanguage: vi.fn(), +})) + +vi.mock("../../services/marketplace", () => ({ + MarketplaceManager: vi.fn().mockImplementation(() => ({ + getCurrentItems: vi.fn().mockResolvedValue([]), + getInstallationMetadata: vi.fn().mockResolvedValue({ project: {}, global: {} }), + })), +})) + +describe("Temperature Error Retry Integration", () => { + let mockProvider: any + let mockTask: any + let mockProviderSettingsManager: any + let mockContextProxy: any + + beforeEach(() => { + // Reset all mocks + vi.clearAllMocks() + + // Create mock task + mockTask = { + taskId: "test-task-123", + instanceId: "instance-123", + apiConfiguration: { + apiProvider: "anthropic", + modelTemperature: 0.7, + }, + clineMessages: [], + apiConversationHistory: [], + overwriteClineMessages: vi.fn(), + overwriteApiConversationHistory: vi.fn(), + recursivelyMakeClineRequests: vi.fn(), + handleWebviewAskResponse: vi.fn(), + } + + // Create mock provider settings manager + mockProviderSettingsManager = { + saveConfig: vi.fn().mockResolvedValue(undefined), + listConfig: vi.fn().mockResolvedValue([{ id: "config-1", name: "default", apiProvider: "anthropic" }]), + } + + // Create mock context proxy + mockContextProxy = { + getValue: vi.fn((key: string) => { + const values: Record = { + currentApiConfigName: "default", + listApiConfigMeta: [{ id: "config-1", name: "default", apiProvider: "anthropic" }], + } + return values[key] + }), + setValue: vi.fn(), + getValues: vi.fn().mockReturnValue({ + currentApiConfigName: "default", + listApiConfigMeta: [{ id: "config-1", name: "default", apiProvider: "anthropic" }], + }), + } + + // Create mock provider + mockProvider = { + getCurrentCline: vi.fn().mockReturnValue(mockTask), + getState: vi.fn().mockResolvedValue({ + apiConfiguration: { + apiProvider: "anthropic", + modelTemperature: 0.7, + }, + currentApiConfigName: "default", + }), + upsertProviderProfile: vi.fn().mockImplementation(async () => { + // Simulate the internal call to postStateToWebview + await mockProvider.postStateToWebview() + return "config-1" + }), + postStateToWebview: vi.fn().mockResolvedValue(undefined), + postMessageToWebview: vi.fn().mockResolvedValue(undefined), + providerSettingsManager: mockProviderSettingsManager, + contextProxy: mockContextProxy, + log: vi.fn(), + } + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + it("should handle temperature error retry flow correctly", async () => { + // Setup: Add a temperature error message to the task + mockTask.clineMessages = [ + { + ts: Date.now() - 2000, + type: "say", + say: "assistant", + text: "I'll help you with that.", + }, + { + ts: Date.now() - 1000, + type: "ask", + ask: "temperature_tool_error", + text: "It looks like the tool failed due to your current temperature setting (0.7).", + }, + ] + + // Setup: Add conversation history with user message + mockTask.apiConversationHistory = [ + { + role: "user", + content: "Please write a function to calculate fibonacci numbers", + }, + { + role: "assistant", + content: [ + { + type: "text", + text: "I'll write a fibonacci function for you.", + }, + { + type: "tool_use", + id: "tool-123", + name: "write_to_file", + input: { + path: "fibonacci.js", + content: "function fibonacci(n) {\n // ... rest of code unchanged\n}", + }, + }, + ], + }, + ] + + // Simulate user clicking "Reduce Temperature to 0.2 & Retry" + const message: WebviewMessage = { + type: "askResponse", + askResponse: "yesButtonClicked" as ClineAskResponse, + } + + // Execute the handler + await webviewMessageHandler(mockProvider, message) + + // Verify temperature was updated + expect(mockProvider.upsertProviderProfile).toHaveBeenCalledWith( + "default", + expect.objectContaining({ + apiProvider: "anthropic", + modelTemperature: 0.2, + }), + ) + + // Verify messages were removed + expect(mockTask.overwriteClineMessages).toHaveBeenCalledWith([ + { + ts: expect.any(Number), + type: "say", + say: "assistant", + text: "I'll help you with that.", + }, + ]) + + // Verify API conversation history was trimmed + expect(mockTask.overwriteApiConversationHistory).toHaveBeenCalledWith([ + { + role: "user", + content: "Please write a function to calculate fibonacci numbers", + }, + ]) + + // Verify the request was retried + expect(mockTask.recursivelyMakeClineRequests).toHaveBeenCalledWith([ + { + type: "text", + text: "Please write a function to calculate fibonacci numbers", + }, + ]) + + // Verify state was posted to webview + expect(mockProvider.postStateToWebview).toHaveBeenCalled() + }) + + it("should handle cancel button click without making changes", async () => { + // Setup: Add a temperature error message + mockTask.clineMessages = [ + { + ts: Date.now(), + type: "ask", + ask: "temperature_tool_error", + text: "Temperature error detected", + }, + ] + + // Simulate user clicking cancel + const message: WebviewMessage = { + type: "askResponse", + askResponse: "noButtonClicked" as ClineAskResponse, + } + + // Execute the handler + await webviewMessageHandler(mockProvider, message) + + // Verify no temperature update was made + expect(mockProvider.upsertProviderProfile).not.toHaveBeenCalled() + + // Verify no messages were removed + expect(mockTask.overwriteClineMessages).not.toHaveBeenCalled() + expect(mockTask.overwriteApiConversationHistory).not.toHaveBeenCalled() + + // Verify no retry was attempted + expect(mockTask.recursivelyMakeClineRequests).not.toHaveBeenCalled() + }) + + it("should handle complex content blocks correctly", async () => { + // Setup: Add conversation history with complex content blocks + mockTask.apiConversationHistory = [ + { + role: "user", + content: [ + { + type: "text", + text: "Here's an image to analyze:", + }, + { + type: "image", + source: { + type: "base64", + media_type: "image/png", + data: "base64data...", + }, + }, + { + type: "text", + text: "Please describe what you see.", + }, + ], + }, + { + role: "assistant", + content: [ + { + type: "tool_use", + id: "tool-456", + name: "write_to_file", + input: { + path: "description.txt", + content: "The image shows... [rest of content]", + }, + }, + ], + }, + ] + + mockTask.clineMessages = [ + { + ts: Date.now(), + type: "ask", + ask: "temperature_tool_error", + text: "Temperature error", + }, + ] + + // Simulate retry + const message: WebviewMessage = { + type: "askResponse", + askResponse: "yesButtonClicked" as ClineAskResponse, + } + + await webviewMessageHandler(mockProvider, message) + + // Verify the content blocks were properly converted + expect(mockTask.recursivelyMakeClineRequests).toHaveBeenCalledWith([ + { + type: "text", + text: "Here's an image to analyze:", + }, + { + type: "image", + source: { + type: "base64", + media_type: "image/png", + data: "base64data...", + }, + }, + { + type: "text", + text: "Please describe what you see.", + }, + ]) + }) + + it("should not trigger temperature retry for non-temperature errors", async () => { + // Setup: Add a different type of error message + mockTask.clineMessages = [ + { + ts: Date.now(), + type: "ask", + ask: "tool", + text: "Regular tool error", + }, + ] + + // Simulate user response + const message: WebviewMessage = { + type: "askResponse", + askResponse: "yesButtonClicked" as ClineAskResponse, + } + + // Mock the regular ask response handler + mockTask.handleWebviewAskResponse = vi.fn() + mockProvider.getCurrentCline.mockReturnValue(mockTask) + + await webviewMessageHandler(mockProvider, message) + + // Verify temperature update was NOT called + expect(mockProvider.upsertProviderProfile).not.toHaveBeenCalled() + + // Verify regular handler was called instead + expect(mockTask.handleWebviewAskResponse).toHaveBeenCalledWith("yesButtonClicked", undefined, undefined) + }) +}) diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index c739c2ade8d..99ca8590be4 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -341,6 +341,61 @@ export const webviewMessageHandler = async ( await provider.postStateToWebview() break case "askResponse": + // Handle temperature error retry with temperature reduction + if (message.askResponse === "yesButtonClicked") { + const currentCline = provider.getCurrentCline() + if (currentCline) { + const lastMessage = currentCline.clineMessages.at(-1) + if (lastMessage?.ask === "temperature_tool_error") { + // User clicked "Reduce Temperature to 0.2 & Retry" + // 1. Update temperature to 0.2 for current API profile + const { apiConfiguration, currentApiConfigName } = await provider.getState() + if (apiConfiguration) { + const updatedConfig = { + ...apiConfiguration, + modelTemperature: 0.2, + } + + // Update the current configuration using the provider's method + await provider.upsertProviderProfile(currentApiConfigName, updatedConfig) + } + + // 2. Remove the temperature error message and the failed tool message before it + const messageIndex = currentCline.clineMessages.length - 1 + const apiConversationHistoryIndex = currentCline.apiConversationHistory.length - 1 + + // Remove the temperature_tool_error ask message + await currentCline.overwriteClineMessages(currentCline.clineMessages.slice(0, messageIndex)) + + // Remove the last assistant message from API history (the one with the failed tool) + if (apiConversationHistoryIndex >= 0) { + await currentCline.overwriteApiConversationHistory( + currentCline.apiConversationHistory.slice(0, apiConversationHistoryIndex), + ) + } + + // 3. Re-send the last user message to retry with lower temperature + const lastUserMessage = currentCline.apiConversationHistory + .slice() + .reverse() + .find((msg) => msg.role === "user") + + if (lastUserMessage && lastUserMessage.content) { + // Convert content to ContentBlockParam[] format + const contentBlocks = + typeof lastUserMessage.content === "string" + ? [{ type: "text" as const, text: lastUserMessage.content }] + : lastUserMessage.content + + // Re-initiate the task loop with the last user content + await currentCline.recursivelyMakeClineRequests(contentBlocks) + } + + break + } + } + } + provider.getCurrentCline()?.handleWebviewAskResponse(message.askResponse!, message.text, message.images) break case "autoCondenseContext": diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index efd2db856c0..2029e81ac51 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -384,6 +384,16 @@ const ChatViewComponent: React.ForwardRefRenderFunction 0)) { vscode.postMessage({ @@ -669,6 +680,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction 0)) { vscode.postMessage({ @@ -1796,7 +1808,14 @@ const ChatViewComponent: React.ForwardRefRenderFunction