mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
feat: Replace image-only attachment with general file attachment support
- Add comprehensive file processing system with MIME type detection - Support multiple file categories (images, documents, code, data, archives, config) - Implement 10MB file size limit and intelligent text/binary handling - Replace camera icon with paperclip icon in chat interface - Add selectFiles message types and handlers - Update translations for attachFiles across all languages - Maintain backward compatibility with existing image functionality Resolves #5532
This commit is contained in:
parent
a6e16e80d9
commit
53422d3856
24 changed files with 286 additions and 5 deletions
|
|
@ -30,6 +30,7 @@ import { Terminal } from "../../integrations/terminal/Terminal"
|
|||
import { openFile } from "../../integrations/misc/open-file"
|
||||
import { openImage, saveImage } from "../../integrations/misc/image-handler"
|
||||
import { selectImages } from "../../integrations/misc/process-images"
|
||||
import { selectFiles } from "../../integrations/misc/process-files"
|
||||
import { getTheme } from "../../integrations/theme/getTheme"
|
||||
import { discoverChromeHostUrl, tryChromeHostUrl } from "../../services/browser/browserDiscovery"
|
||||
import { searchWorkspaceFiles } from "../../services/search/file-search"
|
||||
|
|
@ -381,6 +382,15 @@ export const webviewMessageHandler = async (
|
|||
messageTs: message.messageTs,
|
||||
})
|
||||
break
|
||||
case "selectFiles":
|
||||
const files = await selectFiles()
|
||||
await provider.postMessageToWebview({
|
||||
type: "selectedFiles",
|
||||
files,
|
||||
context: message.context,
|
||||
messageTs: message.messageTs,
|
||||
})
|
||||
break
|
||||
case "exportCurrentTask":
|
||||
const currentTaskId = provider.getCurrentCline()?.taskId
|
||||
if (currentTaskId) {
|
||||
|
|
|
|||
246
src/integrations/misc/process-files.ts
Normal file
246
src/integrations/misc/process-files.ts
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
import * as vscode from "vscode"
|
||||
import fs from "fs/promises"
|
||||
import * as path from "path"
|
||||
|
||||
// File size limit: 10MB
|
||||
const MAX_FILE_SIZE = 10 * 1024 * 1024
|
||||
|
||||
// Supported file types with their MIME types
|
||||
const FILE_TYPE_CATEGORIES = {
|
||||
images: ["png", "jpg", "jpeg", "gif", "bmp", "webp", "svg"],
|
||||
documents: ["pdf", "doc", "docx", "txt", "rtf", "odt", "md"],
|
||||
code: ["js", "ts", "py", "java", "cpp", "c", "h", "hpp", "html", "css", "json", "xml", "yaml", "yml", "php", "rb", "go", "rs", "swift", "kt", "scala", "sh", "bat", "ps1"],
|
||||
data: ["csv", "xls", "xlsx", "sql", "db", "sqlite"],
|
||||
archives: ["zip", "rar", "tar", "gz", "7z"],
|
||||
config: ["ini", "conf", "config", "env", "properties"],
|
||||
}
|
||||
|
||||
// Text file extensions that should be read as content
|
||||
const TEXT_FILE_EXTENSIONS = [
|
||||
...FILE_TYPE_CATEGORIES.code,
|
||||
...FILE_TYPE_CATEGORIES.documents.filter(ext => ["txt", "md"].includes(ext)),
|
||||
...FILE_TYPE_CATEGORIES.config,
|
||||
...FILE_TYPE_CATEGORIES.data.filter(ext => ["csv", "sql"].includes(ext)),
|
||||
]
|
||||
|
||||
export interface ProcessedFile {
|
||||
name: string
|
||||
path: string
|
||||
size: number
|
||||
type: string
|
||||
category: string
|
||||
content?: string // For text files
|
||||
dataUrl?: string // For images and binary files
|
||||
error?: string
|
||||
}
|
||||
|
||||
export async function selectFiles(): Promise<ProcessedFile[]> {
|
||||
const options: vscode.OpenDialogOptions = {
|
||||
canSelectMany: true,
|
||||
openLabel: "Select",
|
||||
filters: {
|
||||
"All Files": ["*"],
|
||||
"Images": FILE_TYPE_CATEGORIES.images,
|
||||
"Documents": FILE_TYPE_CATEGORIES.documents,
|
||||
"Code Files": FILE_TYPE_CATEGORIES.code,
|
||||
"Data Files": FILE_TYPE_CATEGORIES.data,
|
||||
"Archives": FILE_TYPE_CATEGORIES.archives,
|
||||
"Config Files": FILE_TYPE_CATEGORIES.config,
|
||||
},
|
||||
}
|
||||
|
||||
const fileUris = await vscode.window.showOpenDialog(options)
|
||||
|
||||
if (!fileUris || fileUris.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
return await Promise.all(
|
||||
fileUris.map(async (uri: vscode.Uri) => {
|
||||
try {
|
||||
return await processFile(uri.fsPath)
|
||||
} catch (error) {
|
||||
const fileName = path.basename(uri.fsPath)
|
||||
return {
|
||||
name: fileName,
|
||||
path: uri.fsPath,
|
||||
size: 0,
|
||||
type: "unknown",
|
||||
category: "unknown",
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
export async function processFile(filePath: string): Promise<ProcessedFile> {
|
||||
const fileName = path.basename(filePath)
|
||||
const fileExt = path.extname(filePath).toLowerCase().slice(1)
|
||||
|
||||
// Check file size
|
||||
const stats = await fs.stat(filePath)
|
||||
if (stats.size > MAX_FILE_SIZE) {
|
||||
throw new Error(`File size (${formatFileSize(stats.size)}) exceeds the 10MB limit`)
|
||||
}
|
||||
|
||||
const mimeType = getMimeType(filePath)
|
||||
const category = getFileCategory(fileExt)
|
||||
|
||||
const processedFile: ProcessedFile = {
|
||||
name: fileName,
|
||||
path: filePath,
|
||||
size: stats.size,
|
||||
type: mimeType,
|
||||
category,
|
||||
}
|
||||
|
||||
// Handle different file types
|
||||
if (category === "images") {
|
||||
// Process images as data URLs (existing behavior)
|
||||
const buffer = await fs.readFile(filePath)
|
||||
const base64 = buffer.toString("base64")
|
||||
processedFile.dataUrl = `data:${mimeType};base64,${base64}`
|
||||
} else if (isTextFile(fileExt)) {
|
||||
// Read text files as content
|
||||
try {
|
||||
processedFile.content = await fs.readFile(filePath, "utf-8")
|
||||
} catch (error) {
|
||||
// If UTF-8 reading fails, treat as binary
|
||||
const buffer = await fs.readFile(filePath)
|
||||
const base64 = buffer.toString("base64")
|
||||
processedFile.dataUrl = `data:${mimeType};base64,${base64}`
|
||||
}
|
||||
} else {
|
||||
// Handle binary files as base64
|
||||
const buffer = await fs.readFile(filePath)
|
||||
const base64 = buffer.toString("base64")
|
||||
processedFile.dataUrl = `data:${mimeType};base64,${base64}`
|
||||
}
|
||||
|
||||
return processedFile
|
||||
}
|
||||
|
||||
// Backward compatibility function for existing image functionality
|
||||
export async function selectImages(): Promise<string[]> {
|
||||
const options: vscode.OpenDialogOptions = {
|
||||
canSelectMany: true,
|
||||
openLabel: "Select",
|
||||
filters: {
|
||||
Images: FILE_TYPE_CATEGORIES.images,
|
||||
},
|
||||
}
|
||||
|
||||
const fileUris = await vscode.window.showOpenDialog(options)
|
||||
|
||||
if (!fileUris || fileUris.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
return await Promise.all(
|
||||
fileUris.map(async (uri: vscode.Uri) => {
|
||||
const filePath = uri.fsPath
|
||||
const buffer = await fs.readFile(filePath)
|
||||
const base64 = buffer.toString("base64")
|
||||
const mimeType = getMimeType(filePath)
|
||||
return `data:${mimeType};base64,${base64}`
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
function getMimeType(filePath: string): string {
|
||||
const ext = path.extname(filePath).toLowerCase()
|
||||
|
||||
// Image types
|
||||
switch (ext) {
|
||||
case ".png": return "image/png"
|
||||
case ".jpeg":
|
||||
case ".jpg": return "image/jpeg"
|
||||
case ".gif": return "image/gif"
|
||||
case ".bmp": return "image/bmp"
|
||||
case ".webp": return "image/webp"
|
||||
case ".svg": return "image/svg+xml"
|
||||
|
||||
// Document types
|
||||
case ".pdf": return "application/pdf"
|
||||
case ".doc": return "application/msword"
|
||||
case ".docx": return "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
||||
case ".txt": return "text/plain"
|
||||
case ".rtf": return "application/rtf"
|
||||
case ".odt": return "application/vnd.oasis.opendocument.text"
|
||||
case ".md": return "text/markdown"
|
||||
|
||||
// Code types
|
||||
case ".js": return "text/javascript"
|
||||
case ".ts": return "text/typescript"
|
||||
case ".py": return "text/x-python"
|
||||
case ".java": return "text/x-java-source"
|
||||
case ".cpp":
|
||||
case ".c": return "text/x-c"
|
||||
case ".h":
|
||||
case ".hpp": return "text/x-c"
|
||||
case ".html": return "text/html"
|
||||
case ".css": return "text/css"
|
||||
case ".json": return "application/json"
|
||||
case ".xml": return "application/xml"
|
||||
case ".yaml":
|
||||
case ".yml": return "application/x-yaml"
|
||||
case ".php": return "text/x-php"
|
||||
case ".rb": return "text/x-ruby"
|
||||
case ".go": return "text/x-go"
|
||||
case ".rs": return "text/x-rust"
|
||||
case ".swift": return "text/x-swift"
|
||||
case ".kt": return "text/x-kotlin"
|
||||
case ".scala": return "text/x-scala"
|
||||
case ".sh": return "text/x-shellscript"
|
||||
case ".bat": return "text/x-msdos-batch"
|
||||
case ".ps1": return "text/x-powershell"
|
||||
|
||||
// Data types
|
||||
case ".csv": return "text/csv"
|
||||
case ".xls": return "application/vnd.ms-excel"
|
||||
case ".xlsx": return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||
case ".sql": return "text/x-sql"
|
||||
case ".db":
|
||||
case ".sqlite": return "application/x-sqlite3"
|
||||
|
||||
// Archive types
|
||||
case ".zip": return "application/zip"
|
||||
case ".rar": return "application/x-rar-compressed"
|
||||
case ".tar": return "application/x-tar"
|
||||
case ".gz": return "application/gzip"
|
||||
case ".7z": return "application/x-7z-compressed"
|
||||
|
||||
// Config types
|
||||
case ".ini": return "text/plain"
|
||||
case ".conf":
|
||||
case ".config": return "text/plain"
|
||||
case ".env": return "text/plain"
|
||||
case ".properties": return "text/plain"
|
||||
|
||||
default: return "application/octet-stream"
|
||||
}
|
||||
}
|
||||
|
||||
function getFileCategory(extension: string): string {
|
||||
for (const [category, extensions] of Object.entries(FILE_TYPE_CATEGORIES)) {
|
||||
if (extensions.includes(extension)) {
|
||||
return category
|
||||
}
|
||||
}
|
||||
return "other"
|
||||
}
|
||||
|
||||
function isTextFile(extension: string): boolean {
|
||||
return TEXT_FILE_EXTENSIONS.includes(extension)
|
||||
}
|
||||
|
||||
function formatFileSize(bytes: number): string {
|
||||
if (bytes === 0) return "0 Bytes"
|
||||
|
||||
const k = 1024
|
||||
const sizes = ["Bytes", "KB", "MB", "GB"]
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]
|
||||
}
|
||||
|
|
@ -54,6 +54,7 @@ export interface ExtensionMessage {
|
|||
| "action"
|
||||
| "state"
|
||||
| "selectedImages"
|
||||
| "selectedFiles"
|
||||
| "theme"
|
||||
| "workspaceUpdated"
|
||||
| "invoke"
|
||||
|
|
@ -123,6 +124,7 @@ export interface ExtensionMessage {
|
|||
invoke?: "newChat" | "sendMessage" | "primaryButtonClick" | "secondaryButtonClick" | "setChatBoxMessage"
|
||||
state?: ExtensionState
|
||||
images?: string[]
|
||||
files?: any[] // ProcessedFile array from process-files.ts
|
||||
filePaths?: string[]
|
||||
openedTabs?: Array<{
|
||||
label: string
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ export interface WebviewMessage {
|
|||
| "clearTask"
|
||||
| "didShowAnnouncement"
|
||||
| "selectImages"
|
||||
| "selectFiles"
|
||||
| "exportCurrentTask"
|
||||
| "shareCurrentTask"
|
||||
| "showTaskWithId"
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import Thumbnails from "../common/Thumbnails"
|
|||
import ModeSelector from "./ModeSelector"
|
||||
import { MAX_IMAGES_PER_MESSAGE } from "./ChatView"
|
||||
import ContextMenu from "./ContextMenu"
|
||||
import { VolumeX, Pin, Check, Image, WandSparkles, SendHorizontal } from "lucide-react"
|
||||
import { VolumeX, Pin, Check, Image, WandSparkles, SendHorizontal, Paperclip } from "lucide-react"
|
||||
import { IndexingStatusBadge } from "./IndexingStatusBadge"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { usePromptHistory } from "./hooks/usePromptHistory"
|
||||
|
|
@ -41,6 +41,7 @@ interface ChatTextAreaProps {
|
|||
setSelectedImages: React.Dispatch<React.SetStateAction<string[]>>
|
||||
onSend: () => void
|
||||
onSelectImages: () => void
|
||||
onSelectFiles?: () => void
|
||||
shouldDisableImages: boolean
|
||||
onHeightChange?: (height: number) => void
|
||||
mode: Mode
|
||||
|
|
@ -63,6 +64,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
setSelectedImages,
|
||||
onSend,
|
||||
onSelectImages,
|
||||
onSelectFiles,
|
||||
shouldDisableImages,
|
||||
onHeightChange,
|
||||
mode,
|
||||
|
|
@ -984,11 +986,11 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
</StandardTooltip>
|
||||
)}
|
||||
<IndexingStatusBadge />
|
||||
<StandardTooltip content={t("chat:addImages")}>
|
||||
<StandardTooltip content={t("chat:attachFiles")}>
|
||||
<button
|
||||
aria-label={t("chat:addImages")}
|
||||
aria-label={t("chat:attachFiles")}
|
||||
disabled={shouldDisableImages}
|
||||
onClick={!shouldDisableImages ? onSelectImages : undefined}
|
||||
onClick={!shouldDisableImages ? (onSelectFiles || onSelectImages) : undefined}
|
||||
className={cn(
|
||||
"relative inline-flex items-center justify-center",
|
||||
"bg-transparent border-none p-1.5",
|
||||
|
|
@ -1003,7 +1005,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
"opacity-40 cursor-not-allowed grayscale-[30%] hover:bg-transparent hover:border-[rgba(255,255,255,0.08)] active:bg-transparent",
|
||||
"mr-1",
|
||||
)}>
|
||||
<Image className="w-4 h-4" />
|
||||
<Paperclip className="w-4 h-4" />
|
||||
</button>
|
||||
</StandardTooltip>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -701,6 +701,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
const { info: model } = useSelectedModel(apiConfiguration)
|
||||
|
||||
const selectImages = useCallback(() => vscode.postMessage({ type: "selectImages" }), [])
|
||||
const selectFiles = useCallback(() => vscode.postMessage({ type: "selectFiles" }), [])
|
||||
|
||||
const shouldDisableImages =
|
||||
!model?.supportsImages || sendingDisabled || selectedImages.length >= MAX_IMAGES_PER_MESSAGE
|
||||
|
|
@ -1858,6 +1859,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
setSelectedImages={setSelectedImages}
|
||||
onSend={() => handleSendMessage(inputValue, selectedImages)}
|
||||
onSelectImages={selectImages}
|
||||
onSelectFiles={selectFiles}
|
||||
shouldDisableImages={shouldDisableImages}
|
||||
onHeightChange={() => {
|
||||
if (isAtBottom) {
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@
|
|||
"selectApiConfig": "Seleccioneu la configuració de l'API",
|
||||
"enhancePrompt": "Millora la sol·licitud amb context addicional",
|
||||
"addImages": "Afegeix imatges al missatge",
|
||||
"attachFiles": "Attach files to message",
|
||||
"sendMessage": "Envia el missatge",
|
||||
"stopTts": "Atura la síntesi de veu",
|
||||
"typeMessage": "Escriu un missatge...",
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@
|
|||
"selectApiConfig": "API-Konfiguration auswählen",
|
||||
"enhancePrompt": "Prompt mit zusätzlichem Kontext verbessern",
|
||||
"addImages": "Bilder zur Nachricht hinzufügen",
|
||||
"attachFiles": "Attach files to message",
|
||||
"sendMessage": "Nachricht senden",
|
||||
"stopTts": "Text-in-Sprache beenden",
|
||||
"typeMessage": "Nachricht eingeben...",
|
||||
|
|
|
|||
|
|
@ -122,6 +122,7 @@
|
|||
},
|
||||
"enhancePromptDescription": "The 'Enhance Prompt' button helps improve your prompt by providing additional context, clarification, or rephrasing. Try typing a prompt in here and clicking the button again to see how it works.",
|
||||
"addImages": "Add images to message",
|
||||
"attachFiles": "Attach files to message",
|
||||
"sendMessage": "Send message",
|
||||
"stopTts": "Stop text-to-speech",
|
||||
"typeMessage": "Type a message...",
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@
|
|||
"selectApiConfig": "Seleccionar configuración de API",
|
||||
"enhancePrompt": "Mejorar el mensaje con contexto adicional",
|
||||
"addImages": "Agregar imágenes al mensaje",
|
||||
"attachFiles": "Attach files to message",
|
||||
"sendMessage": "Enviar mensaje",
|
||||
"stopTts": "Detener texto a voz",
|
||||
"typeMessage": "Escribe un mensaje...",
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@
|
|||
"selectApiConfig": "Sélectionner la configuration de l'API",
|
||||
"enhancePrompt": "Améliorer la requête avec un contexte supplémentaire",
|
||||
"addImages": "Ajouter des images au message",
|
||||
"attachFiles": "Attach files to message",
|
||||
"sendMessage": "Envoyer le message",
|
||||
"stopTts": "Arrêter la synthèse vocale",
|
||||
"typeMessage": "Écrivez un message...",
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@
|
|||
"selectApiConfig": "एपीआई कॉन्फ़िगरेशन का चयन करें",
|
||||
"enhancePrompt": "अतिरिक्त संदर्भ के साथ प्रॉम्प्ट बढ़ाएँ",
|
||||
"addImages": "संदेश में चित्र जोड़ें",
|
||||
"attachFiles": "Attach files to message",
|
||||
"sendMessage": "संदेश भेजें",
|
||||
"stopTts": "टेक्स्ट-टू-स्पीच बंद करें",
|
||||
"typeMessage": "एक संदेश लिखें...",
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@
|
|||
"description": "Persona khusus yang menyesuaikan perilaku Roo."
|
||||
},
|
||||
"addImages": "Tambahkan gambar ke pesan",
|
||||
"attachFiles": "Attach files to message",
|
||||
"sendMessage": "Kirim pesan",
|
||||
"stopTts": "Hentikan text-to-speech",
|
||||
"typeMessage": "Ketik pesan...",
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@
|
|||
"selectApiConfig": "Seleziona la configurazione API",
|
||||
"enhancePrompt": "Migliora prompt con contesto aggiuntivo",
|
||||
"addImages": "Aggiungi immagini al messaggio",
|
||||
"attachFiles": "Attach files to message",
|
||||
"sendMessage": "Invia messaggio",
|
||||
"stopTts": "Interrompi sintesi vocale",
|
||||
"typeMessage": "Scrivi un messaggio...",
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@
|
|||
"selectApiConfig": "API構成を選択",
|
||||
"enhancePrompt": "追加コンテキストでプロンプトを強化",
|
||||
"addImages": "メッセージに画像を追加",
|
||||
"attachFiles": "Attach files to message",
|
||||
"sendMessage": "メッセージを送信",
|
||||
"stopTts": "テキスト読み上げを停止",
|
||||
"typeMessage": "メッセージを入力...",
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@
|
|||
"selectApiConfig": "API 구성 선택",
|
||||
"enhancePrompt": "추가 컨텍스트로 프롬프트 향상",
|
||||
"addImages": "메시지에 이미지 추가",
|
||||
"attachFiles": "Attach files to message",
|
||||
"sendMessage": "메시지 보내기",
|
||||
"stopTts": "텍스트 음성 변환 중지",
|
||||
"typeMessage": "메시지 입력...",
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@
|
|||
"description": "Gespecialiseerde persona's die het gedrag van Roo aanpassen."
|
||||
},
|
||||
"addImages": "Afbeeldingen toevoegen aan bericht",
|
||||
"attachFiles": "Attach files to message",
|
||||
"sendMessage": "Bericht verzenden",
|
||||
"stopTts": "Stop tekst-naar-spraak",
|
||||
"typeMessage": "Typ een bericht...",
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@
|
|||
"selectApiConfig": "Wybierz konfigurację API",
|
||||
"enhancePrompt": "Ulepsz podpowiedź dodatkowym kontekstem",
|
||||
"addImages": "Dodaj obrazy do wiadomości",
|
||||
"attachFiles": "Attach files to message",
|
||||
"sendMessage": "Wyślij wiadomość",
|
||||
"stopTts": "Zatrzymaj syntezę mowy",
|
||||
"typeMessage": "Wpisz wiadomość...",
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@
|
|||
"selectApiConfig": "Selecionar configuração da API",
|
||||
"enhancePrompt": "Aprimorar prompt com contexto adicional",
|
||||
"addImages": "Adicionar imagens à mensagem",
|
||||
"attachFiles": "Attach files to message",
|
||||
"sendMessage": "Enviar mensagem",
|
||||
"stopTts": "Parar conversão de texto em fala",
|
||||
"typeMessage": "Digite uma mensagem...",
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@
|
|||
"description": "Специализированные персоны, которые настраивают поведение Roo."
|
||||
},
|
||||
"addImages": "Добавить изображения к сообщению",
|
||||
"attachFiles": "Attach files to message",
|
||||
"sendMessage": "Отправить сообщение",
|
||||
"stopTts": "Остановить синтез речи",
|
||||
"typeMessage": "Введите сообщение...",
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@
|
|||
"selectApiConfig": "API yapılandırmasını seçin",
|
||||
"enhancePrompt": "Ek bağlamla istemi geliştir",
|
||||
"addImages": "Mesaja resim ekle",
|
||||
"attachFiles": "Attach files to message",
|
||||
"sendMessage": "Mesaj gönder",
|
||||
"stopTts": "Metin okumayı durdur",
|
||||
"typeMessage": "Bir mesaj yazın...",
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@
|
|||
"selectApiConfig": "Chọn cấu hình API",
|
||||
"enhancePrompt": "Nâng cao yêu cầu với ngữ cảnh bổ sung",
|
||||
"addImages": "Thêm hình ảnh vào tin nhắn",
|
||||
"attachFiles": "Attach files to message",
|
||||
"sendMessage": "Gửi tin nhắn",
|
||||
"stopTts": "Dừng chuyển văn bản thành giọng nói",
|
||||
"typeMessage": "Nhập tin nhắn...",
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@
|
|||
"selectApiConfig": "选择 API 配置",
|
||||
"enhancePrompt": "增强提示词",
|
||||
"addImages": "添加图片到消息",
|
||||
"attachFiles": "Attach files to message",
|
||||
"sendMessage": "发送消息",
|
||||
"stopTts": "停止文本转语音",
|
||||
"typeMessage": "输入消息...",
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@
|
|||
"selectApiConfig": "選取 API 設定",
|
||||
"enhancePrompt": "使用額外內容增強提示",
|
||||
"addImages": "新增圖片到訊息中",
|
||||
"attachFiles": "Attach files to message",
|
||||
"sendMessage": "傳送訊息",
|
||||
"stopTts": "停止文字轉語音",
|
||||
"typeMessage": "輸入訊息...",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue