Make the prompts editable now (#5359)

This commit is contained in:
Will Li 2025-07-08 07:42:16 -07:00 committed by GitHub
parent edce187c03
commit a9a87c2e0f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 1464 additions and 153 deletions

File diff suppressed because it is too large Load diff

View file

@ -6,9 +6,16 @@ import pWaitFor from "p-wait-for"
import * as vscode from "vscode"
import * as yaml from "yaml"
import { type Language, type ProviderSettings, type GlobalState, TelemetryEventName } from "@roo-code/types"
import {
type Language,
type ProviderSettings,
type GlobalState,
type ClineMessage,
TelemetryEventName,
} from "@roo-code/types"
import { CloudService } from "@roo-code/cloud"
import { TelemetryService } from "@roo-code/telemetry"
import { type ApiMessage } from "../task-persistence/apiMessages"
import { ClineProvider } from "./ClineProvider"
import { changeLanguage, t } from "../../i18n"
@ -58,6 +65,200 @@ export const webviewMessageHandler = async (
const updateGlobalState = async <K extends keyof GlobalState>(key: K, value: GlobalState[K]) =>
await provider.contextProxy.setValue(key, value)
/**
* Shared utility to find message indices based on timestamp
*/
const findMessageIndices = (messageTs: number, currentCline: any) => {
const timeCutoff = messageTs - 1000 // 1 second buffer before the message
const messageIndex = currentCline.clineMessages.findIndex((msg: ClineMessage) => msg.ts && msg.ts >= timeCutoff)
const apiConversationHistoryIndex = currentCline.apiConversationHistory.findIndex(
(msg: ApiMessage) => msg.ts && msg.ts >= timeCutoff,
)
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
*/
const removeMessagesThisAndSubsequent = async (
currentCline: any,
messageIndex: number,
apiConversationHistoryIndex: number,
) => {
// Delete this message and all that follow
await currentCline.overwriteClineMessages(currentCline.clineMessages.slice(0, messageIndex))
if (apiConversationHistoryIndex !== -1) {
await currentCline.overwriteApiConversationHistory(
currentCline.apiConversationHistory.slice(0, apiConversationHistoryIndex),
)
}
}
/**
* 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"),
]
const answer = await vscode.window.showInformationMessage(
t("common:confirmation.delete_message"),
{ modal: true },
...options,
)
// Only proceed if user selected one of the options and we have a current cline
if (answer && options.includes(answer) && provider.getCurrentCline()) {
const currentCline = provider.getCurrentCline()!
const { messageIndex, apiConversationHistoryIndex } = findMessageIndices(messageTs, currentCline)
if (messageIndex !== -1) {
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)
}
// Initialize with history item after deletion
await provider.initClineWithHistoryItem(historyItem)
} catch (error) {
console.error("Error in delete message:", error)
vscode.window.showErrorMessage(
`Error deleting message: ${error instanceof Error ? error.message : String(error)}`,
)
}
}
}
}
/**
* Handles message editing operations with user confirmation
*/
const handleEditOperation = async (messageTs: number, editedContent: string): Promise<void> => {
const options = [
t("common:confirmation.edit_this_and_delete_subsequent"),
t("common:confirmation.edit_just_this_message"),
]
const answer = await vscode.window.showInformationMessage(
t("common:confirmation.edit_message"),
{ modal: true },
...options,
)
// Only proceed if user selected one of the options and we have a current cline
if (answer && options.includes(answer) && provider.getCurrentCline()) {
const currentCline = provider.getCurrentCline()!
const { messageIndex, apiConversationHistoryIndex } = findMessageIndices(messageTs, currentCline)
if (messageIndex !== -1) {
try {
// Check which option the user selected
if (answer === options[0]) {
// Edit this message and delete subsequent
await removeMessagesThisAndSubsequent(currentCline, messageIndex, apiConversationHistoryIndex)
} else if (answer === options[1]) {
// Edit just this message
await removeMessagesJustThis(currentCline, messageIndex, apiConversationHistoryIndex)
}
// Process the edited message as a regular user message
// This will add it to the conversation and trigger an AI response
webviewMessageHandler(provider, {
type: "askResponse",
askResponse: "messageResponse",
text: editedContent,
})
// Don't initialize with history item for edit operations
// The webviewMessageHandler will handle the conversation state
} catch (error) {
console.error("Error in edit message:", error)
vscode.window.showErrorMessage(
`Error editing message: ${error instanceof Error ? error.message : String(error)}`,
)
}
}
}
}
/**
* Handles message modification operations (delete or edit) with confirmation dialog
* @param messageTs Timestamp of the message to operate on
* @param operation Type of operation ('delete' or 'edit')
* @param editedContent New content for edit operations
* @returns Promise<void>
*/
const handleMessageModificationsOperation = async (
messageTs: number,
operation: "delete" | "edit",
editedContent?: string,
): Promise<void> => {
if (operation === "delete") {
await handleDeleteOperation(messageTs)
} else if (operation === "edit" && editedContent) {
await handleEditOperation(messageTs, editedContent)
}
}
switch (message.type) {
case "webviewDidLaunch":
// Load custom modes first
@ -989,108 +1190,19 @@ export const webviewMessageHandler = async (
}
break
case "deleteMessage": {
const answer = await vscode.window.showInformationMessage(
t("common:confirmation.delete_message"),
{ modal: true },
t("common:confirmation.just_this_message"),
t("common:confirmation.this_and_subsequent"),
)
if (provider.getCurrentCline() && typeof message.value === "number" && message.value) {
await handleMessageModificationsOperation(message.value, "delete")
}
break
}
case "submitEditedMessage": {
if (
(answer === t("common:confirmation.just_this_message") ||
answer === t("common:confirmation.this_and_subsequent")) &&
provider.getCurrentCline() &&
typeof message.value === "number" &&
message.value
message.value &&
message.editedMessageContent
) {
const timeCutoff = message.value - 1000 // 1 second buffer before the message to delete
const messageIndex = provider
.getCurrentCline()!
.clineMessages.findIndex((msg) => msg.ts && msg.ts >= timeCutoff)
const apiConversationHistoryIndex = provider
.getCurrentCline()
?.apiConversationHistory.findIndex((msg) => msg.ts && msg.ts >= timeCutoff)
if (messageIndex !== -1) {
const { historyItem } = await provider.getTaskWithId(provider.getCurrentCline()!.taskId)
if (answer === t("common:confirmation.just_this_message")) {
// Find the next user message first
const nextUserMessage = provider
.getCurrentCline()!
.clineMessages.slice(messageIndex + 1)
.find((msg) => msg.type === "say" && msg.say === "user_feedback")
// Handle UI messages
if (nextUserMessage) {
// Find absolute index of next user message
const nextUserMessageIndex = provider
.getCurrentCline()!
.clineMessages.findIndex((msg) => msg === nextUserMessage)
// Keep messages before current message and after next user message
await provider
.getCurrentCline()!
.overwriteClineMessages([
...provider.getCurrentCline()!.clineMessages.slice(0, messageIndex),
...provider.getCurrentCline()!.clineMessages.slice(nextUserMessageIndex),
])
} else {
// If no next user message, keep only messages before current message
await provider
.getCurrentCline()!
.overwriteClineMessages(
provider.getCurrentCline()!.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 provider
.getCurrentCline()!
.overwriteApiConversationHistory([
...provider
.getCurrentCline()!
.apiConversationHistory.slice(0, apiConversationHistoryIndex),
...provider
.getCurrentCline()!
.apiConversationHistory.filter(
(msg) => msg.ts && msg.ts >= nextUserMessage.ts,
),
])
} else {
// If no next user message, keep only messages before current API message
await provider
.getCurrentCline()!
.overwriteApiConversationHistory(
provider
.getCurrentCline()!
.apiConversationHistory.slice(0, apiConversationHistoryIndex),
)
}
}
} else if (answer === t("common:confirmation.this_and_subsequent")) {
// Delete this message and all that follow
await provider
.getCurrentCline()!
.overwriteClineMessages(provider.getCurrentCline()!.clineMessages.slice(0, messageIndex))
if (apiConversationHistoryIndex !== -1) {
await provider
.getCurrentCline()!
.overwriteApiConversationHistory(
provider
.getCurrentCline()!
.apiConversationHistory.slice(0, apiConversationHistoryIndex),
)
}
}
await provider.initClineWithHistoryItem(historyItem)
}
await handleMessageModificationsOperation(message.value, "edit", message.editedMessageContent)
}
break
}

View file

@ -23,8 +23,11 @@
"delete_config_profile": "Estàs segur que vols eliminar aquest perfil de configuració?",
"delete_custom_mode_with_rules": "Esteu segur que voleu suprimir aquest mode {scope}?\n\nAixò també suprimirà la carpeta de regles associada a:\n{rulesFolderPath}",
"delete_message": "Què vols eliminar?",
"just_this_message": "Només aquest missatge",
"this_and_subsequent": "Aquest i tots els missatges posteriors"
"edit_message": "Eliminar tots els missatges després d'aquest?",
"delete_just_this_message": "Només aquest missatge",
"edit_just_this_message": "No, només editar aquest",
"delete_this_and_subsequent": "Aquest i tots els missatges posteriors",
"edit_this_and_delete_subsequent": "Sí"
},
"errors": {
"invalid_data_uri": "Format d'URI de dades no vàlid",
@ -112,6 +115,11 @@
"remove": "Eliminar",
"keep": "Mantenir"
},
"buttons": {
"save": "Desar",
"cancel": "Cancel·lar",
"edit": "Editar"
},
"tasks": {
"canceled": "Error de tasca: Ha estat aturada i cancel·lada per l'usuari.",
"deleted": "Fallada de tasca: Ha estat aturada i eliminada per l'usuari.",

View file

@ -19,8 +19,11 @@
"delete_config_profile": "Möchtest du dieses Konfigurationsprofil wirklich löschen?",
"delete_custom_mode_with_rules": "Bist du sicher, dass du diesen {scope}-Modus löschen möchtest?\n\nDadurch wird auch der zugehörige Regelordner unter folgender Adresse gelöscht:\n{rulesFolderPath}",
"delete_message": "Was möchtest du löschen?",
"just_this_message": "Nur diese Nachricht",
"this_and_subsequent": "Diese und alle nachfolgenden Nachrichten"
"edit_message": "Alle Nachrichten nach dieser löschen?",
"delete_just_this_message": "Nur diese Nachricht",
"edit_just_this_message": "Nein, nur diese bearbeiten",
"delete_this_and_subsequent": "Diese und alle nachfolgenden Nachrichten",
"edit_this_and_delete_subsequent": "Ja"
},
"errors": {
"invalid_data_uri": "Ungültiges Daten-URI-Format",
@ -108,6 +111,11 @@
"remove": "Entfernen",
"keep": "Behalten"
},
"buttons": {
"save": "Speichern",
"cancel": "Abbrechen",
"edit": "Bearbeiten"
},
"tasks": {
"canceled": "Aufgabenfehler: Die Aufgabe wurde vom Benutzer gestoppt und abgebrochen.",
"deleted": "Aufgabenfehler: Die Aufgabe wurde vom Benutzer gestoppt und gelöscht.",

View file

@ -19,8 +19,11 @@
"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?",
"just_this_message": "Just this message",
"this_and_subsequent": "This and all subsequent messages"
"edit_message": "Delete all messages after this one?",
"delete_just_this_message": "Just this message",
"edit_just_this_message": "No, just edit this one",
"delete_this_and_subsequent": "This and all subsequent messages",
"edit_this_and_delete_subsequent": "Yes"
},
"errors": {
"invalid_data_uri": "Invalid data URI format",
@ -108,6 +111,11 @@
"remove": "Remove",
"keep": "Keep"
},
"buttons": {
"save": "Save",
"cancel": "Cancel",
"edit": "Edit"
},
"tasks": {
"canceled": "Task error: It was stopped and canceled by the user.",
"deleted": "Task failure: It was stopped and deleted by the user.",

View file

@ -19,8 +19,11 @@
"delete_config_profile": "¿Estás seguro de que deseas eliminar este perfil de configuración?",
"delete_custom_mode_with_rules": "¿Estás seguro de que quieres eliminar este modo {scope}?\n\nEsto también eliminará la carpeta de reglas asociada en:\n{rulesFolderPath}",
"delete_message": "¿Qué deseas eliminar?",
"just_this_message": "Solo este mensaje",
"this_and_subsequent": "Este y todos los mensajes posteriores"
"edit_message": "¿Eliminar todos los mensajes posteriores a este?",
"delete_just_this_message": "Solo este mensaje",
"edit_just_this_message": "No, solo editar este",
"delete_this_and_subsequent": "Este y todos los mensajes posteriores",
"edit_this_and_delete_subsequent": "Sí"
},
"errors": {
"invalid_data_uri": "Formato de URI de datos no válido",
@ -108,6 +111,11 @@
"remove": "Eliminar",
"keep": "Mantener"
},
"buttons": {
"save": "Guardar",
"cancel": "Cancelar",
"edit": "Editar"
},
"tasks": {
"canceled": "Error de tarea: Fue detenida y cancelada por el usuario.",
"deleted": "Fallo de tarea: Fue detenida y eliminada por el usuario.",

View file

@ -19,8 +19,11 @@
"delete_config_profile": "Êtes-vous sûr de vouloir supprimer ce profil de configuration ?",
"delete_custom_mode_with_rules": "Êtes-vous sûr de vouloir supprimer ce mode {scope} ?\n\nCela supprimera également le dossier de règles associé à l'adresse :\n{rulesFolderPath}",
"delete_message": "Que souhaitez-vous supprimer ?",
"just_this_message": "Uniquement ce message",
"this_and_subsequent": "Ce message et tous les messages suivants"
"edit_message": "Supprimer tous les messages après celui-ci ?",
"delete_just_this_message": "Uniquement ce message",
"edit_just_this_message": "Non, modifier uniquement celui-ci",
"delete_this_and_subsequent": "Ce message et tous les messages suivants",
"edit_this_and_delete_subsequent": "Oui"
},
"errors": {
"invalid_data_uri": "Format d'URI de données invalide",
@ -108,6 +111,11 @@
"remove": "Supprimer",
"keep": "Conserver"
},
"buttons": {
"save": "Enregistrer",
"cancel": "Annuler",
"edit": "Modifier"
},
"tasks": {
"canceled": "Erreur de tâche : Elle a été arrêtée et annulée par l'utilisateur.",
"deleted": "Échec de la tâche : Elle a été arrêtée et supprimée par l'utilisateur.",

View file

@ -19,8 +19,11 @@
"delete_config_profile": "क्या आप वाकई इस कॉन्फ़िगरेशन प्रोफ़ाइल को हटाना चाहते हैं?",
"delete_custom_mode_with_rules": "क्या आप वाकई इस {scope} मोड को हटाना चाहते हैं?\n\nयह संबंधित नियम फ़ोल्डर को भी यहाँ हटा देगा:\n{rulesFolderPath}",
"delete_message": "आप क्या हटाना चाहते हैं?",
"just_this_message": "सिर्फ यह संदेश",
"this_and_subsequent": "यह और सभी बाद के संदेश"
"edit_message": "इसके बाद के सभी संदेशों को हटाएं?",
"delete_just_this_message": "सिर्फ यह संदेश",
"edit_just_this_message": "नहीं, केवल इसे संपादित करें",
"delete_this_and_subsequent": "यह और सभी बाद के संदेश",
"edit_this_and_delete_subsequent": "हां"
},
"errors": {
"invalid_data_uri": "अमान्य डेटा URI फॉर्मेट",
@ -108,6 +111,11 @@
"remove": "हटाएं",
"keep": "रखें"
},
"buttons": {
"save": "सहेजें",
"cancel": "रद्द करें",
"edit": "संपादित करें"
},
"tasks": {
"canceled": "टास्क त्रुटि: इसे उपयोगकर्ता द्वारा रोका और रद्द किया गया था।",
"deleted": "टास्क विफलता: इसे उपयोगकर्ता द्वारा रोका और हटाया गया था।",

View file

@ -19,8 +19,11 @@
"delete_config_profile": "Apakah kamu yakin ingin menghapus profil konfigurasi ini?",
"delete_custom_mode_with_rules": "Anda yakin ingin menghapus mode {scope} ini?\n\nIni juga akan menghapus folder aturan terkait di:\n{rulesFolderPath}",
"delete_message": "Apa yang ingin kamu hapus?",
"just_this_message": "Hanya pesan ini",
"this_and_subsequent": "Ini dan semua pesan selanjutnya"
"edit_message": "Hapus semua pesan setelah ini?",
"delete_just_this_message": "Hanya pesan ini",
"edit_just_this_message": "Tidak, hanya edit yang ini",
"delete_this_and_subsequent": "Ini dan semua pesan selanjutnya",
"edit_this_and_delete_subsequent": "Ya"
},
"errors": {
"invalid_data_uri": "Format data URI tidak valid",
@ -108,6 +111,11 @@
"remove": "Hapus",
"keep": "Simpan"
},
"buttons": {
"save": "Simpan",
"cancel": "Batal",
"edit": "Edit"
},
"tasks": {
"canceled": "Error tugas: Dihentikan dan dibatalkan oleh pengguna.",
"deleted": "Kegagalan tugas: Dihentikan dan dihapus oleh pengguna.",

View file

@ -19,8 +19,11 @@
"delete_config_profile": "Sei sicuro di voler eliminare questo profilo di configurazione?",
"delete_custom_mode_with_rules": "Sei sicuro di voler eliminare questa modalità {scope}?\n\nQuesto eliminerà anche la cartella delle regole associata in:\n{rulesFolderPath}",
"delete_message": "Cosa desideri eliminare?",
"just_this_message": "Solo questo messaggio",
"this_and_subsequent": "Questo e tutti i messaggi successivi"
"edit_message": "Eliminare tutti i messaggi dopo questo?",
"delete_just_this_message": "Solo questo messaggio",
"edit_just_this_message": "No, modifica solo questo",
"delete_this_and_subsequent": "Questo e tutti i messaggi successivi",
"edit_this_and_delete_subsequent": "Sì"
},
"errors": {
"invalid_data_uri": "Formato URI dati non valido",
@ -108,6 +111,11 @@
"remove": "Rimuovi",
"keep": "Mantieni"
},
"buttons": {
"save": "Salva",
"cancel": "Annulla",
"edit": "Modifica"
},
"tasks": {
"canceled": "Errore attività: È stata interrotta e annullata dall'utente.",
"deleted": "Fallimento attività: È stata interrotta ed eliminata dall'utente.",

View file

@ -19,8 +19,11 @@
"delete_config_profile": "この設定プロファイルを削除してもよろしいですか?",
"delete_custom_mode_with_rules": "この{scope}モードを削除してもよろしいですか?\n\nこれにより、関連するルールフォルダも次の場所で削除されます:\n{rulesFolderPath}",
"delete_message": "何を削除しますか?",
"just_this_message": "このメッセージのみ",
"this_and_subsequent": "これ以降のすべてのメッセージ"
"edit_message": "これ以降のメッセージをすべて削除しますか?",
"delete_just_this_message": "このメッセージのみ",
"edit_just_this_message": "いいえ、これだけを編集",
"delete_this_and_subsequent": "これ以降のすべてのメッセージ",
"edit_this_and_delete_subsequent": "はい"
},
"errors": {
"invalid_data_uri": "データURIフォーマットが無効です",
@ -108,6 +111,11 @@
"remove": "削除",
"keep": "保持"
},
"buttons": {
"save": "保存",
"cancel": "キャンセル",
"edit": "編集"
},
"tasks": {
"canceled": "タスクエラー:ユーザーによって停止およびキャンセルされました。",
"deleted": "タスク失敗:ユーザーによって停止および削除されました。",

View file

@ -19,8 +19,11 @@
"delete_config_profile": "이 구성 프로필을 삭제하시겠습니까?",
"delete_custom_mode_with_rules": "이 {scope} 모드를 삭제하시겠습니까?\n\n이렇게 하면 연결된 규칙 폴더도 다음 위치에서 삭제됩니다:\n{rulesFolderPath}",
"delete_message": "무엇을 삭제하시겠습니까?",
"just_this_message": "이 메시지만",
"this_and_subsequent": "이 메시지와 모든 후속 메시지"
"edit_message": "이 메시지 이후의 모든 메시지를 삭제하시겠습니까?",
"delete_just_this_message": "이 메시지만",
"edit_just_this_message": "아니요, 이것만 편집",
"delete_this_and_subsequent": "이 메시지와 모든 후속 메시지",
"edit_this_and_delete_subsequent": "예"
},
"errors": {
"invalid_data_uri": "잘못된 데이터 URI 형식",
@ -108,6 +111,11 @@
"remove": "제거",
"keep": "유지"
},
"buttons": {
"save": "저장",
"cancel": "취소",
"edit": "편집"
},
"tasks": {
"canceled": "작업 오류: 사용자에 의해 중지 및 취소되었습니다.",
"deleted": "작업 실패: 사용자에 의해 중지 및 삭제되었습니다.",

View file

@ -19,8 +19,11 @@
"delete_config_profile": "Weet je zeker dat je dit configuratieprofiel wilt verwijderen?",
"delete_custom_mode_with_rules": "Weet je zeker dat je deze {scope}-modus wilt verwijderen?\n\nDit verwijdert ook de bijbehorende regelsmap op:\n{rulesFolderPath}",
"delete_message": "Wat wil je verwijderen?",
"just_this_message": "Alleen dit bericht",
"this_and_subsequent": "Dit en alle volgende berichten"
"delete_just_this_message": "Alleen dit bericht",
"delete_this_and_subsequent": "Dit en alle volgende berichten",
"edit_message": "Alle berichten na dit bericht verwijderen?",
"edit_just_this_message": "Nee, alleen dit bericht bewerken",
"edit_this_and_delete_subsequent": "Ja"
},
"errors": {
"invalid_data_uri": "Ongeldig data-URI-formaat",
@ -108,6 +111,11 @@
"remove": "Verwijderen",
"keep": "Behouden"
},
"buttons": {
"save": "Opslaan",
"cancel": "Annuleren",
"edit": "Bewerken"
},
"tasks": {
"canceled": "Taakfout: gestopt en geannuleerd door gebruiker.",
"deleted": "Taakfout: gestopt en verwijderd door gebruiker.",

View file

@ -19,8 +19,11 @@
"delete_config_profile": "Czy na pewno chcesz usunąć ten profil konfiguracyjny?",
"delete_custom_mode_with_rules": "Czy na pewno chcesz usunąć ten tryb {scope}?\n\nSpowoduje to również usunięcie powiązanego folderu reguł pod adresem:\n{rulesFolderPath}",
"delete_message": "Co chcesz usunąć?",
"just_this_message": "Tylko tę wiadomość",
"this_and_subsequent": "Tę i wszystkie kolejne wiadomości"
"delete_just_this_message": "Tylko tę wiadomość",
"delete_this_and_subsequent": "Tę i wszystkie kolejne wiadomości",
"edit_message": "Usunąć wszystkie wiadomości po tej?",
"edit_just_this_message": "Nie, tylko edytuj tę wiadomość",
"edit_this_and_delete_subsequent": "Tak"
},
"errors": {
"invalid_data_uri": "Nieprawidłowy format URI danych",
@ -108,6 +111,11 @@
"remove": "Usuń",
"keep": "Zachowaj"
},
"buttons": {
"save": "Zapisz",
"cancel": "Anuluj",
"edit": "Edytuj"
},
"tasks": {
"canceled": "Błąd zadania: Zostało zatrzymane i anulowane przez użytkownika.",
"deleted": "Niepowodzenie zadania: Zostało zatrzymane i usunięte przez użytkownika.",

View file

@ -23,8 +23,11 @@
"delete_config_profile": "Tem certeza de que deseja excluir este perfil de configuração?",
"delete_custom_mode_with_rules": "Tem certeza de que deseja excluir este modo {scope}?\n\nIsso também excluirá a pasta de regras associada em:\n{rulesFolderPath}",
"delete_message": "O que você gostaria de excluir?",
"just_this_message": "Apenas esta mensagem",
"this_and_subsequent": "Esta e todas as mensagens subsequentes"
"delete_just_this_message": "Apenas esta mensagem",
"delete_this_and_subsequent": "Esta e todas as mensagens subsequentes",
"edit_message": "Excluir todas as mensagens após esta?",
"edit_just_this_message": "Não, apenas editar esta",
"edit_this_and_delete_subsequent": "Sim"
},
"errors": {
"invalid_data_uri": "Formato de URI de dados inválido",
@ -112,6 +115,11 @@
"remove": "Remover",
"keep": "Manter"
},
"buttons": {
"save": "Salvar",
"cancel": "Cancelar",
"edit": "Editar"
},
"tasks": {
"canceled": "Erro na tarefa: Foi interrompida e cancelada pelo usuário.",
"deleted": "Falha na tarefa: Foi interrompida e excluída pelo usuário.",

View file

@ -19,8 +19,11 @@
"delete_config_profile": "Вы уверены, что хотите удалить этот профиль конфигурации?",
"delete_custom_mode_with_rules": "Вы уверены, что хотите удалить этот режим {scope}?\n\nЭто также приведет к удалению соответствующей папки правил по адресу:\n{rulesFolderPath}",
"delete_message": "Что вы хотите удалить?",
"just_this_message": "Только это сообщение",
"this_and_subsequent": "Это и все последующие сообщения"
"delete_just_this_message": "Только это сообщение",
"delete_this_and_subsequent": "Это и все последующие сообщения",
"edit_message": "Удалить все сообщения после этого?",
"edit_just_this_message": "Нет, только редактировать это",
"edit_this_and_delete_subsequent": "Да"
},
"errors": {
"invalid_data_uri": "Неверный формат URI данных",
@ -108,6 +111,11 @@
"remove": "Удалить",
"keep": "Оставить"
},
"buttons": {
"save": "Сохранить",
"cancel": "Отмена",
"edit": "Редактировать"
},
"tasks": {
"canceled": "Ошибка задачи: Она была остановлена и отменена пользователем.",
"deleted": "Сбой задачи: Она была остановлена и удалена пользователем.",

View file

@ -19,8 +19,11 @@
"delete_config_profile": "Bu yapılandırma profilini silmek istediğinizden emin misiniz?",
"delete_custom_mode_with_rules": "Bu {scope} modunu silmek istediğinizden emin misiniz?\n\nBu işlem, ilişkili kurallar klasörünü de şu konumdan silecektir:\n{rulesFolderPath}",
"delete_message": "Neyi silmek istersiniz?",
"just_this_message": "Sadece bu mesajı",
"this_and_subsequent": "Bu ve sonraki tüm mesajları"
"delete_just_this_message": "Sadece bu mesajı",
"delete_this_and_subsequent": "Bu ve sonraki tüm mesajları",
"edit_message": "Bu mesajdan sonraki tüm mesajlar silinsin mi?",
"edit_just_this_message": "Hayır, sadece bunu düzenle",
"edit_this_and_delete_subsequent": "Evet"
},
"errors": {
"invalid_data_uri": "Geçersiz veri URI formatı",
@ -108,6 +111,11 @@
"remove": "Kaldır",
"keep": "Koru"
},
"buttons": {
"save": "Kaydet",
"cancel": "İptal",
"edit": "Düzenle"
},
"tasks": {
"canceled": "Görev hatası: Kullanıcı tarafından durduruldu ve iptal edildi.",
"deleted": "Görev başarısız: Kullanıcı tarafından durduruldu ve silindi.",

View file

@ -19,8 +19,11 @@
"delete_config_profile": "Bạn có chắc chắn muốn xóa hồ sơ cấu hình này không?",
"delete_custom_mode_with_rules": "Bạn có chắc chắn muốn xóa chế độ {scope} này không?\n\nThao tác này cũng sẽ xóa thư mục quy tắc liên quan tại:\n{rulesFolderPath}",
"delete_message": "Bạn muốn xóa gì?",
"just_this_message": "Chỉ tin nhắn này",
"this_and_subsequent": "Tin nhắn này và tất cả tin nhắn tiếp theo"
"delete_just_this_message": "Chỉ tin nhắn này",
"delete_this_and_subsequent": "Tin nhắn này và tất cả tin nhắn tiếp theo",
"edit_message": "Xóa tất cả tin nhắn sau tin nhắn này?",
"edit_just_this_message": "Không, chỉ chỉnh sửa tin nhắn này",
"edit_this_and_delete_subsequent": "Có"
},
"errors": {
"invalid_data_uri": "Định dạng URI dữ liệu không hợp lệ",
@ -108,6 +111,11 @@
"remove": "Xóa",
"keep": "Giữ"
},
"buttons": {
"save": "Lưu",
"cancel": "Hủy",
"edit": "Chỉnh sửa"
},
"tasks": {
"canceled": "Lỗi nhiệm vụ: Nó đã bị dừng và hủy bởi người dùng.",
"deleted": "Nhiệm vụ thất bại: Nó đã bị dừng và xóa bởi người dùng.",

View file

@ -19,8 +19,11 @@
"delete_config_profile": "您确定要删除此配置文件吗?",
"delete_custom_mode_with_rules": "您确定要删除此 {scope} 模式吗?\n\n这也将删除位于以下位置的关联规则文件夹\n{rulesFolderPath}",
"delete_message": "您想删除什么?",
"just_this_message": "仅此消息",
"this_and_subsequent": "此消息及所有后续消息"
"edit_message": "删除此消息后的所有消息?",
"delete_just_this_message": "仅此消息",
"edit_just_this_message": "不,仅编辑此消息",
"delete_this_and_subsequent": "此消息及所有后续消息",
"edit_this_and_delete_subsequent": "是"
},
"errors": {
"invalid_mcp_config": "项目MCP配置格式无效",
@ -113,6 +116,11 @@
"remove": "删除",
"keep": "保留"
},
"buttons": {
"save": "保存",
"cancel": "取消",
"edit": "编辑"
},
"tasks": {
"canceled": "任务错误:它已被用户停止并取消。",
"deleted": "任务失败:它已被用户停止并删除。",

View file

@ -19,8 +19,11 @@
"delete_config_profile": "您確定要刪除此設定檔案嗎?",
"delete_custom_mode_with_rules": "您確定要刪除此 {scope} 模式嗎?\n\n這也將刪除位於以下位置的關聯規則資料夾\n{rulesFolderPath}",
"delete_message": "您想刪除哪些內容?",
"just_this_message": "僅這則訊息",
"this_and_subsequent": "這則訊息及所有後續訊息"
"edit_message": "刪除此訊息後的所有訊息?",
"delete_just_this_message": "僅這則訊息",
"edit_just_this_message": "否,僅編輯此訊息",
"delete_this_and_subsequent": "這則訊息及所有後續訊息",
"edit_this_and_delete_subsequent": "是"
},
"errors": {
"invalid_data_uri": "資料 URI 格式無效",
@ -108,6 +111,11 @@
"remove": "刪除",
"keep": "保留"
},
"buttons": {
"save": "儲存",
"cancel": "取消",
"edit": "編輯"
},
"tasks": {
"canceled": "工作錯誤:它已被使用者停止並取消。",
"deleted": "工作失敗:它已被使用者停止並刪除。",

View file

@ -110,6 +110,7 @@ export interface WebviewMessage {
| "enhancedPrompt"
| "draggedImages"
| "deleteMessage"
| "submitEditedMessage"
| "terminalOutputLineLimit"
| "terminalShellIntegrationTimeout"
| "terminalShellIntegrationDisabled"
@ -193,6 +194,7 @@ export interface WebviewMessage {
| "saveCodeIndexSettingsAtomic"
| "requestCodeIndexSecretStatus"
text?: string
editedMessageContent?: string
tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "account"
disabled?: boolean
dataUri?: string

View file

@ -111,6 +111,8 @@ export const ChatRowContent = ({
const [reasoningCollapsed, setReasoningCollapsed] = useState(true)
const [isDiffErrorExpanded, setIsDiffErrorExpanded] = useState(false)
const [showCopySuccess, setShowCopySuccess] = useState(false)
const [isEditing, setIsEditing] = useState(false)
const [editedContent, setEditedContent] = useState("")
const { copyWithFeedback } = useCopyToClipboard()
// Memoized callback to prevent re-renders caused by inline arrow functions
@ -118,6 +120,31 @@ export const ChatRowContent = ({
onToggleExpand(message.ts)
}, [onToggleExpand, message.ts])
// Handle edit button click
const handleEditClick = useCallback(() => {
setIsEditing(true)
setEditedContent(message.text || "")
// Edit mode is now handled entirely in the frontend
// No need to notify the backend
}, [message.text])
// Handle cancel edit
const handleCancelEdit = useCallback(() => {
setIsEditing(false)
setEditedContent(message.text || "")
}, [message.text])
// Handle save edit
const handleSaveEdit = useCallback(() => {
setIsEditing(false)
// Send edited message to backend
vscode.postMessage({
type: "submitEditedMessage",
value: message.ts,
editedMessageContent: editedContent,
})
}, [message.ts, editedContent])
const [cost, apiReqCancelReason, apiReqStreamingFailedMessage] = useMemo(() => {
if (message.text !== null && message.text !== undefined && message.say === "api_req_started") {
const info = safeJsonParse<ClineApiReqInfo>(message.text)
@ -1001,23 +1028,56 @@ export const ChatRowContent = ({
case "user_feedback":
return (
<div className="bg-vscode-editor-background border rounded-xs p-1 overflow-hidden whitespace-pre-wrap">
<div className="flex justify-between">
<div className="flex-grow px-2 py-1 wrap-anywhere">
<Mention text={message.text} withShadow />
{isEditing ? (
<div className="flex flex-col gap-2 p-2">
<textarea
className="w-full p-2 bg-vscode-input-background text-vscode-input-foreground border border-vscode-input-border rounded-xs"
value={editedContent}
onChange={(e) => setEditedContent(e.target.value)}
rows={5}
autoFocus
/>
<div className="flex justify-end gap-2">
<Button variant="secondary" size="sm" onClick={handleCancelEdit}>
{t("chat:cancel.title")}
</Button>
<Button variant="default" size="sm" onClick={handleSaveEdit}>
{t("chat:save.title")}
</Button>
</div>
</div>
<Button
variant="ghost"
size="icon"
className="shrink-0"
disabled={isStreaming}
onClick={(e) => {
e.stopPropagation()
vscode.postMessage({ type: "deleteMessage", value: message.ts })
}}>
<span className="codicon codicon-trash" />
</Button>
</div>
{message.images && message.images.length > 0 && (
) : (
<div className="flex justify-between">
<div className="flex-grow px-2 py-1 wrap-anywhere">
<Mention text={message.text} withShadow />
</div>
<div className="flex">
<Button
variant="ghost"
size="icon"
className="shrink-0"
disabled={isStreaming}
onClick={(e) => {
e.stopPropagation()
handleEditClick()
}}>
<span className="codicon codicon-edit" />
</Button>
<Button
variant="ghost"
size="icon"
className="shrink-0"
disabled={isStreaming}
onClick={(e) => {
e.stopPropagation()
vscode.postMessage({ type: "deleteMessage", value: message.ts })
}}>
<span className="codicon codicon-trash" />
</Button>
</div>
</div>
)}
{!isEditing && message.images && message.images.length > 0 && (
<Thumbnails images={message.images} style={{ marginTop: "8px" }} />
)}
</div>