mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
working functionality
This commit is contained in:
parent
d926a771bc
commit
24a8c10048
11 changed files with 428 additions and 83 deletions
|
|
@ -110,6 +110,10 @@ export const globalSettingsSchema = z.object({
|
|||
hasOpenedModeSelector: z.boolean().optional(),
|
||||
lastModeExportPath: z.string().optional(),
|
||||
lastModeImportPath: z.string().optional(),
|
||||
|
||||
// Message modification confirmation preferences
|
||||
skipEditMessageConfirmation: z.boolean().optional(),
|
||||
skipDeleteMessageConfirmation: z.boolean().optional(),
|
||||
})
|
||||
|
||||
export type GlobalSettings = z.infer<typeof globalSettingsSchema>
|
||||
|
|
|
|||
|
|
@ -28,9 +28,13 @@ const mockClineProvider = {
|
|||
globalStorageUri: { fsPath: "/mock/global/storage" },
|
||||
},
|
||||
setValue: vi.fn(),
|
||||
getValue: vi.fn(),
|
||||
},
|
||||
log: vi.fn(),
|
||||
postStateToWebview: vi.fn(),
|
||||
getCurrentCline: vi.fn(),
|
||||
getTaskWithId: vi.fn(),
|
||||
initClineWithHistoryItem: vi.fn(),
|
||||
} as unknown as ClineProvider
|
||||
|
||||
import { t } from "../../../i18n"
|
||||
|
|
@ -482,3 +486,151 @@ describe("webviewMessageHandler - deleteCustomMode", () => {
|
|||
expect(mockClineProvider.postMessageToWebview).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe("webviewMessageHandler - message dialog preferences", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
// Mock a current Cline instance
|
||||
vi.mocked(mockClineProvider.getCurrentCline).mockReturnValue({
|
||||
taskId: "test-task-id",
|
||||
apiConversationHistory: [],
|
||||
clineMessages: [],
|
||||
} as any)
|
||||
// Reset getValue mock
|
||||
vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(false)
|
||||
})
|
||||
|
||||
describe("skipEditMessageConfirmation", () => {
|
||||
it("should save edit message confirmation preference when set to true", async () => {
|
||||
await webviewMessageHandler(mockClineProvider, {
|
||||
type: "skipEditMessageConfirmation",
|
||||
bool: true,
|
||||
})
|
||||
|
||||
expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("skipEditMessageConfirmation", true)
|
||||
expect(mockClineProvider.postStateToWebview).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should save edit message confirmation preference when set to false", async () => {
|
||||
await webviewMessageHandler(mockClineProvider, {
|
||||
type: "skipEditMessageConfirmation",
|
||||
bool: false,
|
||||
})
|
||||
|
||||
expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("skipEditMessageConfirmation", false)
|
||||
expect(mockClineProvider.postStateToWebview).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should default to false when bool is not provided", async () => {
|
||||
await webviewMessageHandler(mockClineProvider, {
|
||||
type: "skipEditMessageConfirmation",
|
||||
})
|
||||
|
||||
expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("skipEditMessageConfirmation", false)
|
||||
expect(mockClineProvider.postStateToWebview).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe("skipDeleteMessageConfirmation", () => {
|
||||
it("should save delete message confirmation preference when set to true", async () => {
|
||||
await webviewMessageHandler(mockClineProvider, {
|
||||
type: "skipDeleteMessageConfirmation",
|
||||
bool: true,
|
||||
})
|
||||
|
||||
expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("skipDeleteMessageConfirmation", true)
|
||||
expect(mockClineProvider.postStateToWebview).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should save delete message confirmation preference when set to false", async () => {
|
||||
await webviewMessageHandler(mockClineProvider, {
|
||||
type: "skipDeleteMessageConfirmation",
|
||||
bool: false,
|
||||
})
|
||||
|
||||
expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("skipDeleteMessageConfirmation", false)
|
||||
expect(mockClineProvider.postStateToWebview).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should default to false when bool is not provided", async () => {
|
||||
await webviewMessageHandler(mockClineProvider, {
|
||||
type: "skipDeleteMessageConfirmation",
|
||||
})
|
||||
|
||||
expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("skipDeleteMessageConfirmation", false)
|
||||
expect(mockClineProvider.postStateToWebview).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe("deleteMessage", () => {
|
||||
it("should show dialog when skipDeleteMessageConfirmation is false", async () => {
|
||||
vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(false)
|
||||
|
||||
await webviewMessageHandler(mockClineProvider, {
|
||||
type: "deleteMessage",
|
||||
messageTs: 123456789,
|
||||
})
|
||||
|
||||
expect(mockClineProvider.postMessageToWebview).toHaveBeenCalledWith({
|
||||
type: "showDeleteMessageDialog",
|
||||
messageTs: 123456789,
|
||||
})
|
||||
})
|
||||
|
||||
it("should skip dialog and directly delete when skipDeleteMessageConfirmation is true", async () => {
|
||||
vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(true)
|
||||
|
||||
// Mock the necessary functions for deletion
|
||||
vi.mocked(mockClineProvider.getTaskWithId).mockResolvedValue({
|
||||
historyItem: { id: "test-history-id" },
|
||||
} as any)
|
||||
|
||||
await webviewMessageHandler(mockClineProvider, {
|
||||
type: "deleteMessage",
|
||||
messageTs: 123456789,
|
||||
})
|
||||
|
||||
// Should not show dialog
|
||||
expect(mockClineProvider.postMessageToWebview).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: "showDeleteMessageDialog",
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("submitEditedMessage", () => {
|
||||
it("should show dialog when skipEditMessageConfirmation is false", async () => {
|
||||
vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(false)
|
||||
|
||||
await webviewMessageHandler(mockClineProvider, {
|
||||
type: "submitEditedMessage",
|
||||
messageTs: 123456789,
|
||||
text: "edited content",
|
||||
})
|
||||
|
||||
expect(mockClineProvider.postMessageToWebview).toHaveBeenCalledWith({
|
||||
type: "showEditMessageDialog",
|
||||
messageTs: 123456789,
|
||||
text: "edited content",
|
||||
})
|
||||
})
|
||||
|
||||
it("should skip dialog and directly edit when skipEditMessageConfirmation is true", async () => {
|
||||
vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(true)
|
||||
|
||||
await webviewMessageHandler(mockClineProvider, {
|
||||
type: "submitEditedMessage",
|
||||
messageTs: 123456789,
|
||||
text: "edited content",
|
||||
})
|
||||
|
||||
// Should not show dialog
|
||||
expect(mockClineProvider.postMessageToWebview).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: "showEditMessageDialog",
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -77,55 +77,6 @@ export const webviewMessageHandler = async (
|
|||
return { messageIndex, apiConversationHistoryIndex }
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes just the target message, preserving messages after the next user message
|
||||
*/
|
||||
const removeMessagesJustThis = async (
|
||||
currentCline: any,
|
||||
messageIndex: number,
|
||||
apiConversationHistoryIndex: number,
|
||||
) => {
|
||||
// Find the next user message first
|
||||
const nextUserMessage = currentCline.clineMessages
|
||||
.slice(messageIndex + 1)
|
||||
.find((msg: ClineMessage) => msg.type === "say" && msg.say === "user_feedback")
|
||||
|
||||
// Handle UI messages
|
||||
if (nextUserMessage) {
|
||||
// Find absolute index of next user message
|
||||
const nextUserMessageIndex = currentCline.clineMessages.findIndex(
|
||||
(msg: ClineMessage) => msg === nextUserMessage,
|
||||
)
|
||||
|
||||
// Keep messages before current message and after next user message
|
||||
await currentCline.overwriteClineMessages([
|
||||
...currentCline.clineMessages.slice(0, messageIndex),
|
||||
...currentCline.clineMessages.slice(nextUserMessageIndex),
|
||||
])
|
||||
} else {
|
||||
// If no next user message, keep only messages before current message
|
||||
await currentCline.overwriteClineMessages(currentCline.clineMessages.slice(0, messageIndex))
|
||||
}
|
||||
|
||||
// Handle API messages
|
||||
if (apiConversationHistoryIndex !== -1) {
|
||||
if (nextUserMessage && nextUserMessage.ts) {
|
||||
// Keep messages before current API message and after next user message
|
||||
await currentCline.overwriteApiConversationHistory([
|
||||
...currentCline.apiConversationHistory.slice(0, apiConversationHistoryIndex),
|
||||
...currentCline.apiConversationHistory.filter(
|
||||
(msg: ApiMessage) => msg.ts && msg.ts >= nextUserMessage.ts,
|
||||
),
|
||||
])
|
||||
} else {
|
||||
// If no next user message, keep only messages before current API message
|
||||
await currentCline.overwriteApiConversationHistory(
|
||||
currentCline.apiConversationHistory.slice(0, apiConversationHistoryIndex),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the target message and all subsequent messages
|
||||
*/
|
||||
|
|
@ -148,19 +99,27 @@ export const webviewMessageHandler = async (
|
|||
* Handles message deletion operations with user confirmation
|
||||
*/
|
||||
const handleDeleteOperation = async (messageTs: number): Promise<void> => {
|
||||
const options = [
|
||||
t("common:confirmation.delete_just_this_message"),
|
||||
t("common:confirmation.delete_this_and_subsequent"),
|
||||
]
|
||||
// Check if user has opted to skip the confirmation
|
||||
const skipDeleteMessageConfirmation = getGlobalState("skipDeleteMessageConfirmation")
|
||||
|
||||
const answer = await vscode.window.showInformationMessage(
|
||||
t("common:confirmation.delete_message"),
|
||||
{ modal: true },
|
||||
...options,
|
||||
)
|
||||
if (skipDeleteMessageConfirmation) {
|
||||
// Directly handle the deletion without showing dialog
|
||||
await handleDeleteMessageConfirm(messageTs)
|
||||
} else {
|
||||
// Send message to webview to show delete confirmation dialog
|
||||
await provider.postMessageToWebview({
|
||||
type: "showDeleteMessageDialog",
|
||||
messageTs,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Only proceed if user selected one of the options and we have a current cline
|
||||
if (answer && options.includes(answer) && provider.getCurrentCline()) {
|
||||
/**
|
||||
* Handles confirmed message deletion from webview dialog
|
||||
*/
|
||||
const handleDeleteMessageConfirm = async (messageTs: number): Promise<void> => {
|
||||
// Only proceed if we have a current cline
|
||||
if (provider.getCurrentCline()) {
|
||||
const currentCline = provider.getCurrentCline()!
|
||||
const { messageIndex, apiConversationHistoryIndex } = findMessageIndices(messageTs, currentCline)
|
||||
|
||||
|
|
@ -168,14 +127,8 @@ export const webviewMessageHandler = async (
|
|||
try {
|
||||
const { historyItem } = await provider.getTaskWithId(currentCline.taskId)
|
||||
|
||||
// Check which option the user selected
|
||||
if (answer === options[0]) {
|
||||
// Delete just this message
|
||||
await removeMessagesJustThis(currentCline, messageIndex, apiConversationHistoryIndex)
|
||||
} else if (answer === options[1]) {
|
||||
// Delete this message and all subsequent
|
||||
await removeMessagesThisAndSubsequent(currentCline, messageIndex, apiConversationHistoryIndex)
|
||||
}
|
||||
// Delete this message and all subsequent messages
|
||||
await removeMessagesThisAndSubsequent(currentCline, messageIndex, apiConversationHistoryIndex)
|
||||
|
||||
// Initialize with history item after deletion
|
||||
await provider.initClineWithHistoryItem(historyItem)
|
||||
|
|
@ -193,14 +146,28 @@ export const webviewMessageHandler = async (
|
|||
* Handles message editing operations with user confirmation
|
||||
*/
|
||||
const handleEditOperation = async (messageTs: number, editedContent: string): Promise<void> => {
|
||||
const answer = await vscode.window.showWarningMessage(
|
||||
t("common:confirmation.edit_warning"),
|
||||
{ modal: true },
|
||||
t("common:confirmation.proceed"),
|
||||
)
|
||||
// Check if user has opted to skip the confirmation
|
||||
const skipEditMessageConfirmation = getGlobalState("skipEditMessageConfirmation")
|
||||
|
||||
// Only proceed if user selected "Proceed" and we have a current cline
|
||||
if (answer === t("common:confirmation.proceed") && provider.getCurrentCline()) {
|
||||
if (skipEditMessageConfirmation) {
|
||||
// Directly handle the edit without showing dialog
|
||||
await handleEditMessageConfirm(messageTs, editedContent)
|
||||
} else {
|
||||
// Send message to webview to show edit confirmation dialog
|
||||
await provider.postMessageToWebview({
|
||||
type: "showEditMessageDialog",
|
||||
messageTs,
|
||||
text: editedContent,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles confirmed message editing from webview dialog
|
||||
*/
|
||||
const handleEditMessageConfirm = async (messageTs: number, editedContent: string): Promise<void> => {
|
||||
// Only proceed if we have a current cline
|
||||
if (provider.getCurrentCline()) {
|
||||
const currentCline = provider.getCurrentCline()!
|
||||
|
||||
// Use findMessageIndices to find messages based on timestamp
|
||||
|
|
@ -1249,6 +1216,14 @@ export const webviewMessageHandler = async (
|
|||
await updateGlobalState("historyPreviewCollapsed", message.bool ?? false)
|
||||
// No need to call postStateToWebview here as the UI already updated optimistically
|
||||
break
|
||||
case "skipEditMessageConfirmation":
|
||||
await updateGlobalState("skipEditMessageConfirmation", message.bool ?? false)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "skipDeleteMessageConfirmation":
|
||||
await updateGlobalState("skipDeleteMessageConfirmation", message.bool ?? false)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "toggleApiConfigPin":
|
||||
if (message.text) {
|
||||
const currentPinned = getGlobalState("pinnedApiConfigs") ?? {}
|
||||
|
|
@ -1526,6 +1501,16 @@ export const webviewMessageHandler = async (
|
|||
}
|
||||
}
|
||||
break
|
||||
case "deleteMessageConfirm":
|
||||
if (message.messageTs) {
|
||||
await handleDeleteMessageConfirm(message.messageTs)
|
||||
}
|
||||
break
|
||||
case "editMessageConfirm":
|
||||
if (message.messageTs && message.text) {
|
||||
await handleEditMessageConfirm(message.messageTs, message.text)
|
||||
}
|
||||
break
|
||||
case "getListApiConfiguration":
|
||||
try {
|
||||
const listApiConfig = await provider.providerSettingsManager.listConfig()
|
||||
|
|
|
|||
|
|
@ -17,12 +17,7 @@
|
|||
"confirmation": {
|
||||
"reset_state": "Are you sure you want to reset all state and secret storage in the extension? This cannot be undone.",
|
||||
"delete_config_profile": "Are you sure you want to delete this configuration profile?",
|
||||
"delete_custom_mode_with_rules": "Are you sure you want to delete this {scope} mode?\n\nThis will also delete the associated rules folder at:\n{rulesFolderPath}",
|
||||
"delete_message": "What would you like to delete?",
|
||||
"edit_warning": "Editing this message will delete all subsequent messages in the conversation. Do you want to proceed?",
|
||||
"delete_just_this_message": "Just this message",
|
||||
"delete_this_and_subsequent": "This and all subsequent messages",
|
||||
"proceed": "Proceed"
|
||||
"delete_custom_mode_with_rules": "Are you sure you want to delete this {scope} mode?\n\nThis will also delete the associated rules folder at:\n{rulesFolderPath}"
|
||||
},
|
||||
"errors": {
|
||||
"invalid_data_uri": "Invalid data URI format",
|
||||
|
|
|
|||
|
|
@ -105,6 +105,8 @@ export interface ExtensionMessage {
|
|||
| "shareTaskSuccess"
|
||||
| "codeIndexSettingsSaved"
|
||||
| "codeIndexSecretStatus"
|
||||
| "showDeleteMessageDialog"
|
||||
| "showEditMessageDialog"
|
||||
text?: string
|
||||
payload?: any // Add a generic payload for now, can refine later
|
||||
action?:
|
||||
|
|
@ -157,6 +159,7 @@ export interface ExtensionMessage {
|
|||
visibility?: ShareVisibility
|
||||
rulesFolderPath?: string
|
||||
settings?: any
|
||||
messageTs?: number
|
||||
}
|
||||
|
||||
export type ExtensionState = Pick<
|
||||
|
|
@ -182,6 +185,8 @@ export type ExtensionState = Pick<
|
|||
| "alwaysAllowSubtasks"
|
||||
| "alwaysAllowExecute"
|
||||
| "alwaysAllowUpdateTodoList"
|
||||
| "skipEditMessageConfirmation"
|
||||
| "skipDeleteMessageConfirmation"
|
||||
| "allowedCommands"
|
||||
| "allowedMaxRequests"
|
||||
| "browserToolEnabled"
|
||||
|
|
|
|||
|
|
@ -80,6 +80,8 @@ export interface WebviewMessage {
|
|||
| "allowedMaxRequests"
|
||||
| "alwaysAllowSubtasks"
|
||||
| "alwaysAllowUpdateTodoList"
|
||||
| "skipEditMessageConfirmation"
|
||||
| "skipDeleteMessageConfirmation"
|
||||
| "autoCondenseContext"
|
||||
| "autoCondenseContextPercent"
|
||||
| "condensingApiConfigId"
|
||||
|
|
@ -110,7 +112,9 @@ export interface WebviewMessage {
|
|||
| "enhancedPrompt"
|
||||
| "draggedImages"
|
||||
| "deleteMessage"
|
||||
| "deleteMessageConfirm"
|
||||
| "submitEditedMessage"
|
||||
| "editMessageConfirm"
|
||||
| "terminalOutputLineLimit"
|
||||
| "terminalShellIntegrationTimeout"
|
||||
| "terminalShellIntegrationDisabled"
|
||||
|
|
@ -225,6 +229,7 @@ export interface WebviewMessage {
|
|||
ids?: string[]
|
||||
hasSystemPromptOverride?: boolean
|
||||
terminalOperation?: "continue" | "abort"
|
||||
messageTs?: number
|
||||
historyPreviewCollapsed?: boolean
|
||||
filters?: { type?: string; search?: string; tags?: string[] }
|
||||
url?: string // For openExternal
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import McpView from "./components/mcp/McpView"
|
|||
import { MarketplaceView } from "./components/marketplace/MarketplaceView"
|
||||
import ModesView from "./components/modes/ModesView"
|
||||
import { HumanRelayDialog } from "./components/human-relay/HumanRelayDialog"
|
||||
import { DeleteMessageDialog, EditMessageDialog } from "./components/chat/MessageModificationConfirmationDialog"
|
||||
import { AccountView } from "./components/account/AccountView"
|
||||
import { useAddNonInteractiveClickListener } from "./components/ui/hooks/useNonInteractiveClick"
|
||||
import { TooltipProvider } from "./components/ui/tooltip"
|
||||
|
|
@ -48,6 +49,10 @@ const App = () => {
|
|||
cloudApiUrl,
|
||||
renderContext,
|
||||
mdmCompliant,
|
||||
skipEditMessageConfirmation,
|
||||
setSkipEditMessageConfirmation,
|
||||
skipDeleteMessageConfirmation,
|
||||
setSkipDeleteMessageConfirmation,
|
||||
} = useExtensionState()
|
||||
|
||||
// Create a persistent state manager
|
||||
|
|
@ -66,6 +71,24 @@ const App = () => {
|
|||
promptText: "",
|
||||
})
|
||||
|
||||
const [deleteMessageDialogState, setDeleteMessageDialogState] = useState<{
|
||||
isOpen: boolean
|
||||
messageTs: number
|
||||
}>({
|
||||
isOpen: false,
|
||||
messageTs: 0,
|
||||
})
|
||||
|
||||
const [editMessageDialogState, setEditMessageDialogState] = useState<{
|
||||
isOpen: boolean
|
||||
messageTs: number
|
||||
text: string
|
||||
}>({
|
||||
isOpen: false,
|
||||
messageTs: 0,
|
||||
text: "",
|
||||
})
|
||||
|
||||
const settingsRef = useRef<SettingsViewRef>(null)
|
||||
const chatViewRef = useRef<ChatViewRef>(null)
|
||||
|
||||
|
|
@ -121,11 +144,38 @@ const App = () => {
|
|||
setHumanRelayDialogState({ isOpen: true, requestId, promptText })
|
||||
}
|
||||
|
||||
if (message.type === "showDeleteMessageDialog" && message.messageTs) {
|
||||
// Check if user has opted to skip the confirmation
|
||||
if (skipDeleteMessageConfirmation) {
|
||||
// Directly send the confirmation without showing dialog
|
||||
vscode.postMessage({
|
||||
type: "deleteMessageConfirm",
|
||||
messageTs: message.messageTs,
|
||||
})
|
||||
} else {
|
||||
setDeleteMessageDialogState({ isOpen: true, messageTs: message.messageTs })
|
||||
}
|
||||
}
|
||||
|
||||
if (message.type === "showEditMessageDialog" && message.messageTs && message.text) {
|
||||
// Check if user has opted to skip the confirmation
|
||||
if (skipEditMessageConfirmation) {
|
||||
// Directly send the confirmation without showing dialog
|
||||
vscode.postMessage({
|
||||
type: "editMessageConfirm",
|
||||
messageTs: message.messageTs,
|
||||
text: message.text,
|
||||
})
|
||||
} else {
|
||||
setEditMessageDialogState({ isOpen: true, messageTs: message.messageTs, text: message.text })
|
||||
}
|
||||
}
|
||||
|
||||
if (message.type === "acceptInput") {
|
||||
chatViewRef.current?.acceptInput()
|
||||
}
|
||||
},
|
||||
[switchTab],
|
||||
[switchTab, skipDeleteMessageConfirmation, skipEditMessageConfirmation],
|
||||
)
|
||||
|
||||
useEvent("message", onMessage)
|
||||
|
|
@ -207,6 +257,45 @@ const App = () => {
|
|||
onSubmit={(requestId, text) => vscode.postMessage({ type: "humanRelayResponse", requestId, text })}
|
||||
onCancel={(requestId) => vscode.postMessage({ type: "humanRelayCancel", requestId })}
|
||||
/>
|
||||
<DeleteMessageDialog
|
||||
open={deleteMessageDialogState.isOpen}
|
||||
onOpenChange={(open) => setDeleteMessageDialogState((prev) => ({ ...prev, isOpen: open }))}
|
||||
onConfirm={(dontShowAgain) => {
|
||||
// Save the preference if checkbox was checked
|
||||
if (dontShowAgain) {
|
||||
setSkipDeleteMessageConfirmation(true)
|
||||
vscode.postMessage({
|
||||
type: "skipDeleteMessageConfirmation",
|
||||
bool: true,
|
||||
})
|
||||
}
|
||||
vscode.postMessage({
|
||||
type: "deleteMessageConfirm",
|
||||
messageTs: deleteMessageDialogState.messageTs,
|
||||
})
|
||||
setDeleteMessageDialogState((prev) => ({ ...prev, isOpen: false }))
|
||||
}}
|
||||
/>
|
||||
<EditMessageDialog
|
||||
open={editMessageDialogState.isOpen}
|
||||
onOpenChange={(open) => setEditMessageDialogState((prev) => ({ ...prev, isOpen: open }))}
|
||||
onConfirm={(dontShowAgain) => {
|
||||
// Save the preference if checkbox was checked
|
||||
if (dontShowAgain) {
|
||||
setSkipEditMessageConfirmation(true)
|
||||
vscode.postMessage({
|
||||
type: "skipEditMessageConfirmation",
|
||||
bool: true,
|
||||
})
|
||||
}
|
||||
vscode.postMessage({
|
||||
type: "editMessageConfirm",
|
||||
messageTs: editMessageDialogState.messageTs,
|
||||
text: editMessageDialogState.text,
|
||||
})
|
||||
setEditMessageDialogState((prev) => ({ ...prev, isOpen: false }))
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -981,7 +981,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
<div className="absolute bottom-1 right-1 z-30">
|
||||
<IconButton
|
||||
iconClass="codicon-edit"
|
||||
title={t("chat:save")}
|
||||
title={t("chat:save.tooltip")}
|
||||
disabled={sendingDisabled}
|
||||
onClick={onSend}
|
||||
className="opacity-60 hover:opacity-100 text-vscode-descriptionForeground hover:text-vscode-foreground"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
import React, { useState } from "react"
|
||||
import { useAppTranslation } from "@src/i18n/TranslationContext"
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
Checkbox,
|
||||
} from "@src/components/ui"
|
||||
|
||||
interface MessageModificationConfirmationDialogProps {
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
onConfirm: (dontShowAgain: boolean) => void
|
||||
type: "edit" | "delete"
|
||||
}
|
||||
|
||||
export const MessageModificationConfirmationDialog: React.FC<MessageModificationConfirmationDialogProps> = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
onConfirm,
|
||||
type,
|
||||
}) => {
|
||||
const { t } = useAppTranslation()
|
||||
const [dontShowAgain, setDontShowAgain] = useState(false)
|
||||
|
||||
const isEdit = type === "edit"
|
||||
const title = isEdit ? t("common:confirmation.edit_message") : t("common:confirmation.delete_message")
|
||||
const description = isEdit ? t("common:confirmation.edit_warning") : t("common:confirmation.delete_warning")
|
||||
|
||||
const handleConfirm = () => {
|
||||
onConfirm(dontShowAgain)
|
||||
setDontShowAgain(false) // Reset for next time
|
||||
}
|
||||
|
||||
const handleOpenChange = (open: boolean) => {
|
||||
if (!open) {
|
||||
setDontShowAgain(false) // Reset when dialog closes
|
||||
}
|
||||
onOpenChange(open)
|
||||
}
|
||||
|
||||
return (
|
||||
<AlertDialog open={open} onOpenChange={handleOpenChange}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle className="text-lg">{title}</AlertDialogTitle>
|
||||
<AlertDialogDescription className="text-base">{description}</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<div className="flex items-center space-x-2 px-6 py-1">
|
||||
<Checkbox
|
||||
id="dont-show-again"
|
||||
checked={dontShowAgain}
|
||||
onCheckedChange={(checked) => setDontShowAgain(checked as boolean)}
|
||||
/>
|
||||
<label
|
||||
htmlFor="dont-show-again"
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer">
|
||||
{t("common:confirmation.dont_show_again")}
|
||||
</label>
|
||||
</div>
|
||||
<AlertDialogFooter className="flex-col gap-2">
|
||||
<AlertDialogCancel className="bg-vscode-button-secondaryBackground hover:bg-vscode-button-secondaryHoverBackground text-vscode-button-secondaryForeground border-vscode-button-border">
|
||||
{t("common:answers.cancel")}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={handleConfirm}
|
||||
className="bg-vscode-button-background hover:bg-vscode-button-hoverBackground text-vscode-button-foreground border-vscode-button-border">
|
||||
{t("common:confirmation.proceed")}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
}
|
||||
|
||||
// Export convenience components for backward compatibility
|
||||
export const EditMessageDialog: React.FC<Omit<MessageModificationConfirmationDialogProps, "type">> = (props) => (
|
||||
<MessageModificationConfirmationDialog {...props} type="edit" />
|
||||
)
|
||||
|
||||
export const DeleteMessageDialog: React.FC<Omit<MessageModificationConfirmationDialogProps, "type">> = (props) => (
|
||||
<MessageModificationConfirmationDialog {...props} type="delete" />
|
||||
)
|
||||
|
|
@ -131,6 +131,10 @@ export interface ExtensionStateContextType extends ExtensionState {
|
|||
routerModels?: RouterModels
|
||||
alwaysAllowUpdateTodoList?: boolean
|
||||
setAlwaysAllowUpdateTodoList: (value: boolean) => void
|
||||
skipEditMessageConfirmation?: boolean
|
||||
setSkipEditMessageConfirmation: (value: boolean) => void
|
||||
skipDeleteMessageConfirmation?: boolean
|
||||
setSkipDeleteMessageConfirmation: (value: boolean) => void
|
||||
}
|
||||
|
||||
export const ExtensionStateContext = createContext<ExtensionStateContextType | undefined>(undefined)
|
||||
|
|
@ -224,6 +228,8 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
|||
},
|
||||
codebaseIndexModels: { ollama: {}, openai: {} },
|
||||
alwaysAllowUpdateTodoList: true,
|
||||
skipEditMessageConfirmation: false,
|
||||
skipDeleteMessageConfirmation: false,
|
||||
})
|
||||
|
||||
const [didHydrateState, setDidHydrateState] = useState(false)
|
||||
|
|
@ -466,6 +472,14 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
|||
setAlwaysAllowUpdateTodoList: (value) => {
|
||||
setState((prevState) => ({ ...prevState, alwaysAllowUpdateTodoList: value }))
|
||||
},
|
||||
skipEditMessageConfirmation: state.skipEditMessageConfirmation,
|
||||
setSkipEditMessageConfirmation: (value) => {
|
||||
setState((prevState) => ({ ...prevState, skipEditMessageConfirmation: value }))
|
||||
},
|
||||
skipDeleteMessageConfirmation: state.skipDeleteMessageConfirmation,
|
||||
setSkipDeleteMessageConfirmation: (value) => {
|
||||
setState((prevState) => ({ ...prevState, skipDeleteMessageConfirmation: value }))
|
||||
},
|
||||
}
|
||||
|
||||
return <ExtensionStateContext.Provider value={contextValue}>{children}</ExtensionStateContext.Provider>
|
||||
|
|
|
|||
|
|
@ -51,5 +51,13 @@
|
|||
"success": {
|
||||
"imageDataUriCopied": "Image data URI copied to clipboard"
|
||||
}
|
||||
},
|
||||
"confirmation": {
|
||||
"delete_message": "Delete Message",
|
||||
"delete_warning": "Deleting this message will delete all subsequent messages in the conversation. Do you want to proceed?",
|
||||
"edit_message": "Edit Message",
|
||||
"edit_warning": "Editing this message will delete all subsequent messages in the conversation. Do you want to proceed?",
|
||||
"proceed": "Proceed",
|
||||
"dont_show_again": "Don't show this again"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue