-
-
- {AgentStatusMap[curAgentState].message}
-
+
);
}
diff --git a/frontend/src/i18n/translation.json b/frontend/src/i18n/translation.json
index 8d1f2617fa1..795c60e051f 100644
--- a/frontend/src/i18n/translation.json
+++ b/frontend/src/i18n/translation.json
@@ -72,19 +72,43 @@
"en": "Options",
"zh-CN": "选项",
"zh-TW": "選項",
- "de": "Optionen"
+ "de": "Optionen",
+ "ko-KR": "옵션",
+ "no": "Alternativer",
+ "it": "Opzioni",
+ "pt": "Opções",
+ "es": "Opciones",
+ "ar": "خيارات",
+ "fr": "Options",
+ "tr": "Seçenekler"
},
"CODE_EDITOR$FILE_SAVE_ERROR": {
"en": "An unknown error occurred while saving the file",
"zh-CN": "文件保存时发生未知错误",
"zh-TW": "文件保存時發生未知錯誤",
- "de": "Beim Speichern der Datei ist ein unbekannter Fehler aufgetreten"
+ "de": "Beim Speichern der Datei ist ein unbekannter Fehler aufgetreten",
+ "ko-KR": "파일 저장 중 알 수 없는 오류가 발생했습니다",
+ "no": "En ukjent feil oppstod under lagring av filen",
+ "it": "Si è verificato un errore sconosciuto durante il salvataggio del file",
+ "pt": "Ocorreu um erro desconhecido ao salvar o arquivo",
+ "es": "Ocurrió un error desconocido al guardar el archivo",
+ "ar": "حدث خطأ غير معروف أثناء حفظ الملف",
+ "fr": "Une erreur inconnue s'est produite lors de l'enregistrement du fichier",
+ "tr": "Dosya kaydedilirken bilinmeyen bir hata oluştu"
},
"CODE_EDITOR$EMPTY_MESSAGE": {
"en": "No file selected.",
"zh-CN": "文件未选中",
"zh-TW": "未選取任何文件。",
- "de": "Keine Datei ausgewählt."
+ "de": "Keine Datei ausgewählt.",
+ "ko-KR": "선택된 파일이 없습니다.",
+ "no": "Ingen fil valgt.",
+ "it": "Nessun file selezionato.",
+ "pt": "Nenhum arquivo selecionado.",
+ "es": "Ningún archivo seleccionado.",
+ "ar": "لم يتم اختيار أي ملف.",
+ "fr": "Aucun fichier sélectionné.",
+ "tr": "Hiçbir dosya seçilmedi."
},
"FILE_SERVICE$SELECT_FILE_ERROR": {
"en": "Error selecting file. Please try again.",
@@ -336,12 +360,30 @@
"CONFIGURATION$SECURITY_SELECT_LABEL": {
"en": "Security analyzer",
"de": "Sicherheitsanalysator",
- "zh-CN": "安全分析器"
+ "zh-CN": "安全分析器",
+ "ko-KR": "보안 분석기",
+ "no": "Sikkerhetsanalysator",
+ "zh-TW": "安全分析器",
+ "it": "Analizzatore di sicurezza",
+ "pt": "Analisador de segurança",
+ "es": "Analizador de seguridad",
+ "ar": "محلل الأمان",
+ "fr": "Analyseur de sécurité",
+ "tr": "Güvenlik analizörü"
},
"CONFIGURATION$SECURITY_SELECT_PLACEHOLDER": {
"en": "Select a security analyzer (optional)",
"de": "Wählen Sie einen Sicherheitsanalysator (optional)",
- "zh-CN": "选择一个安全分析器(可选)"
+ "zh-CN": "选择一个安全分析器(可选)",
+ "ko-KR": "보안 분석기 선택 (선택사항)",
+ "no": "Velg en sikkerhetsanalysator (valgfritt)",
+ "zh-TW": "選擇安全分析器(可選)",
+ "it": "Seleziona un analizzatore di sicurezza (opzionale)",
+ "pt": "Selecione um analisador de segurança (opcional)",
+ "es": "Seleccione un analizador de seguridad (opcional)",
+ "ar": "اختر محلل أمان (اختياري)",
+ "fr": "Sélectionnez un analyseur de sécurité (facultatif)",
+ "tr": "Bir güvenlik analizörü seçin (isteğe bağlı)"
},
"CONFIGURATION$MODAL_CLOSE_BUTTON_LABEL": {
"en": "Close",
@@ -386,100 +428,268 @@
},
"CONFIGURATION$SETTINGS_NEED_UPDATE_MESSAGE": {
"en": "We've changed some settings in the latest update. Take a minute to review.",
- "de": "Mit dem letzten Update haben wir ein paar Einstellungen geändert. Bitte kontrollieren Ihre Einstellungen."
+ "de": "Mit dem letzten Update haben wir ein paar Einstellungen geändert. Bitte kontrollieren Ihre Einstellungen.",
+ "zh-CN": "我们在最新更新中更改了一些设置。请花点时间检查一下。",
+ "ko-KR": "최신 업데이트에서 일부 설정을 변경했습니다. 잠시 시간을 내어 검토해 주세요.",
+ "no": "Vi har endret noen innstillinger i den siste oppdateringen. Ta deg tid til å se gjennom dem.",
+ "zh-TW": "我們在最新更新中更改了一些設定。請花點時間檢查一下。",
+ "it": "Abbiamo modificato alcune impostazioni nell'ultimo aggiornamento. Prenditi un momento per rivederle.",
+ "pt": "Alteramos algumas configurações na última atualização. Reserve um momento para revisar.",
+ "es": "Hemos cambiado algunas configuraciones en la última actualización. Tómate un momento para revisarlas.",
+ "ar": "لقد قمنا بتغيير بعض الإعدادات في التحديث الأخير. خذ دقيقة لمراجعتها.",
+ "fr": "Nous avons modifié certains paramètres dans la dernière mise à jour. Prenez un moment pour les examiner.",
+ "tr": "Son güncellemede bazı ayarları değiştirdik. Gözden geçirmek için bir dakikanızı ayırın."
},
"CONFIGURATION$AGENT_LOADING": {
- "en": "Please wait while the agent loads. This may take a few seconds...",
- "de": "Bitte warten Sie, während der Agent lädt. Das kann ein paar Sekunden dauern..."
+ "en": "Please wait while the agent loads. This may take a few minutes...",
+ "de": "Bitte warten Sie, während der Agent lädt. Das kann ein paar Minuten dauern...",
+ "zh-CN": "请稍候,代理正在加载中。这可能需要几分钟...",
+ "ko-KR": "에이전트가 로드되는 동안 기다려 주세요. 몇 분 정도 걸릴 수 있습니다...",
+ "no": "Vennligst vent mens agenten laster. Dette kan ta noen minutter...",
+ "zh-TW": "請稍候,代理正在載入中。這可能需要幾分鐘...",
+ "it": "Attendere mentre l'agente si carica. Potrebbe richiedere alcuni minuti...",
+ "pt": "Por favor, aguarde enquanto o agente carrega. Isso pode levar alguns minutos...",
+ "es": "Por favor, espere mientras el agente se carga. Esto puede tardar unos minutos...",
+ "ar": "يرجى الانتظار أثناء تحميل الوكيل. قد يستغرق هذا بضع دقائق...",
+ "fr": "Veuillez patienter pendant le chargement de l'agent. Cela peut prendre quelques minutes...",
+ "tr": "Lütfen ajan yüklenirken bekleyin. Bu birkaç dakika sürebilir..."
},
"CONFIGURATION$AGENT_RUNNING": {
"en": "Please stop the agent before editing these settings.",
- "de": "Bitte beenden Sie den Agenten vor der Bearbeitung der Einstellungen."
+ "de": "Bitte beenden Sie den Agenten vor der Bearbeitung der Einstellungen.",
+ "zh-CN": "请在编辑这些设置之前停止代理。",
+ "ko-KR": "이 설정을 편집하기 전에 에이전트를 중지해 주세요.",
+ "no": "Vennligst stopp agenten før du redigerer disse innstillingene.",
+ "zh-TW": "請在編輯這些設定之前停止代理。",
+ "it": "Si prega di fermare l'agente prima di modificare queste impostazioni.",
+ "pt": "Por favor, pare o agente antes de editar estas configurações.",
+ "es": "Por favor, detenga el agente antes de editar estas configuraciones.",
+ "ar": "يرجى إيقاف الوكيل قبل تعديل هذه الإعدادات.",
+ "fr": "Veuillez arrêter l'agent avant de modifier ces paramètres.",
+ "tr": "Bu ayarları düzenlemeden önce lütfen ajanı durdurun."
},
"CONFIGURATION$ERROR_FETCH_MODELS": {
"en": "Failed to fetch models and agents",
"zh-CN": "获取模型和智能体失败",
- "de": "Fehler beim Abrufen der Modelle und Agenten"
+ "de": "Fehler beim Abrufen der Modelle und Agenten",
+ "zh-TW": "獲取模型和智能體失敗",
+ "es": "Error al obtener modelos y agentes",
+ "fr": "Échec de la récupération des modèles et des agents",
+ "it": "Impossibile recuperare modelli e agenti",
+ "pt": "Falha ao buscar modelos e agentes",
+ "ko-KR": "모델 및 에이전트 가져오기 실패",
+ "ar": "فشل في جلب النماذج والوكلاء",
+ "tr": "Modeller ve ajanlar getirilemedi",
+ "no": "Kunne ikke hente modeller og agenter"
},
"SESSION$SERVER_CONNECTED_MESSAGE": {
"en": "Connected to server",
"zh-CN": "已连接到服务器",
- "de": "Verbindung zum Server hergestellt"
+ "de": "Verbindung zum Server hergestellt",
+ "zh-TW": "已連接到伺服器",
+ "es": "Conectado al servidor",
+ "fr": "Connecté au serveur",
+ "it": "Connesso al server",
+ "pt": "Conectado ao servidor",
+ "ko-KR": "서버에 연결됨",
+ "ar": "تم الاتصال بالخادم",
+ "tr": "Sunucuya bağlandı",
+ "no": "Koblet til server"
},
"SESSION$SESSION_HANDLING_ERROR_MESSAGE": {
"en": "Error handling message",
"zh-CN": "处理消息时发生错误",
- "de": "Fehler beim Verarbeiten der Nachricht"
+ "de": "Fehler beim Verarbeiten der Nachricht",
+ "zh-TW": "處理訊息時發生錯誤",
+ "es": "Error al procesar el mensaje",
+ "fr": "Erreur lors du traitement du message",
+ "it": "Errore durante l'elaborazione del messaggio",
+ "pt": "Erro ao processar a mensagem",
+ "ko-KR": "메시지 처리 중 오류 발생",
+ "ar": "خطأ في معالجة الرسالة",
+ "tr": "Mesaj işlenirken hata oluştu",
+ "no": "Feil ved behandling av melding"
},
"SESSION$SESSION_CONNECTION_ERROR_MESSAGE": {
"en": "Error connecting to session",
"zh-CN": "连接到会话时发生错误",
- "de": "Verbindung zur Sitzung fehlgeschlagen"
+ "de": "Verbindung zur Sitzung fehlgeschlagen",
+ "zh-TW": "連接到會話時發生錯誤",
+ "es": "Error al conectar con la sesión",
+ "fr": "Erreur de connexion à la session",
+ "it": "Errore durante la connessione alla sessione",
+ "pt": "Erro ao conectar à sessão",
+ "ko-KR": "세션 연결 오류",
+ "ar": "خطأ في الاتصال بالجلسة",
+ "tr": "Oturuma bağlanırken hata oluştu",
+ "no": "Feil ved tilkobling til økt"
},
"SESSION$SOCKET_NOT_INITIALIZED_ERROR_MESSAGE": {
"en": "Socket not initialized",
"zh-CN": "Socket 未初始化",
- "de": "Socket nicht initialisiert"
+ "de": "Socket nicht initialisiert",
+ "zh-TW": "Socket 未初始化",
+ "es": "Socket no inicializado",
+ "fr": "Socket non initialisé",
+ "it": "Socket non inizializzato",
+ "pt": "Socket não inicializado",
+ "ko-KR": "소켓이 초기화되지 않았습니다",
+ "ar": "لم يتم تهيئة Socket",
+ "tr": "Soket başlatılmadı"
},
"EXPLORER$UPLOAD_ERROR_MESSAGE": {
"en": "Error uploading file",
"zh-CN": "上传文件时发生错误",
- "de": "Fehler beim Hochladen der Datei"
+ "de": "Fehler beim Hochladen der Datei",
+ "zh-TW": "上傳檔案時發生錯誤",
+ "es": "Error al subir el archivo",
+ "fr": "Erreur lors du téléchargement du fichier",
+ "it": "Errore durante il caricamento del file",
+ "pt": "Erro ao fazer upload do arquivo",
+ "ko-KR": "파일 업로드 중 오류 발생",
+ "ar": "خطأ في تحميل الملف",
+ "tr": "Dosya yüklenirken hata oluştu"
},
"EXPLORER$LABEL_DROP_FILES": {
"en": "Drop files here",
"zh-CN": "将文件拖到这里",
- "de": "Dateien hier ablegen"
+ "de": "Dateien hier ablegen",
+ "zh-TW": "將檔案拖曳至此",
+ "es": "Suelta los archivos aquí",
+ "fr": "Déposez les fichiers ici",
+ "it": "Trascina i file qui",
+ "pt": "Solte os arquivos aqui",
+ "ko-KR": "파일을 여기에 놓으세요",
+ "ar": "أسقط الملفات هنا",
+ "tr": "Dosyaları buraya bırakın"
},
"EXPLORER$LABEL_WORKSPACE": {
"en": "Workspace",
"zh-CN": "工作区",
- "de": "Arbeitsbereich"
+ "de": "Arbeitsbereich",
+ "zh-TW": "工作區",
+ "es": "Espacio de trabajo",
+ "fr": "Espace de travail",
+ "it": "Area di lavoro",
+ "pt": "Espaço de trabalho",
+ "ko-KR": "작업 공간",
+ "ar": "مساحة العمل",
+ "tr": "Çalışma alanı"
},
"EXPLORER$EMPTY_WORKSPACE_MESSAGE": {
"en": "No files in workspace",
"zh-CN": "工作区没有文件",
- "de": "Keine Dateien im Arbeitsbereich"
+ "de": "Keine Dateien im Arbeitsbereich",
+ "zh-TW": "工作區沒有檔案",
+ "es": "No hay archivos en el espacio de trabajo",
+ "fr": "Aucun fichier dans l'espace de travail",
+ "it": "Nessun file nell'area di lavoro",
+ "pt": "Nenhum arquivo no espaço de trabalho",
+ "ko-KR": "작업 공간에 파일이 없습니다",
+ "ar": "لا توجد ملفات في مساحة العمل",
+ "tr": "Çalışma alanında dosya yok"
},
"EXPLORER$LOADING_WORKSPACE_MESSAGE": {
"en": "Loading workspace...",
"zh-CN": "正在加载工作区...",
- "de": "Arbeitsbereich wird geladen..."
+ "de": "Arbeitsbereich wird geladen...",
+ "zh-TW": "正在載入工作區...",
+ "es": "Cargando espacio de trabajo...",
+ "fr": "Chargement de l'espace de travail...",
+ "it": "Caricamento dell'area di lavoro...",
+ "pt": "Carregando espaço de trabalho...",
+ "ko-KR": "작업 공간 로딩 중...",
+ "ar": "جارٍ تحميل مساحة العمل...",
+ "tr": "Çalışma alanı yükleniyor..."
},
"EXPLORER$REFRESH_ERROR_MESSAGE": {
"en": "Error refreshing workspace",
"zh-CN": "工作区刷新错误",
- "de": "Fehler beim Aktualisieren des Arbeitsbereichs"
+ "de": "Fehler beim Aktualisieren des Arbeitsbereichs",
+ "zh-TW": "工作區重新整理錯誤",
+ "es": "Error al actualizar el espacio de trabajo",
+ "fr": "Erreur lors de l'actualisation de l'espace de travail",
+ "it": "Errore durante l'aggiornamento dell'area di lavoro",
+ "pt": "Erro ao atualizar o espaço de trabalho",
+ "ko-KR": "작업 공간 새로 고침 오류",
+ "ar": "خطأ في تحديث مساحة العمل",
+ "tr": "Çalışma alanı yenilenirken hata oluştu"
},
"EXPLORER$UPLOAD_SUCCESS_MESSAGE": {
"en": "Successfully uploaded {{count}} file(s)",
"zh-CN": "成功上传 {{count}} 个文件",
- "de": "Erfolgreich {{count}} Datei(en) hochgeladen"
+ "de": "Erfolgreich {{count}} Datei(en) hochgeladen",
+ "zh-TW": "成功上傳 {{count}} 個檔案",
+ "es": "Se subieron {{count}} archivo(s) con éxito",
+ "fr": "{{count}} fichier(s) téléchargé(s) avec succès",
+ "it": "Caricato con successo {{count}} file",
+ "pt": "{{count}} arquivo(s) carregado(s) com sucesso",
+ "ko-KR": "{{count}}개의 파일을 성공적으로 업로드했습니다",
+ "ar": "تم تحميل {{count}} ملف (ملفات) بنجاح",
+ "tr": "{{count}} dosya başarıyla yüklendi"
},
"EXPLORER$NO_FILES_UPLOADED_MESSAGE": {
"en": "No files were uploaded",
"zh-CN": "没有文件上传",
- "de": "Keine Dateien wurden hochgeladen"
+ "de": "Keine Dateien wurden hochgeladen",
+ "zh-TW": "沒有檔案被上傳",
+ "es": "No se subieron archivos",
+ "fr": "Aucun fichier n'a été téléchargé",
+ "it": "Nessun file è stato caricato",
+ "pt": "Nenhum arquivo foi carregado",
+ "ko-KR": "업로드된 파일이 없습니다",
+ "ar": "لم يتم تحميل أي ملفات",
+ "tr": "Hiçbir dosya yüklenmedi"
},
"EXPLORER$UPLOAD_PARTIAL_SUCCESS_MESSAGE": {
"en": "{{count}} file(s) were skipped during upload",
+ "de": "{{count}} Datei(en) wurden während des Hochladens übersprungen",
"zh-CN": "{{count}} 个文件在上传过程中被跳过",
- "de": "{{count}} Datei(en) wurden während des Hochladens übersprungen"
+ "zh-TW": "{{count}} 個檔案在上傳過程中被跳過",
+ "es": "Se omitieron {{count}} archivo(s) durante la carga",
+ "fr": "{{count}} fichier(s) ont été ignorés pendant le téléchargement",
+ "it": "{{count}} file sono stati saltati durante il caricamento",
+ "pt": "{{count}} arquivo(s) foram ignorados durante o upload",
+ "ko-KR": "업로드 중 {{count}}개의 파일이 건너뛰어졌습니다",
+ "ar": "تم تخطي {{count}} ملف (ملفات) أثناء التحميل",
+ "tr": "Yükleme sırasında {{count}} dosya atlandı"
},
"EXPLORER$UPLOAD_UNEXPECTED_RESPONSE_MESSAGE": {
"en": "Unexpected response structure from server",
"zh-CN": "服务器响应结构不符合预期",
- "de": "Unerwartetes Antwortformat vom Server"
+ "de": "Unerwartetes Antwortformat vom Server",
+ "zh-TW": "伺服器回應結構不符合預期",
+ "es": "Estructura de respuesta inesperada del servidor",
+ "fr": "Structure de réponse inattendue du serveur",
+ "it": "Struttura di risposta inaspettata dal server",
+ "pt": "Estrutura de resposta inesperada do servidor",
+ "ko-KR": "서버로부터 예상치 못한 응답 구조",
+ "ar": "بنية استجابة غير متوقعة من الخادم",
+ "tr": "Sunucudan beklenmeyen yanıt yapısı"
},
"LOAD_SESSION$MODAL_TITLE": {
"en": "Return to existing session?",
"de": "Zurück zu vorhandener Sitzung?",
"zh-CN": "是否继续未完成的会话?",
- "zh-TW": "是否繼續未完成的會話?"
+ "zh-TW": "是否繼續未完成的會話?",
+ "es": "¿Volver a la sesión existente?",
+ "fr": "Revenir à la session existante ?",
+ "it": "Tornare alla sessione esistente?",
+ "pt": "Retornar à sessão existente?",
+ "ko-KR": "기존 세션으로 돌아가시겠습니까?",
+ "ar": "العودة إلى الجلسة الحالية؟",
+ "tr": "Mevcut oturuma dönmek ister misiniz?"
},
"LOAD_SESSION$MODAL_CONTENT": {
"en": "You seem to have an ongoing session. Would you like to pick up where you left off, or start fresh?",
"de": "Sie haben eine aktive Sitzung. Möchten Sie die Arbeit an der vorherigen Stelle fortsetzen oder von vorne anfangen?",
+ "es": "Parece que tienes una sesión en curso. ¿Te gustaría continuar donde lo dejaste o empezar de nuevo?",
+ "fr": "Il semble que vous ayez une session en cours. Souhaitez-vous reprendre là où vous vous êtes arrêté ou recommencer à zéro ?",
+ "it": "Sembra che tu abbia una sessione in corso. Vorresti riprendere da dove hai lasciato o ricominciare da capo?",
+ "pt": "Parece que você tem uma sessão em andamento. Gostaria de continuar de onde parou ou começar do zero?",
+ "ko-KR": "진행 중인 세션이 있는 것 같습니다. 중단한 곳에서 계속하시겠습니까, 아니면 새로 시작하시겠습니까?",
+ "ar": "يبدو أن لديك جلسة جارية. هل ترغب في استكمال ما توقفت عنده أم البدء من جديد؟",
+ "tr": "Devam eden bir oturumunuz var gibi görünüyor. Kaldığınız yerden devam etmek mi yoksa yeniden başlamak mı istersiniz?",
"zh-CN": "您似乎有一个未完成的任务。您想继续之前的工作还是重新开始?",
"zh-TW": "您似乎有一個未完成的任務。您想從上次離開的地方繼續還是重新開始?"
},
@@ -487,103 +697,276 @@
"en": "Resume Session",
"de": "Sitzung fortsetzen",
"zh-CN": "恢复会话",
- "zh-TW": "恢復會話"
+ "zh-TW": "恢復會話",
+ "es": "Reanudar sesión",
+ "fr": "Reprendre la session",
+ "it": "Riprendi sessione",
+ "pt": "Retomar sessão",
+ "ko-KR": "세션 재개",
+ "ar": "استئناف الجلسة",
+ "tr": "Oturumu Devam Ettir"
},
"LOAD_SESSION$START_NEW_SESSION_MODAL_ACTION_LABEL": {
"en": "Start New Session",
"de": "Neue Sitzung starten",
"zh-CN": "开始新会话",
- "zh-TW": "開始新會話"
+ "zh-TW": "開始新會話",
+ "es": "Iniciar nueva sesión",
+ "fr": "Démarrer une nouvelle session",
+ "it": "Avvia nuova sessione",
+ "pt": "Iniciar nova sessão",
+ "ko-KR": "새 세션 시작",
+ "ar": "بدء جلسة جديدة",
+ "tr": "Yeni Oturum Başlat"
},
"FEEDBACK$MODAL_TITLE": {
- "en": "Share feedback"
+ "en": "Share feedback",
+ "de": "Feedback teilen",
+ "zh-CN": "分享反馈",
+ "zh-TW": "分享反饋",
+ "es": "Compartir comentarios",
+ "fr": "Partager des commentaires",
+ "it": "Condividi feedback",
+ "pt": "Compartilhar feedback",
+ "ko-KR": "피드백 공유",
+ "ar": "مشاركة التعليقات",
+ "tr": "Geri bildirim paylaş"
},
"FEEDBACK$MODAL_CONTENT": {
- "en": "To help us improve, we collect feedback from your interactions to improve our prompts. By submitting this form, you consent to us collecting this data."
+ "en": "To help us improve, we collect feedback from your interactions to improve our prompts. By submitting this form, you consent to us collecting this data.",
+ "de": "Um uns zu verbessern, sammeln wir Feedback aus Ihren Interaktionen, um unsere Prompts zu verbessern. Durch das Absenden dieses Formulars stimmen Sie der Erfassung dieser Daten zu.",
+ "zh-CN": "为了帮助我们改进,我们会收集您的互动反馈以改进我们的提示。提交此表单即表示您同意我们收集这些数据。",
+ "zh-TW": "為了幫助我們改進,我們會收集您的互動反饋以改進我們的提示。提交此表單即表示您同意我們收集這些數據。",
+ "es": "Para ayudarnos a mejorar, recopilamos comentarios de sus interacciones para mejorar nuestras indicaciones. Al enviar este formulario, usted consiente que recopilemos estos datos.",
+ "fr": "Pour nous aider à nous améliorer, nous recueillons des commentaires de vos interactions pour améliorer nos invites. En soumettant ce formulaire, vous consentez à ce que nous collections ces données.",
+ "it": "Per aiutarci a migliorare, raccogliamo feedback dalle tue interazioni per migliorare i nostri prompt. Inviando questo modulo, acconsenti alla raccolta di questi dati.",
+ "pt": "Para nos ajudar a melhorar, coletamos feedback de suas interações para aprimorar nossas sugestões. Ao enviar este formulário, você consente que coletemos esses dados.",
+ "ko-KR": "개선을 위해 귀하의 상호 작용에서 피드백을 수집하여 프롬프트를 개선합니다. 이 양식을 제출함으로써 귀하는 이 데이터 수집에 동의하게 됩니다.",
+ "ar": "لمساعدتنا على التحسين، نقوم بجمع التعليقات من تفاعلاتك لتحسين مطالباتنا. من خلال إرسال هذا النموذج، فإنك توافق على جمعنا لهذه البيانات.",
+ "tr": "Kendimizi geliştirmemize yardımcı olmak için, etkileşimlerinizden geri bildirim toplayarak ipuçlarımızı iyileştiriyoruz. Bu formu göndererek, bu verileri toplamamıza izin vermiş olursunuz."
},
"FEEDBACK$EMAIL_LABEL": {
- "en": "Your email"
+ "en": "Your email",
+ "de": "Ihre E-Mail-Adresse",
+ "zh-CN": "您的电子邮箱",
+ "zh-TW": "您的電子郵箱",
+ "es": "Su correo electrónico",
+ "fr": "Votre e-mail",
+ "it": "La tua email",
+ "pt": "Seu e-mail",
+ "ko-KR": "귀하의 이메일",
+ "ar": "بريدك الإلكتروني",
+ "tr": "E-posta adresiniz"
},
"FEEDBACK$CONTRIBUTE_LABEL": {
- "en": "Contribute to public dataset"
+ "en": "Contribute to public dataset",
+ "de": "Zum öffentlichen Datensatz beitragen",
+ "zh-CN": "贡献到公共数据集",
+ "zh-TW": "貢獻到公共數據集",
+ "es": "Contribuir al conjunto de datos público",
+ "fr": "Contribuer à l'ensemble de données public",
+ "it": "Contribuisci al dataset pubblico",
+ "pt": "Contribuir para o conjunto de dados público",
+ "ko-KR": "공개 데이터셋에 기여",
+ "ar": "المساهمة في مجموعة البيانات العامة",
+ "tr": "Genel veri setine katkıda bulun"
},
"FEEDBACK$SHARE_LABEL": {
- "en": "Share"
+ "en": "Share",
+ "de": "Teilen",
+ "zh-CN": "分享",
+ "zh-TW": "分享",
+ "es": "Compartir",
+ "fr": "Partager",
+ "it": "Condividi",
+ "pt": "Compartilhar",
+ "ko-KR": "공유",
+ "ar": "مشاركة",
+ "tr": "Paylaş"
},
"FEEDBACK$CANCEL_LABEL": {
- "en": "Cancel"
+ "en": "Cancel",
+ "de": "Abbruch",
+ "zh-CN": "取消",
+ "zh-TW": "取消",
+ "es": "Cancelar",
+ "fr": "Annuler",
+ "it": "Annulla",
+ "pt": "Cancelar",
+ "ko-KR": "취소",
+ "ar": "إلغاء",
+ "tr": "İptal"
},
"FEEDBACK$EMAIL_PLACEHOLDER": {
"en": "Enter your email address."
},
"CHAT_INTERFACE$INITIALIZING_AGENT_LOADING_MESSAGE": {
- "en": "Initializing agent (may take up to 10 seconds)...",
- "zh-CN": "正在初始化智能体(可能需要 10 秒以上时间)",
- "de": "Agent wird initialisiert (kann bis zu 10 Sekunden dauern)...",
- "ko-KR": "에이전트 설치중(10초 정도 걸립니다)...",
- "no": "Initialiserer agent (det kan ta opptil 10 sekunder)...",
- "zh-TW": "初始化智能體(可能需要 10 秒以上時間)",
- "it": "Inizializzazione dell'agente (può richiedere fino a 10 secondi)...",
- "pt": "Inicializando o agente (pode levar até 10 segundos)...",
- "es": "Inicializando el agente (puede tardar hasta 10 segundos)...",
- "ar": "جاري تهيئة الوكيل (قد يستغرق حتى 10 ثواني)...",
- "fr": "Initialisation de l'agent (peut prendre jusqu'à 10 secondes)...",
- "tr": "Ajan başlatılıyor (bu işlem 10 saniye kadar sürebilir)..."
+ "en": "Starting up!",
+ "de": "Wird gestartet!",
+ "zh-CN": "正在启动!",
+ "zh-TW": "正在啟動!",
+ "ko-KR": "시작 중입니다!",
+ "no": "Starter opp!",
+ "it": "Avvio in corso!",
+ "pt": "Iniciando!",
+ "es": "¡Iniciando!",
+ "ar": "جارٍ البدء!",
+ "fr": "Démarrage en cours !",
+ "tr": "Başlatılıyor!"
},
"CHAT_INTERFACE$AGENT_INIT_MESSAGE": {
"en": "Agent is initialized, waiting for task...",
"de": "Agent ist initialisiert und wartet auf Aufgabe...",
- "zh-CN": "智能体已初始化,等待任务中..."
+ "zh-CN": "智能体已初始化,等待任务中...",
+ "zh-TW": "智能體已初始化,等待任務中...",
+ "ko-KR": "에이전트가 초기화되었습니다. 작업을 기다리는 중...",
+ "no": "Agenten er initialisert, venter på oppgave...",
+ "it": "L'agente è inizializzato, in attesa di compiti...",
+ "pt": "Agente inicializado, aguardando tarefa...",
+ "es": "El agente está inicializado, esperando tarea...",
+ "ar": "تم تهيئة الوكيل، في انتظار المهمة...",
+ "fr": "L'agent est initialisé, en attente de tâche...",
+ "tr": "Ajan başlatıldı, görev bekleniyor..."
},
"CHAT_INTERFACE$AGENT_RUNNING_MESSAGE": {
"en": "Agent is running task",
"de": "Agent führt Aufgabe aus",
- "zh-CN": "智能体正在执行任务..."
+ "zh-CN": "智能体正在执行任务...",
+ "zh-TW": "智能體正在執行任務...",
+ "ko-KR": "에이전트가 작업을 실행 중입니다",
+ "no": "Agenten utfører oppgave",
+ "it": "L'agente sta eseguendo il compito",
+ "pt": "O agente está executando a tarefa",
+ "es": "El agente está ejecutando la tarea",
+ "ar": "الوكيل يقوم بتنفيذ المهمة",
+ "fr": "L'agent exécute la tâche",
+ "tr": "Ajan görevi yürütüyor"
},
"CHAT_INTERFACE$AGENT_AWAITING_USER_INPUT_MESSAGE": {
"en": "Agent is awaiting user input...",
"de": "Agent wartet auf Benutzereingabe...",
- "zh-CN": "智能体正在等待用户输入..."
+ "zh-CN": "智能体正在等待用户输入...",
+ "zh-TW": "智能體正在等待用戶輸入...",
+ "ko-KR": "에이전트가 사용자 입력을 기다리고 있습니다...",
+ "no": "Agenten venter på brukerinndata...",
+ "it": "L'agente è in attesa dell'input dell'utente...",
+ "pt": "O agente está aguardando a entrada do usuário...",
+ "es": "El agente está esperando la entrada del usuario...",
+ "ar": "الوكيل في انتظار إدخال المستخدم...",
+ "fr": "L'agent attend l'entrée de l'utilisateur...",
+ "tr": "Ajan kullanıcı girdisini bekliyor..."
},
"CHAT_INTERFACE$AGENT_PAUSED_MESSAGE": {
"en": "Agent has paused.",
"de": "Agent pausiert.",
- "zh-CN": "智能体已暂停"
+ "zh-CN": "智能体已暂停",
+ "zh-TW": "智能體已暫停",
+ "ko-KR": "에이전트가 일시 중지되었습니다.",
+ "no": "Agenten har pauset.",
+ "it": "L'agente ha messo in pausa.",
+ "pt": "O agente foi pausado.",
+ "es": "El agente ha pausado.",
+ "ar": "توقف الوكيل مؤقتًا.",
+ "fr": "L'agent a mis en pause.",
+ "tr": "Ajan duraklatıldı."
},
"CHAT_INTERFACE$AGENT_STOPPED_MESSAGE": {
"en": "Agent has stopped.",
"de": "Agent hat angehalten.",
- "zh-CN": "智能体已停止"
+ "zh-CN": "智能体已停止",
+ "zh-TW": "智能體已停止",
+ "ko-KR": "에이전트가 중지되었습니다.",
+ "no": "Agenten har stoppet.",
+ "it": "L'agente si è fermato.",
+ "pt": "O agente parou.",
+ "es": "El agente se ha detenido.",
+ "ar": "توقف الوكيل.",
+ "fr": "L'agent s'est arrêté.",
+ "tr": "Ajan durdu."
},
"CHAT_INTERFACE$AGENT_FINISHED_MESSAGE": {
"en": "Agent has finished the task.",
"de": "Agent hat die Aufgabe erledigt.",
- "zh-CN": "智能体已完成任务"
+ "zh-CN": "智能体已完成任务",
+ "zh-TW": "智能體已完成任務",
+ "ko-KR": "에이전트가 작업을 완료했습니다.",
+ "no": "Agenten har fullført oppgaven.",
+ "it": "L'agente ha completato il compito.",
+ "pt": "O agente concluiu a tarefa.",
+ "es": "El agente ha terminado la tarea.",
+ "ar": "أنهى الوكيل المهمة.",
+ "fr": "L'agent a terminé la tâche.",
+ "tr": "Ajan görevi tamamladı."
},
"CHAT_INTERFACE$AGENT_REJECTED_MESSAGE": {
"en": "Agent has rejected the task.",
"de": "Agent hat die Aufgabe abgelehnt.",
- "zh-CN": "智能体拒绝任务"
+ "zh-CN": "智能体拒绝任务",
+ "zh-TW": "智能體拒絕任務",
+ "ko-KR": "에이전트가 작업을 거부했습니다.",
+ "no": "Agenten har avvist oppgaven.",
+ "it": "L'agente ha rifiutato il compito.",
+ "pt": "O agente rejeitou a tarefa.",
+ "es": "El agente ha rechazado la tarea.",
+ "ar": "رفض الوكيل المهمة.",
+ "fr": "L'agent a rejeté la tâche.",
+ "tr": "Ajan görevi reddetti."
},
"CHAT_INTERFACE$AGENT_ERROR_MESSAGE": {
"en": "Agent encountered an error.",
"de": "Agent ist auf einen Fehler gelaufen.",
- "zh-CN": "智能体遇到错误"
+ "zh-CN": "智能体遇到错误",
+ "zh-TW": "智能體遇到錯誤",
+ "ko-KR": "에이전트에 오류가 발생했습니다.",
+ "no": "Agenten støtte på en feil.",
+ "it": "L'agente ha riscontrato un errore.",
+ "pt": "O agente encontrou um erro.",
+ "es": "El agente encontró un error.",
+ "ar": "واجه الوكيل خطأ.",
+ "fr": "L'agent a rencontré une erreur.",
+ "tr": "Ajan bir hatayla karşılaştı."
},
"CHAT_INTERFACE$AGENT_AWAITING_USER_CONFIRMATION_MESSAGE": {
"en": "Agent is awaiting user confirmation for the pending action.",
"de": "Agent wartet auf die Bestätigung des Benutzers für die ausstehende Aktion.",
- "zh-CN": "代理正在等待用户确认待处理的操作。"
+ "zh-CN": "代理正在等待用户确认待处理的操作。",
+ "zh-TW": "代理正在等待用戶確認待處理的操作。",
+ "ko-KR": "에이전트가 대기 중인 작업에 대한 사용자 확인을 기다리고 있습니다.",
+ "no": "Agenten venter på brukerbekreftelse for den ventende handlingen.",
+ "it": "L'agente è in attesa della conferma dell'utente per l'azione in sospeso.",
+ "pt": "O agente está aguardando a confirmação do usuário para a ação pendente.",
+ "es": "El agente está esperando la confirmación del usuario para la acción pendiente.",
+ "ar": "الوكيل ينتظر تأكيد المستخدم للإجراء المعلق.",
+ "fr": "L'agent attend la confirmation de l'utilisateur pour l'action en attente.",
+ "tr": "Ajan, bekleyen işlem için kullanıcı onayını bekliyor."
},
"CHAT_INTERFACE$AGENT_ACTION_USER_CONFIRMED_MESSAGE": {
"en": "Agent action has been confirmed!",
"de": "Die Aktion des Agenten wurde bestätigt!",
- "zh-CN": "代理操作已确认!"
+ "zh-CN": "代理操作已确认!",
+ "zh-TW": "代理操作已確認!",
+ "ko-KR": "에이전트 작업이 확인되었습니다!",
+ "no": "Agenthandlingen har blitt bekreftet!",
+ "it": "L'azione dell'agente è stata confermata!",
+ "pt": "A ação do agente foi confirmada!",
+ "es": "¡La acción del agente ha sido confirmada!",
+ "ar": "تم تأكيد إجراء الوكيل!",
+ "fr": "L'action de l'agent a été confirmée !",
+ "tr": "Ajan eylemi onaylandı!"
},
"CHAT_INTERFACE$AGENT_ACTION_USER_REJECTED_MESSAGE": {
"en": "Agent action has been rejected!",
"de": "Die Aktion des Agenten wurde abgelehnt!",
- "zh-CN": "代理操作已被拒绝!"
+ "zh-CN": "代理操作已被拒绝!",
+ "zh-TW": "代理操作已被拒絕!",
+ "ko-KR": "에이전트 작업이 거부되었습니다!",
+ "no": "Agenthandlingen har blitt avvist!",
+ "it": "L'azione dell'agente è stata rifiutata!",
+ "pt": "A ação do agente foi rejeitada!",
+ "es": "¡La acción del agente ha sido rechazada!",
+ "ar": "تم رفض إجراء الوكيل!",
+ "fr": "L'action de l'agent a été rejetée !",
+ "tr": "Ajan eylemi reddedildi!"
},
"CHAT_INTERFACE$INPUT_PLACEHOLDER": {
"en": "Message assistant...",
@@ -602,22 +985,58 @@
"CHAT_INTERFACE$INPUT_CONTINUE_MESSAGE": {
"en": "Continue",
"zh-CN": "继续",
- "de": "Fortfahren"
+ "de": "Fortfahren",
+ "zh-TW": "繼續",
+ "ko-KR": "계속",
+ "no": "Fortsett",
+ "it": "Continua",
+ "pt": "Continuar",
+ "es": "Continuar",
+ "ar": "استمرار",
+ "fr": "Continuer",
+ "tr": "Devam et"
},
"CHAT_INTERFACE$USER_ASK_CONFIRMATION": {
"en": "Do you want to continue with this action?",
"de": "Möchten Sie mit dieser Aktion fortfahren?",
- "zh-CN": "您要继续此操作吗?"
+ "zh-CN": "您要继续此操作吗?",
+ "zh-TW": "您要繼續此操作嗎?",
+ "ko-KR": "이 작업을 계속하시겠습니까?",
+ "no": "Vil du fortsette med denne handlingen?",
+ "it": "Vuoi continuare con questa azione?",
+ "pt": "Deseja continuar com esta ação?",
+ "es": "¿Desea continuar con esta acción?",
+ "ar": "هل تريد الاستمرار في هذا الإجراء؟",
+ "fr": "Voulez-vous continuer avec cette action ?",
+ "tr": "Bu işleme devam etmek istiyor musunuz?"
},
"CHAT_INTERFACE$USER_CONFIRMED": {
"en": "Confirm the requested action",
"de": "Bestätigen Sie die angeforderte Aktion",
- "zh-CN": "确认请求的操作"
+ "zh-CN": "确认请求的操作",
+ "zh-TW": "確認請求的操作",
+ "ko-KR": "요청된 작업 확인",
+ "no": "Bekreft den forespurte handlingen",
+ "it": "Conferma l'azione richiesta",
+ "pt": "Confirmar a ação solicitada",
+ "es": "Confirmar la acción solicitada",
+ "ar": "تأكيد الإجراء المطلوب",
+ "fr": "Confirmer l'action demandée",
+ "tr": "İstenen eylemi onayla"
},
"CHAT_INTERFACE$USER_REJECTED": {
"en": "Reject the requested action",
"de": "Lehnen Sie die angeforderte Aktion ab",
- "zh-CN": "拒绝请求的操作"
+ "zh-CN": "拒绝请求的操作",
+ "zh-TW": "拒絕請求的操作",
+ "ko-KR": "요청된 작업 거부",
+ "no": "Avvis den forespurte handlingen",
+ "it": "Rifiuta l'azione richiesta",
+ "pt": "Rejeitar a ação solicitada",
+ "es": "Rechazar la acción solicitada",
+ "ar": "رفض الإجراء المطلوب",
+ "fr": "Rejeter l'action demandée",
+ "tr": "İstenen eylemi reddet"
},
"CHAT_INTERFACE$INPUT_SEND_MESSAGE_BUTTON_CONTENT": {
"en": "Send",
@@ -635,27 +1054,72 @@
"CHAT_INTERFACE$CHAT_MESSAGE_COPIED": {
"en": "Message copied to clipboard",
"zh-CN": "消息已复制到剪贴板",
- "de": "Nachricht in die Zwischenablage kopiert"
+ "de": "Nachricht in die Zwischenablage kopiert",
+ "ko-KR": "메시지가 클립보드에 복사되었습니다",
+ "no": "Melding kopiert til utklippstavlen",
+ "zh-TW": "訊息已複製到剪貼簿",
+ "it": "Messaggio copiato negli appunti",
+ "pt": "Mensagem copiada para a área de transferência",
+ "es": "Mensaje copiado al portapapeles",
+ "ar": "تم نسخ الرسالة إلى الحافظة",
+ "fr": "Message copié dans le presse-papiers",
+ "tr": "Mesaj panoya kopyalandı"
},
"CHAT_INTERFACE$CHAT_MESSAGE_COPY_FAILED": {
"en": "Failed to copy message to clipboard",
"zh-CN": "复制消息到剪贴板失败",
- "de": "Nachricht konnte nicht in die Zwischenablage kopiert werden"
+ "de": "Nachricht konnte nicht in die Zwischenablage kopiert werden",
+ "ko-KR": "메시지를 클립보드에 복사하지 못했습니다",
+ "no": "Kunne ikke kopiere meldingen til utklippstavlen",
+ "zh-TW": "無法將訊息複製到剪貼簿",
+ "it": "Impossibile copiare il messaggio negli appunti",
+ "pt": "Falha ao copiar mensagem para a área de transferência",
+ "es": "No se pudo copiar el mensaje al portapapeles",
+ "ar": "فشل نسخ الرسالة إلى الحافظة",
+ "fr": "Échec de la copie du message dans le presse-papiers",
+ "tr": "Mesaj panoya kopyalanamadı"
},
"CHAT_INTERFACE$TOOLTIP_COPY_MESSAGE": {
"en": "Copy message",
"zh-CN": "复制消息",
- "de": "Nachricht kopieren"
+ "de": "Nachricht kopieren",
+ "ko-KR": "메시지 복사",
+ "no": "Kopier melding",
+ "zh-TW": "複製訊息",
+ "it": "Copia messaggio",
+ "pt": "Copiar mensagem",
+ "es": "Copiar mensaje",
+ "ar": "نسخ الرسالة",
+ "fr": "Copier le message",
+ "tr": "Mesajı kopyala"
},
"CHAT_INTERFACE$TOOLTIP_SEND_MESSAGE": {
"en": "Send message",
"zh-CN": "发送消息",
- "de": "Nachricht senden"
+ "de": "Nachricht senden",
+ "ko-KR": "메시지 보내기",
+ "no": "Send melding",
+ "zh-TW": "發送訊息",
+ "it": "Invia messaggio",
+ "pt": "Enviar mensagem",
+ "es": "Enviar mensaje",
+ "ar": "إرسال الرسالة",
+ "fr": "Envoyer le message",
+ "tr": "Mesaj gönder"
},
"CHAT_INTERFACE$TOOLTIP_UPLOAD_IMAGE": {
"en": "Upload image",
"zh-CN": "上传图片",
- "de": "Bild hochladen"
+ "de": "Bild hochladen",
+ "ko-KR": "이미지 업로드",
+ "no": "Last opp bilde",
+ "zh-TW": "上傳圖片",
+ "it": "Carica immagine",
+ "pt": "Carregar imagem",
+ "es": "Subir imagen",
+ "ar": "تحميل الصورة",
+ "fr": "Télécharger une image",
+ "tr": "Resim yükle"
},
"CHAT_INTERFACE$INITIAL_MESSAGE": {
"en": "Hi! I'm OpenHands, an AI Software Engineer. What would you like to build with me today?",
@@ -687,7 +1151,16 @@
"CHAT_INTERFACE$TO_BOTTOM": {
"en": "To Bottom",
"de": "Nach unten",
- "zh-CN": "回到底部"
+ "zh-CN": "回到底部",
+ "ko-KR": "맨 아래로",
+ "no": "Til bunnen",
+ "zh-TW": "回到底部",
+ "it": "In fondo",
+ "pt": "Para o fundo",
+ "es": "Ir al final",
+ "ar": "إلى الأسفل",
+ "fr": "Vers le bas",
+ "tr": "En alta"
},
"CHAT_INTERFACE$MESSAGE_ARIA_LABEL": {
"en": "Message from {{sender}}",
@@ -733,93 +1206,309 @@
"SECURITY_ANALYZER$UNKNOWN_RISK": {
"en": "Unknown Risk",
"de": "Unbekanntes Risiko",
- "zh-CN": "未知风险"
+ "zh-CN": "未知风险",
+ "ko-KR": "알 수 없는 위험",
+ "no": "Ukjent risiko",
+ "zh-TW": "未知風險",
+ "it": "Rischio sconosciuto",
+ "pt": "Risco desconhecido",
+ "es": "Riesgo desconocido",
+ "ar": "مخاطر غير معروفة",
+ "fr": "Risque inconnu",
+ "tr": "Bilinmeyen risk"
},
"SECURITY_ANALYZER$LOW_RISK": {
"en": "Low Risk",
"de": "Niedriges Risiko",
- "zh-CN": "低风险"
+ "zh-CN": "低风险",
+ "ko-KR": "낮은 위험",
+ "no": "Lav risiko",
+ "zh-TW": "低風險",
+ "it": "Rischio basso",
+ "pt": "Baixo risco",
+ "es": "Riesgo bajo",
+ "ar": "مخاطر منخفضة",
+ "fr": "Risque faible",
+ "tr": "Düşük risk"
},
"SECURITY_ANALYZER$MEDIUM_RISK": {
"en": "Medium Risk",
"de": "Mittleres Risiko",
- "zh-CN": "中等风险"
+ "zh-CN": "中等风险",
+ "ko-KR": "중간 위험",
+ "no": "Middels risiko",
+ "zh-TW": "中等風險",
+ "it": "Rischio medio",
+ "pt": "Risco médio",
+ "es": "Riesgo medio",
+ "ar": "مخاطر متوسطة",
+ "fr": "Risque moyen",
+ "tr": "Orta risk"
},
"SECURITY_ANALYZER$HIGH_RISK": {
"en": "High Risk",
"de": "Hohes Risiko",
- "zh-CN": "高风险"
+ "zh-CN": "高风险",
+ "ko-KR": "높은 위험",
+ "no": "Høy risiko",
+ "zh-TW": "高風險",
+ "it": "Rischio elevato",
+ "pt": "Alto risco",
+ "es": "Riesgo alto",
+ "ar": "مخاطر عالية",
+ "fr": "Risque élevé",
+ "tr": "Yüksek risk"
},
"SETTINGS$MODEL_TOOLTIP": {
"en": "Select the language model to use.",
"zh-CN": "选择要使用的语言模型",
"zh-TW": "選擇要使用的語言模型。",
- "de": "Wähle das zu verwendende Modell."
+ "de": "Wähle das zu verwendende Modell.",
+ "ko-KR": "사용할 언어 모델을 선택하세요.",
+ "no": "Velg språkmodellen som skal brukes.",
+ "it": "Seleziona il modello linguistico da utilizzare.",
+ "pt": "Selecione o modelo de linguagem a ser usado.",
+ "es": "Seleccione el modelo de lenguaje a utilizar.",
+ "ar": "اختر نموذج اللغة المراد استخدامه.",
+ "fr": "Sélectionnez le modèle de langage à utiliser.",
+ "tr": "Kullanılacak dil modelini seçin."
},
"SETTINGS$AGENT_TOOLTIP": {
"en": "Select the agent to use.",
"zh-CN": "选择要使用的智能体",
"zh-TW": "選擇要使用的智能體。",
- "de": "Wähle den zu verwendenden Agenten."
+ "de": "Wähle den zu verwendenden Agenten.",
+ "ko-KR": "사용할 에이전트를 선택하세요.",
+ "no": "Velg agenten som skal brukes.",
+ "it": "Seleziona l'agente da utilizzare.",
+ "pt": "Selecione o agente a ser usado.",
+ "es": "Seleccione el agente a utilizar.",
+ "ar": "اختر الوكيل المراد استخدامه.",
+ "fr": "Sélectionnez l'agent à utiliser.",
+ "tr": "Kullanılacak ajanı seçin."
},
"SETTINGS$LANGUAGE_TOOLTIP": {
"en": "Select the language for the UI.",
"zh-CN": "选择界面语言",
"zh-TW": "選擇 UI 的語言。",
- "de": "Wähle die Sprache für die Oberfläche."
+ "de": "Wähle die Sprache für die Oberfläche.",
+ "ko-KR": "UI 언어를 선택하세요.",
+ "no": "Velg språk for brukergrensesnittet.",
+ "it": "Seleziona la lingua per l'interfaccia utente.",
+ "pt": "Selecione o idioma para a interface do usuário.",
+ "es": "Seleccione el idioma para la interfaz de usuario.",
+ "ar": "اختر لغة واجهة المستخدم.",
+ "fr": "Sélectionnez la langue de l'interface utilisateur.",
+ "tr": "Kullanıcı arayüzü için dil seçin."
},
"SETTINGS$DISABLED_RUNNING": {
"en": "Cannot be changed while the agent is running.",
"zh-CN": "在智能体运行时无法更改",
"zh-TW": "智能體正在執行時無法更改。",
- "de": "Kann bei laufender Aufgabe nicht geändert werden."
+ "de": "Kann bei laufender Aufgabe nicht geändert werden.",
+ "ko-KR": "에이전트가 실행 중일 때는 변경할 수 없습니다.",
+ "no": "Kan ikke endres mens agenten kjører.",
+ "it": "Non può essere modificato mentre l'agente è in esecuzione.",
+ "pt": "Não pode ser alterado enquanto o agente está em execução.",
+ "es": "No se puede cambiar mientras el agente está en ejecución.",
+ "ar": "لا يمكن تغييره أثناء تشغيل الوكيل.",
+ "fr": "Ne peut pas être modifié pendant que l'agent est en cours d'exécution.",
+ "tr": "Ajan çalışırken değiştirilemez."
},
"SETTINGS$API_KEY_PLACEHOLDER": {
"en": "Enter your API key.",
"zh-CN": "输入您的 API key",
"zh-TW": "輸入您的 API 金鑰。",
- "de": "Modell API Schlüssel."
+ "de": "Modell API Schlüssel.",
+ "ko-KR": "API 키를 입력하세요.",
+ "no": "Skriv inn din API-nøkkel.",
+ "it": "Inserisci la tua chiave API.",
+ "pt": "Digite sua chave de API.",
+ "es": "Ingrese su clave de API.",
+ "ar": "أدخل مفتاح API الخاص بك.",
+ "fr": "Entrez votre clé API.",
+ "tr": "API anahtarınızı girin."
},
"SETTINGS$CONFIRMATION_MODE": {
"en": "Enable Confirmation Mode",
"de": "Bestätigungsmodus aktivieren",
- "zh-CN": "启用确认模式"
+ "zh-CN": "启用确认模式",
+ "zh-TW": "啟用確認模式",
+ "ko-KR": "확인 모드 활성화",
+ "no": "Aktiver bekreftelsesmodus",
+ "it": "Abilita modalità di conferma",
+ "pt": "Ativar modo de confirmação",
+ "es": "Habilitar modo de confirmación",
+ "ar": "تفعيل وضع التأكيد",
+ "fr": "Activer le mode de confirmation",
+ "tr": "Onay Modunu Etkinleştir"
},
"SETTINGS$CONFIRMATION_MODE_TOOLTIP": {
"en": "Awaits for user confirmation before executing code.",
"de": "Wartet auf die Bestätigung des Benutzers, bevor der Code ausgeführt wird.",
- "zh-CN": "在执行代码之前等待用户确认。"
+ "zh-CN": "在执行代码之前等待用户确认。",
+ "zh-TW": "在執行程式碼之前等待使用者確認。",
+ "ko-KR": "코드 실행 전 사용자 확인을 기다립니다.",
+ "no": "Venter på brukerbekreftelse før koden utføres.",
+ "it": "Attende la conferma dell'utente prima di eseguire il codice.",
+ "pt": "Aguarda a confirmação do usuário antes de executar o código.",
+ "es": "Espera la confirmación del usuario antes de ejecutar el código.",
+ "ar": "ينتظر تأكيد المستخدم قبل تنفيذ الكود.",
+ "fr": "Attend la confirmation de l'utilisateur avant d'exécuter le code.",
+ "tr": "Kodu çalıştırmadan önce kullanıcı onayını bekler."
},
"SETTINGS$AGENT_SELECT_ENABLED": {
- "en": "Enable Agent Selection - Advanced Users"
+ "en": "Enable Agent Selection - Advanced Users",
+ "zh-CN": "启用智能体选择 - 高级用户",
+ "zh-TW": "啟用智能體選擇 - 進階使用者",
+ "de": "Agentenauswahl aktivieren - Fortgeschrittene Benutzer",
+ "ko-KR": "에이전트 선택 활성화 - 고급 사용자",
+ "no": "Aktiver agentvalg - Avanserte brukere",
+ "it": "Abilita selezione agente - Utenti avanzati",
+ "pt": "Ativar seleção de agente - Usuários avançados",
+ "es": "Habilitar selección de agente - Usuarios avanzados",
+ "ar": "تمكين اختيار الوكيل - المستخدمين المتقدمين",
+ "fr": "Activer la sélection d'agent - Utilisateurs avancés",
+ "tr": "Ajan Seçimini Etkinleştir - İleri Düzey Kullanıcılar"
},
"SETTINGS$SECURITY_ANALYZER": {
"en": "Enable Security Analyzer",
"de": "Sicherheitsanalysator aktivieren",
- "zh-CN": "启用安全分析器"
+ "zh-CN": "启用安全分析器",
+ "zh-TW": "啟用安全分析器",
+ "ko-KR": "보안 분석기 활성화",
+ "no": "Aktiver sikkerhetsanalysator",
+ "it": "Abilita analizzatore di sicurezza",
+ "pt": "Ativar analisador de segurança",
+ "es": "Habilitar analizador de seguridad",
+ "ar": "تمكين محلل الأمان",
+ "fr": "Activer l'analyseur de sécurité",
+ "tr": "Güvenlik Analizörünü Etkinleştir"
},
"BROWSER$EMPTY_MESSAGE": {
"en": "No page loaded.",
"zh-CN": "页面未加载",
"zh-TW": "未加載任何頁面。",
- "de": "Keine Seite geladen."
+ "de": "Keine Seite geladen.",
+ "ko-KR": "페이지가 로드되지 않았습니다.",
+ "no": "Ingen side lastet.",
+ "it": "Nessuna pagina caricata.",
+ "pt": "Nenhuma página carregada.",
+ "es": "Ninguna página cargada.",
+ "ar": "لم يتم تحميل أي صفحة.",
+ "fr": "Aucune page chargée.",
+ "tr": "Sayfa yüklenmedi."
},
"PLANNER$EMPTY_MESSAGE": {
"en": "No plan created.",
"zh-CN": "计划未创建",
"zh-TW": "未創建任何計劃。",
- "de": "Kein Plan erstellt."
+ "de": "Kein Plan erstellt.",
+ "ko-KR": "생성된 계획이 없습니다.",
+ "no": "Ingen plan opprettet.",
+ "it": "Nessun piano creato.",
+ "pt": "Nenhum plano criado.",
+ "es": "Ningún plan creado.",
+ "ar": "لم يتم إنشاء أي خطة.",
+ "fr": "Aucun plan créé.",
+ "tr": "Plan oluşturulmadı."
},
"FEEDBACK$PUBLIC_LABEL": {
"en": "Public",
"zh-CN": "公开",
- "zh-TW": "公開。",
- "de": "Öffentlich"
+ "zh-TW": "公開",
+ "de": "Öffentlich",
+ "ko-KR": "공개",
+ "no": "Offentlig",
+ "it": "Pubblico",
+ "pt": "Público",
+ "es": "Público",
+ "ar": "عام",
+ "fr": "Public",
+ "tr": "Herkese Açık"
},
"FEEDBACK$PRIVATE_LABEL": {
"en": "Private",
"zh-CN": "私有",
- "zh-TW": "私有。",
- "de": "Privat"
+ "zh-TW": "私有",
+ "de": "Privat",
+ "ko-KR": "비공개",
+ "no": "Privat",
+ "it": "Privato",
+ "pt": "Privado",
+ "es": "Privado",
+ "ar": "خاص",
+ "fr": "Privé",
+ "tr": "Özel"
+ },
+ "STATUS$STARTING_RUNTIME": {
+ "en": "Starting Runtime...",
+ "zh-CN": "启动运行时...",
+ "zh-TW": "啟動運行時...",
+ "de": "Laufzeitumgebung wird gestartet...",
+ "ko-KR": "런타임 시작 중...",
+ "no": "Starter kjøretidsmiljø...",
+ "it": "Avvio dell'ambiente di esecuzione...",
+ "pt": "Iniciando o ambiente de execução...",
+ "es": "Iniciando el entorno de ejecución...",
+ "ar": "جارٍ بدء بيئة التشغيل...",
+ "fr": "Démarrage de l'environnement d'exécution...",
+ "tr": "Çalışma zamanı başlatılıyor..."
+ },
+ "STATUS$STARTING_CONTAINER": {
+ "en": "Preparing container, this might take a few minutes...",
+ "zh-CN": "正在准备容器,这可能需要几分钟...",
+ "zh-TW": "正在準備容器,這可能需要幾分鐘...",
+ "de": "Container wird vorbereitet, dies kann einige Minuten dauern...",
+ "ko-KR": "컨테이너를 준비 중입니다. 몇 분 정도 걸릴 수 있습니다...",
+ "no": "Forbereder container, dette kan ta noen minutter...",
+ "it": "Preparazione del container in corso, potrebbe richiedere alcuni minuti...",
+ "pt": "Preparando o container, isso pode levar alguns minutos...",
+ "es": "Preparando el contenedor, esto puede tardar unos minutos...",
+ "ar": "جارٍ إعداد الحاوية، قد يستغرق هذا بضع دقائق...",
+ "fr": "Préparation du conteneur, cela peut prendre quelques minutes...",
+ "tr": "Konteyner hazırlanıyor, bu işlem birkaç dakika sürebilir..."
+ },
+ "STATUS$PREPARING_CONTAINER": {
+ "en": "Preparing to start container...",
+ "zh-CN": "正在准备启动容器...",
+ "zh-TW": "正在準備啟動容器...",
+ "de": "Vorbereitung zum Starten des Containers...",
+ "ko-KR": "컨테이너 시작 준비 중...",
+ "no": "Forbereder å starte container...",
+ "it": "Preparazione all'avvio del container...",
+ "pt": "Preparando para iniciar o container...",
+ "es": "Preparando para iniciar el contenedor...",
+ "ar": "جارٍ التحضير لبدء الحاوية...",
+ "fr": "Préparation du démarrage du conteneur...",
+ "tr": "Konteyner başlatılmaya hazırlanıyor..."
+ },
+ "STATUS$CONTAINER_STARTED": {
+ "en": "Container started.",
+ "zh-CN": "容器已启动。",
+ "zh-TW": "容器已啟動。",
+ "de": "Container gestartet.",
+ "ko-KR": "컨테이너가 시작되었습니다.",
+ "no": "Container startet.",
+ "it": "Container avviato.",
+ "pt": "Container iniciado.",
+ "es": "Contenedor iniciado.",
+ "ar": "تم بدء الحاوية.",
+ "fr": "Conteneur démarré.",
+ "tr": "Konteyner başlatıldı."
+ },
+ "STATUS$WAITING_FOR_CLIENT": {
+ "en": "Waiting for client to become ready...",
+ "zh-CN": "等待客户端准备就绪...",
+ "zh-TW": "等待客戶端準備就緒...",
+ "de": "Warten auf Bereitschaft des Clients...",
+ "ko-KR": "클라이언트가 준비될 때까지 기다리는 중...",
+ "no": "Venter på at klienten skal bli klar...",
+ "it": "In attesa che il client sia pronto...",
+ "pt": "Aguardando o cliente ficar pronto...",
+ "es": "Esperando a que el cliente esté listo...",
+ "ar": "في انتظار جاهزية العميل...",
+ "fr": "En attente que le client soit prêt...",
+ "tr": "İstemcinin hazır olması bekleniyor..."
}
}
diff --git a/frontend/src/services/actions.ts b/frontend/src/services/actions.ts
index 72b1a4a8cf5..1f2e99a3796 100644
--- a/frontend/src/services/actions.ts
+++ b/frontend/src/services/actions.ts
@@ -6,10 +6,11 @@ import {
ActionSecurityRisk,
appendSecurityAnalyzerInput,
} from "#/state/securityAnalyzerSlice";
+import { setCurStatusMessage } from "#/state/statusSlice";
import { setRootTask } from "#/state/taskSlice";
import store from "#/store";
import ActionType from "#/types/ActionType";
-import { ActionMessage } from "#/types/Message";
+import { ActionMessage, StatusMessage } from "#/types/Message";
import { SocketMessage } from "#/types/ResponseType";
import { handleObservationMessage } from "./observations";
import { getRootTask } from "./taskService";
@@ -138,6 +139,16 @@ export function handleActionMessage(message: ActionMessage) {
}
}
+export function handleStatusMessage(message: StatusMessage) {
+ const msg = message.message == null ? "" : message.message.trim();
+ store.dispatch(
+ setCurStatusMessage({
+ ...message,
+ message: msg,
+ }),
+ );
+}
+
export function handleAssistantMessage(data: string | SocketMessage) {
let socketMessage: SocketMessage;
@@ -149,7 +160,9 @@ export function handleAssistantMessage(data: string | SocketMessage) {
if ("action" in socketMessage) {
handleActionMessage(socketMessage);
- } else {
+ } else if ("observation" in socketMessage) {
handleObservationMessage(socketMessage);
+ } else if ("message" in socketMessage) {
+ handleStatusMessage(socketMessage);
}
}
diff --git a/frontend/src/services/session.ts b/frontend/src/services/session.ts
index 392905eaa39..8e77a33cf2f 100644
--- a/frontend/src/services/session.ts
+++ b/frontend/src/services/session.ts
@@ -8,11 +8,19 @@ import { I18nKey } from "#/i18n/declaration";
const translate = (key: I18nKey) => i18next.t(key);
+// Define a type for the messages
+type Message = {
+ action: ActionType;
+ args: Record
;
+};
+
class Session {
private static _socket: WebSocket | null = null;
private static _latest_event_id: number = -1;
+ private static _messageQueue: Message[] = [];
+
public static _history: Record[] = [];
// callbacks contain a list of callable functions
@@ -83,6 +91,7 @@ class Session {
toast.success("ws", translate(I18nKey.SESSION$SERVER_CONNECTED_MESSAGE));
Session._connecting = false;
Session._initializeAgent();
+ Session._flushQueue();
Session.callbacks.open?.forEach((callback) => {
callback(e);
});
@@ -94,7 +103,6 @@ class Session {
data = JSON.parse(e.data);
Session._history.push(data);
} catch (err) {
- // TODO: report the error
toast.error(
"ws",
translate(I18nKey.SESSION$SESSION_HANDLING_ERROR_MESSAGE),
@@ -115,6 +123,7 @@ class Session {
};
Session._socket.onerror = () => {
+ // TODO report error
toast.error(
"ws",
translate(I18nKey.SESSION$SESSION_CONNECTION_ERROR_MESSAGE),
@@ -145,9 +154,20 @@ class Session {
Session._socket = null;
}
+ private static _flushQueue(): void {
+ while (Session._messageQueue.length > 0) {
+ const message = Session._messageQueue.shift();
+ if (message) {
+ setTimeout(() => Session.send(JSON.stringify(message)), 1000);
+ }
+ }
+ }
+
static send(message: string): void {
+ const messageObject: Message = JSON.parse(message);
+
if (Session._connecting) {
- setTimeout(() => Session.send(message), 1000);
+ Session._messageQueue.push(messageObject);
return;
}
if (!Session.isConnected()) {
diff --git a/frontend/src/state/statusSlice.ts b/frontend/src/state/statusSlice.ts
new file mode 100644
index 00000000000..5517d6af863
--- /dev/null
+++ b/frontend/src/state/statusSlice.ts
@@ -0,0 +1,23 @@
+import { createSlice, PayloadAction } from "@reduxjs/toolkit";
+import { StatusMessage } from "#/types/Message";
+
+const initialStatusMessage: StatusMessage = {
+ message: "",
+ is_error: false,
+};
+
+export const statusSlice = createSlice({
+ name: "status",
+ initialState: {
+ curStatusMessage: initialStatusMessage,
+ },
+ reducers: {
+ setCurStatusMessage: (state, action: PayloadAction) => {
+ state.curStatusMessage = action.payload;
+ },
+ },
+});
+
+export const { setCurStatusMessage } = statusSlice.actions;
+
+export default statusSlice.reducer;
diff --git a/frontend/src/store.ts b/frontend/src/store.ts
index 7fffbfb5701..0de8d08d075 100644
--- a/frontend/src/store.ts
+++ b/frontend/src/store.ts
@@ -8,6 +8,7 @@ import errorsReducer from "./state/errorsSlice";
import taskReducer from "./state/taskSlice";
import jupyterReducer from "./state/jupyterSlice";
import securityAnalyzerReducer from "./state/securityAnalyzerSlice";
+import statusReducer from "./state/statusSlice";
export const rootReducer = combineReducers({
browser: browserReducer,
@@ -19,6 +20,7 @@ export const rootReducer = combineReducers({
agent: agentReducer,
jupyter: jupyterReducer,
securityAnalyzer: securityAnalyzerReducer,
+ status: statusReducer,
});
const store = configureStore({
diff --git a/frontend/src/types/Message.tsx b/frontend/src/types/Message.tsx
index 515441c74c1..a7a062cd6ec 100644
--- a/frontend/src/types/Message.tsx
+++ b/frontend/src/types/Message.tsx
@@ -31,3 +31,12 @@ export interface ObservationMessage {
// The timestamp of the message
timestamp: string;
}
+
+export interface StatusMessage {
+ // TODO not implemented yet
+ // Whether the status is an error, default is false
+ is_error: boolean;
+
+ // A status message to display to the user
+ message: string;
+}
diff --git a/frontend/src/types/ResponseType.tsx b/frontend/src/types/ResponseType.tsx
index b635d78c337..cad6131f80e 100644
--- a/frontend/src/types/ResponseType.tsx
+++ b/frontend/src/types/ResponseType.tsx
@@ -1,5 +1,5 @@
-import { ActionMessage, ObservationMessage } from "./Message";
+import { ActionMessage, ObservationMessage, StatusMessage } from "./Message";
-type SocketMessage = ActionMessage | ObservationMessage;
+type SocketMessage = ActionMessage | ObservationMessage | StatusMessage;
export { type SocketMessage };
diff --git a/openhands/core/main.py b/openhands/core/main.py
index c25ba9a0d81..3aa6b5ef180 100644
--- a/openhands/core/main.py
+++ b/openhands/core/main.py
@@ -55,7 +55,6 @@ def create_runtime(
config: The app config.
sid: The session id.
- runtime_tools_config: (will be deprecated) The runtime tools config.
"""
# if sid is provided on the command line, use it as the name of the event stream
# otherwise generate it on the basis of the configured jwt_secret
diff --git a/openhands/runtime/client/client.py b/openhands/runtime/client/client.py
index 987fddf9095..1b34eb5b538 100644
--- a/openhands/runtime/client/client.py
+++ b/openhands/runtime/client/client.py
@@ -16,8 +16,10 @@
import pexpect
from fastapi import FastAPI, HTTPException, Request, UploadFile
+from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from pydantic import BaseModel
+from starlette.exceptions import HTTPException as StarletteHTTPException
from uvicorn import run
from openhands.core.logger import openhands_logger as logger
@@ -562,6 +564,35 @@ async def lifespan(app: FastAPI):
app = FastAPI(lifespan=lifespan)
+ # TODO below 3 exception handlers were recommended by Sonnet.
+ # Are these something we should keep?
+ @app.exception_handler(Exception)
+ async def global_exception_handler(request: Request, exc: Exception):
+ logger.exception('Unhandled exception occurred:')
+ return JSONResponse(
+ status_code=500,
+ content={
+ 'message': 'An unexpected error occurred. Please try again later.'
+ },
+ )
+
+ @app.exception_handler(StarletteHTTPException)
+ async def http_exception_handler(request: Request, exc: StarletteHTTPException):
+ logger.error(f'HTTP exception occurred: {exc.detail}')
+ return JSONResponse(
+ status_code=exc.status_code, content={'message': exc.detail}
+ )
+
+ @app.exception_handler(RequestValidationError)
+ async def validation_exception_handler(
+ request: Request, exc: RequestValidationError
+ ):
+ logger.error(f'Validation error occurred: {exc}')
+ return JSONResponse(
+ status_code=422,
+ content={'message': 'Invalid request parameters', 'details': exc.errors()},
+ )
+
@app.middleware('http')
async def one_request_at_a_time(request: Request, call_next):
assert client is not None
diff --git a/openhands/runtime/client/runtime.py b/openhands/runtime/client/runtime.py
index 6a8d5eea3e6..bb2c78b79af 100644
--- a/openhands/runtime/client/runtime.py
+++ b/openhands/runtime/client/runtime.py
@@ -2,6 +2,7 @@
import tempfile
import threading
import uuid
+from typing import Callable
from zipfile import ZipFile
import docker
@@ -119,6 +120,7 @@ def __init__(
sid: str = 'default',
plugins: list[PluginRequirement] | None = None,
env_vars: dict[str, str] | None = None,
+ status_message_callback: Callable | None = None,
):
self.config = config
self._host_port = 30000 # initial dummy value
@@ -130,12 +132,13 @@ def __init__(
self.instance_id = (
sid + '_' + str(uuid.uuid4()) if sid is not None else str(uuid.uuid4())
)
+ self.status_message_callback = status_message_callback
+ self.send_status_message('STATUS$STARTING_RUNTIME')
self.docker_client: docker.DockerClient = self._init_docker_client()
self.base_container_image = self.config.sandbox.base_container_image
self.runtime_container_image = self.config.sandbox.runtime_container_image
self.container_name = self.container_name_prefix + self.instance_id
-
self.container = None
self.action_semaphore = threading.Semaphore(1) # Ensure one action at a time
@@ -146,9 +149,10 @@ def __init__(
self.log_buffer: LogBuffer | None = None
if self.config.sandbox.runtime_extra_deps:
- logger.info(
+ logger.debug(
f'Installing extra user-provided dependencies in the runtime image: {self.config.sandbox.runtime_extra_deps}'
)
+
self.skip_container_logs = (
os.environ.get('SKIP_CONTAINER_LOGS', 'false').lower() == 'true'
)
@@ -157,6 +161,8 @@ def __init__(
raise ValueError(
'Neither runtime container image nor base container image is set'
)
+ logger.info('Preparing container, this might take a few minutes...')
+ self.send_status_message('STATUS$STARTING_CONTAINER')
self.runtime_container_image = build_runtime_image(
self.base_container_image,
self.runtime_builder,
@@ -169,9 +175,13 @@ def __init__(
)
# will initialize both the event stream and the env vars
- super().__init__(config, event_stream, sid, plugins, env_vars)
+ super().__init__(
+ config, event_stream, sid, plugins, env_vars, status_message_callback
+ )
+
+ logger.info('Waiting for client to become ready...')
+ self.send_status_message('STATUS$WAITING_FOR_CLIENT')
- logger.info('Waiting for runtime container to be alive...')
self._wait_until_alive()
self.setup_initial_env()
@@ -179,6 +189,7 @@ def __init__(
logger.info(
f'Container initialized with plugins: {[plugin.name for plugin in self.plugins]}'
)
+ self.send_status_message(' ')
@staticmethod
def _init_docker_client() -> docker.DockerClient:
@@ -201,9 +212,8 @@ def _init_container(
plugins: list[PluginRequirement] | None = None,
):
try:
- logger.info(
- f'Starting container with image: {self.runtime_container_image} and name: {self.container_name}'
- )
+ logger.info('Preparing to start container...')
+ self.send_status_message('STATUS$PREPARING_CONTAINER')
plugin_arg = ''
if plugins is not None and len(plugins) > 0:
plugin_arg = (
@@ -241,17 +251,17 @@ def _init_container(
if self.config.debug:
environment['DEBUG'] = 'true'
- logger.info(f'Workspace Base: {self.config.workspace_base}')
+ logger.debug(f'Workspace Base: {self.config.workspace_base}')
if mount_dir is not None and sandbox_workspace_dir is not None:
# e.g. result would be: {"/home/user/openhands/workspace": {'bind': "/workspace", 'mode': 'rw'}}
volumes = {mount_dir: {'bind': sandbox_workspace_dir, 'mode': 'rw'}}
- logger.info(f'Mount dir: {mount_dir}')
+ logger.debug(f'Mount dir: {mount_dir}')
else:
logger.warn(
'Warning: Mount dir is not set, will not mount the workspace directory to the container!\n'
)
volumes = None
- logger.info(f'Sandbox workspace: {sandbox_workspace_dir}')
+ logger.debug(f'Sandbox workspace: {sandbox_workspace_dir}')
if self.config.sandbox.browsergym_eval_env is not None:
browsergym_arg = (
@@ -259,6 +269,7 @@ def _init_container(
)
else:
browsergym_arg = ''
+
container = self.docker_client.containers.run(
self.runtime_container_image,
command=(
@@ -281,6 +292,7 @@ def _init_container(
)
self.log_buffer = LogBuffer(container)
logger.info(f'Container started. Server url: {self.api_url}')
+ self.send_status_message('STATUS$CONTAINER_STARTED')
return container
except Exception as e:
logger.error(
@@ -539,3 +551,8 @@ def _find_available_port(self, max_attempts=5):
return port
# If no port is found after max_attempts, return the last tried port
return port
+
+ def send_status_message(self, message: str):
+ """Sends a status message if the callback function was provided."""
+ if self.status_message_callback:
+ self.status_message_callback(message)
diff --git a/openhands/runtime/e2b/runtime.py b/openhands/runtime/e2b/runtime.py
index 82ca16f9391..d2988895ba5 100644
--- a/openhands/runtime/e2b/runtime.py
+++ b/openhands/runtime/e2b/runtime.py
@@ -1,3 +1,5 @@
+from typing import Callable, Optional
+
from openhands.core.config import AppConfig
from openhands.events.action import (
FileReadAction,
@@ -25,8 +27,15 @@ def __init__(
sid: str = 'default',
plugins: list[PluginRequirement] | None = None,
sandbox: E2BSandbox | None = None,
+ status_message_callback: Optional[Callable] = None,
):
- super().__init__(config, event_stream, sid, plugins)
+ super().__init__(
+ config,
+ event_stream,
+ sid,
+ plugins,
+ status_message_callback=status_message_callback,
+ )
if sandbox is None:
self.sandbox = E2BSandbox()
if not isinstance(self.sandbox, E2BSandbox):
diff --git a/openhands/runtime/remote/runtime.py b/openhands/runtime/remote/runtime.py
index d37433c3d4d..9cc0ebe7bac 100644
--- a/openhands/runtime/remote/runtime.py
+++ b/openhands/runtime/remote/runtime.py
@@ -2,6 +2,7 @@
import tempfile
import threading
import uuid
+from typing import Callable, Optional
from zipfile import ZipFile
import requests
@@ -55,6 +56,7 @@ def __init__(
sid: str = 'default',
plugins: list[PluginRequirement] | None = None,
env_vars: dict[str, str] | None = None,
+ status_message_callback: Optional[Callable] = None,
):
self.config = config
if self.config.sandbox.api_hostname == 'localhost':
@@ -168,7 +170,9 @@ def __init__(
)
# Initialize the eventstream and env vars
- super().__init__(config, event_stream, sid, plugins, env_vars)
+ super().__init__(
+ config, event_stream, sid, plugins, env_vars, status_message_callback
+ )
logger.info(
f'Runtime initialized with plugins: {[plugin.name for plugin in self.plugins]}'
diff --git a/openhands/runtime/runtime.py b/openhands/runtime/runtime.py
index 902e6027f20..9c7fbe54475 100644
--- a/openhands/runtime/runtime.py
+++ b/openhands/runtime/runtime.py
@@ -3,6 +3,7 @@
import json
import os
from abc import abstractmethod
+from typing import Callable
from openhands.core.config import AppConfig, SandboxConfig
from openhands.core.logger import openhands_logger as logger
@@ -58,11 +59,13 @@ def __init__(
sid: str = 'default',
plugins: list[PluginRequirement] | None = None,
env_vars: dict[str, str] | None = None,
+ status_message_callback: Callable | None = None,
):
self.sid = sid
self.event_stream = event_stream
self.event_stream.subscribe(EventStreamSubscriber.RUNTIME, self.on_event)
self.plugins = plugins if plugins is not None and len(plugins) > 0 else []
+ self.status_message_callback = status_message_callback
self.config = copy.deepcopy(config)
atexit.register(self.close)
diff --git a/openhands/server/session/agent.py b/openhands/server/session/agent_session.py
similarity index 86%
rename from openhands/server/session/agent.py
rename to openhands/server/session/agent_session.py
index 31a8a821cb0..bb55d37b2f0 100644
--- a/openhands/server/session/agent.py
+++ b/openhands/server/session/agent_session.py
@@ -1,3 +1,6 @@
+import asyncio
+from typing import Callable, Optional
+
from openhands.controller import AgentController
from openhands.controller.agent import Agent
from openhands.controller.state.state import State
@@ -46,9 +49,9 @@ async def start(
max_budget_per_task: float | None = None,
agent_to_llm_config: dict[str, LLMConfig] | None = None,
agent_configs: dict[str, AgentConfig] | None = None,
+ status_message_callback: Optional[Callable] = None,
):
"""Starts the Agent session
-
Parameters:
- runtime_name: The name of the runtime associated with the session
- config:
@@ -58,13 +61,12 @@ async def start(
- agent_to_llm_config:
- agent_configs:
"""
-
if self.controller or self.runtime:
raise RuntimeError(
'Session already started. You need to close this session and start a new one.'
)
await self._create_security_analyzer(config.security.security_analyzer)
- await self._create_runtime(runtime_name, config, agent)
+ await self._create_runtime(runtime_name, config, agent, status_message_callback)
await self._create_controller(
agent,
config.security.confirmation_mode,
@@ -96,13 +98,19 @@ async def _create_security_analyzer(self, security_analyzer: str | None):
- security_analyzer: The name of the security analyzer to use
"""
- logger.info(f'Using security analyzer: {security_analyzer}')
if security_analyzer:
+ logger.debug(f'Using security analyzer: {security_analyzer}')
self.security_analyzer = options.SecurityAnalyzers.get(
security_analyzer, SecurityAnalyzer
)(self.event_stream)
- async def _create_runtime(self, runtime_name: str, config: AppConfig, agent: Agent):
+ async def _create_runtime(
+ self,
+ runtime_name: str,
+ config: AppConfig,
+ agent: Agent,
+ status_message_callback: Optional[Callable] = None,
+ ):
"""Creates a runtime instance
Parameters:
@@ -112,17 +120,27 @@ async def _create_runtime(self, runtime_name: str, config: AppConfig, agent: Age
"""
if self.runtime is not None:
- raise Exception('Runtime already created')
+ raise RuntimeError('Runtime already created')
logger.info(f'Initializing runtime `{runtime_name}` now...')
runtime_cls = get_runtime_cls(runtime_name)
- self.runtime = runtime_cls(
+
+ self.runtime = await asyncio.to_thread(
+ runtime_cls,
config=config,
event_stream=self.event_stream,
sid=self.sid,
plugins=agent.sandbox_plugins,
+ status_message_callback=status_message_callback,
)
+ if self.runtime is not None:
+ logger.debug(
+ f'Runtime initialized with plugins: {[plugin.name for plugin in self.runtime.plugins]}'
+ )
+ else:
+ logger.warning('Runtime initialization failed')
+
async def _create_controller(
self,
agent: Agent,
@@ -178,5 +196,5 @@ async def _create_controller(
)
logger.info(f'Restored agent state from session, sid: {self.sid}')
except Exception as e:
- logger.info(f'Error restoring state: {e}')
+ logger.info(f'State could not be restored: {e}')
logger.info('Agent controller initialized.')
diff --git a/openhands/server/session/manager.py b/openhands/server/session/manager.py
index a14fdc8be17..99da3bc4cb6 100644
--- a/openhands/server/session/manager.py
+++ b/openhands/server/session/manager.py
@@ -35,9 +35,11 @@ def get_session(self, sid: str) -> Session | None:
async def send(self, sid: str, data: dict[str, object]) -> bool:
"""Sends data to the client."""
- if sid not in self._sessions:
+ session = self.get_session(sid)
+ if session is None:
+ logger.error(f'*** No session found for {sid}, skipping message ***')
return False
- return await self._sessions[sid].send(data)
+ return await session.send(data)
async def send_error(self, sid: str, message: str) -> bool:
"""Sends an error message to the client."""
diff --git a/openhands/server/session/session.py b/openhands/server/session/session.py
index 588df196108..fd9e9aa578a 100644
--- a/openhands/server/session/session.py
+++ b/openhands/server/session/session.py
@@ -21,7 +21,7 @@
from openhands.events.stream import EventStreamSubscriber
from openhands.llm.llm import LLM
from openhands.runtime.utils.shutdown_listener import should_continue
-from openhands.server.session.agent import AgentSession
+from openhands.server.session.agent_session import AgentSession
from openhands.storage.files import FileStore
DEL_DELT_SEC = 60 * 60 * 5
@@ -33,6 +33,7 @@ class Session:
last_active_ts: int = 0
is_alive: bool = True
agent_session: AgentSession
+ loop: asyncio.AbstractEventLoop
def __init__(
self, sid: str, ws: WebSocket | None, config: AppConfig, file_store: FileStore
@@ -45,6 +46,7 @@ def __init__(
EventStreamSubscriber.SERVER, self.on_event
)
self.config = config
+ self.loop = asyncio.get_event_loop()
async def close(self):
self.is_alive = False
@@ -113,6 +115,7 @@ async def _initialize_agent(self, data: dict):
max_budget_per_task=self.config.max_budget_per_task,
agent_to_llm_config=self.config.get_agent_to_llm_config_map(),
agent_configs=self.config.get_agent_configs(),
+ status_message_callback=self.queue_status_message,
)
except Exception as e:
logger.exception(f'Error creating controller: {e}')
@@ -125,7 +128,8 @@ async def _initialize_agent(self, data: dict):
)
async def on_event(self, event: Event):
- """Callback function for agent events.
+ """Callback function for events that mainly come from the agent.
+ Event is the base class for any agent action and observation.
Args:
event: The agent event (Observation or Action).
@@ -135,7 +139,6 @@ async def on_event(self, event: Event):
if isinstance(event, NullObservation):
return
if event.source == EventSource.AGENT:
- logger.info('Server event')
await self.send(event_to_dict(event))
elif event.source == EventSource.USER and isinstance(
event, CmdOutputObservation
@@ -172,6 +175,9 @@ async def send(self, data: dict[str, object]) -> bool:
await asyncio.sleep(0.001) # This flushes the data to the client
self.last_active_ts = int(time.time())
return True
+ except RuntimeError:
+ self.is_alive = False
+ return False
except WebSocketDisconnect:
self.is_alive = False
return False
@@ -195,3 +201,8 @@ def load_from_data(self, data: dict) -> bool:
return False
self.is_alive = data.get('is_alive', False)
return True
+
+ def queue_status_message(self, message: str):
+ """Queues a status message to be sent asynchronously."""
+ # Ensure the coroutine runs in the main event loop
+ asyncio.run_coroutine_threadsafe(self.send_message(message), self.loop)
From 1b1d8f0b02e5e6103f2cd3cbf250b8ec4d034ebc Mon Sep 17 00:00:00 2001
From: Xingyao Wang
Date: Tue, 24 Sep 2024 15:47:27 -0500
Subject: [PATCH 10/12] [eval] Use `imap_unorderd` for parallizing evaluation
(#4040)
---
evaluation/utils/shared.py | 23 ++++++++++-------------
1 file changed, 10 insertions(+), 13 deletions(-)
diff --git a/evaluation/utils/shared.py b/evaluation/utils/shared.py
index d3850882882..2981e557990 100644
--- a/evaluation/utils/shared.py
+++ b/evaluation/utils/shared.py
@@ -301,6 +301,11 @@ def _process_instance_wrapper(
time.sleep(5)
+def _process_instance_wrapper_mp(args):
+ """Wrapper for multiprocessing, especially for imap_unordered."""
+ return _process_instance_wrapper(*args)
+
+
def run_evaluation(
dataset: pd.DataFrame,
metadata: EvalMetadata | None,
@@ -328,21 +333,13 @@ def run_evaluation(
try:
if use_multiprocessing:
with mp.Pool(num_workers) as pool:
- results = [
- pool.apply_async(
- _process_instance_wrapper,
- args=(
- process_instance_func,
- instance,
- metadata,
- True,
- max_retries,
- ),
- )
+ args_iter = (
+ (process_instance_func, instance, metadata, True, max_retries)
for _, instance in dataset.iterrows()
- ]
+ )
+ results = pool.imap_unordered(_process_instance_wrapper_mp, args_iter)
for result in results:
- update_progress(result.get(), pbar, output_fp)
+ update_progress(result, pbar, output_fp)
else:
for _, instance in dataset.iterrows():
result = _process_instance_wrapper(
From ee284bae8f6f30235e89022c63b88f05092a9239 Mon Sep 17 00:00:00 2001
From: tofarr
Date: Tue, 24 Sep 2024 15:49:30 -0600
Subject: [PATCH 11/12] Fix server lock up on session init (#4007)
---
openhands/controller/agent_controller.py | 7 ++---
openhands/core/cli.py | 3 +++
openhands/core/main.py | 3 +++
openhands/server/session/agent_session.py | 33 +++++++++++++++++------
4 files changed, 33 insertions(+), 13 deletions(-)
diff --git a/openhands/controller/agent_controller.py b/openhands/controller/agent_controller.py
index 724d2c36f37..29bb27e2201 100644
--- a/openhands/controller/agent_controller.py
+++ b/openhands/controller/agent_controller.py
@@ -54,7 +54,7 @@ class AgentController:
confirmation_mode: bool
agent_to_llm_config: dict[str, LLMConfig]
agent_configs: dict[str, AgentConfig]
- agent_task: asyncio.Task | None = None
+ agent_task: asyncio.Future | None = None
parent: 'AgentController | None' = None
delegate: 'AgentController | None' = None
_pending_action: Action | None = None
@@ -115,9 +115,6 @@ def __init__(
# stuck helper
self._stuck_detector = StuckDetector(self.state)
- if not is_delegate:
- self.agent_task = asyncio.create_task(self._start_step_loop())
-
async def close(self):
"""Closes the agent controller, canceling any ongoing tasks and unsubscribing from the event stream."""
if self.agent_task is not None:
@@ -149,7 +146,7 @@ async def report_error(self, message: str, exception: Exception | None = None):
self.state.last_error += f': {exception}'
self.event_stream.add_event(ErrorObservation(message), EventSource.AGENT)
- async def _start_step_loop(self):
+ async def start_step_loop(self):
"""The main loop for the agent's step-by-step execution."""
logger.info(f'[Agent Controller {self.id}] Starting step loop...')
diff --git a/openhands/core/cli.py b/openhands/core/cli.py
index 071ae248eea..7c4380a27aa 100644
--- a/openhands/core/cli.py
+++ b/openhands/core/cli.py
@@ -121,6 +121,9 @@ async def main():
event_stream=event_stream,
)
+ if controller is not None:
+ controller.agent_task = asyncio.create_task(controller.start_step_loop())
+
async def prompt_for_next_task():
next_message = input('How can I help? >> ')
if next_message == 'exit':
diff --git a/openhands/core/main.py b/openhands/core/main.py
index 3aa6b5ef180..2b03f2f7a90 100644
--- a/openhands/core/main.py
+++ b/openhands/core/main.py
@@ -143,6 +143,9 @@ async def run_controller(
headless_mode=headless_mode,
)
+ if controller is not None:
+ controller.agent_task = asyncio.create_task(controller.start_step_loop())
+
assert isinstance(task_str, str), f'task_str must be a string, got {type(task_str)}'
# Logging
logger.info(
diff --git a/openhands/server/session/agent_session.py b/openhands/server/session/agent_session.py
index bb55d37b2f0..976b285f54a 100644
--- a/openhands/server/session/agent_session.py
+++ b/openhands/server/session/agent_session.py
@@ -1,4 +1,6 @@
import asyncio
+
+from threading import Thread
from typing import Callable, Optional
from openhands.controller import AgentController
@@ -65,9 +67,14 @@ async def start(
raise RuntimeError(
'Session already started. You need to close this session and start a new one.'
)
- await self._create_security_analyzer(config.security.security_analyzer)
- await self._create_runtime(runtime_name, config, agent, status_message_callback)
- await self._create_controller(
+
+ self.loop = asyncio.new_event_loop()
+ self.thread = Thread(target=self._run, daemon=True)
+ self.thread.start()
+
+ self._create_security_analyzer(config.security.security_analyzer)
+ self._create_runtime(runtime_name, config, agent, status_message_callback)
+ self._create_controller(
agent,
config.security.confirmation_mode,
max_iterations,
@@ -75,6 +82,13 @@ async def start(
agent_to_llm_config=agent_to_llm_config,
agent_configs=agent_configs,
)
+
+ if self.controller is not None:
+ self.controller.agent_task = asyncio.run_coroutine_threadsafe(self.controller.start_step_loop(), self.loop) # type: ignore
+
+ def _run(self):
+ asyncio.set_event_loop(self.loop)
+ self.loop.run_forever()
async def close(self):
"""Closes the Agent session"""
@@ -89,9 +103,13 @@ async def close(self):
self.runtime.close()
if self.security_analyzer is not None:
await self.security_analyzer.close()
+
+ self.loop.call_soon_threadsafe(self.loop.stop)
+ self.thread.join()
+
self._closed = True
- async def _create_security_analyzer(self, security_analyzer: str | None):
+ def _create_security_analyzer(self, security_analyzer: str | None):
"""Creates a SecurityAnalyzer instance that will be used to analyze the agent actions
Parameters:
@@ -104,7 +122,7 @@ async def _create_security_analyzer(self, security_analyzer: str | None):
security_analyzer, SecurityAnalyzer
)(self.event_stream)
- async def _create_runtime(
+ def _create_runtime(
self,
runtime_name: str,
config: AppConfig,
@@ -125,8 +143,7 @@ async def _create_runtime(
logger.info(f'Initializing runtime `{runtime_name}` now...')
runtime_cls = get_runtime_cls(runtime_name)
- self.runtime = await asyncio.to_thread(
- runtime_cls,
+ self.runtime = runtime_cls(
config=config,
event_stream=self.event_stream,
sid=self.sid,
@@ -141,7 +158,7 @@ async def _create_runtime(
else:
logger.warning('Runtime initialization failed')
- async def _create_controller(
+ def _create_controller(
self,
agent: Agent,
confirmation_mode: bool,
From 1d052818ae51856c13e6d468ab79673747440ae5 Mon Sep 17 00:00:00 2001
From: mamoodi
Date: Tue, 24 Sep 2024 23:20:45 -0400
Subject: [PATCH 12/12] Set runtime container image so it doesn't need to be
rebuilt (#4035)
---
.github/workflows/ghcr_runtime.yml | 16 +++++++---------
tests/runtime/conftest.py | 1 +
2 files changed, 8 insertions(+), 9 deletions(-)
diff --git a/.github/workflows/ghcr_runtime.yml b/.github/workflows/ghcr_runtime.yml
index cf49c2384ab..8d6f622058b 100644
--- a/.github/workflows/ghcr_runtime.yml
+++ b/.github/workflows/ghcr_runtime.yml
@@ -145,8 +145,7 @@ jobs:
run: make install-python-dependencies
- name: Run runtime tests
run: |
- # We install pytest-xdist in order to run tests across CPUs. However, tests start to fail when we run
- # then across more than 2 CPUs for some reason
+ # We install pytest-xdist in order to run tests across CPUs
poetry run pip install pytest-xdist
# Install to be able to retry on failures for flaky tests
@@ -158,10 +157,10 @@ jobs:
SKIP_CONTAINER_LOGS=true \
TEST_RUNTIME=eventstream \
SANDBOX_USER_ID=$(id -u) \
- SANDBOX_BASE_CONTAINER_IMAGE=$image_name \
+ SANDBOX_RUNTIME_CONTAINER_IMAGE=$image_name \
TEST_IN_CI=true \
RUN_AS_OPENHANDS=false \
- poetry run pytest -n 3 --reruns 1 --reruns-delay 3 --cov=agenthub --cov=openhands --cov-report=xml -s ./tests/runtime
+ poetry run pytest -n 3 -raR --reruns 1 --reruns-delay 3 --cov=agenthub --cov=openhands --cov-report=xml -s ./tests/runtime
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
env:
@@ -207,8 +206,7 @@ jobs:
run: make install-python-dependencies
- name: Run runtime tests
run: |
- # We install pytest-xdist in order to run tests across CPUs. However, tests start to fail when we run
- # then across more than 2 CPUs for some reason
+ # We install pytest-xdist in order to run tests across CPUs
poetry run pip install pytest-xdist
# Install to be able to retry on failures for flaky tests
@@ -220,10 +218,10 @@ jobs:
SKIP_CONTAINER_LOGS=true \
TEST_RUNTIME=eventstream \
SANDBOX_USER_ID=$(id -u) \
- SANDBOX_BASE_CONTAINER_IMAGE=$image_name \
+ SANDBOX_RUNTIME_CONTAINER_IMAGE=$image_name \
TEST_IN_CI=true \
RUN_AS_OPENHANDS=true \
- poetry run pytest -n 3 --reruns 1 --reruns-delay 3 --cov=agenthub --cov=openhands --cov-report=xml -s ./tests/runtime
+ poetry run pytest -n 3 -raR --reruns 1 --reruns-delay 3 --cov=agenthub --cov=openhands --cov-report=xml -s ./tests/runtime
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
env:
@@ -275,7 +273,7 @@ jobs:
TEST_RUNTIME=eventstream \
SANDBOX_USER_ID=$(id -u) \
- SANDBOX_BASE_CONTAINER_IMAGE=$image_name \
+ SANDBOX_RUNTIME_CONTAINER_IMAGE=$image_name \
TEST_IN_CI=true \
TEST_ONLY=true \
./tests/integration/regenerate.sh
diff --git a/tests/runtime/conftest.py b/tests/runtime/conftest.py
index 6ce93256e4a..2308244fb35 100644
--- a/tests/runtime/conftest.py
+++ b/tests/runtime/conftest.py
@@ -243,6 +243,7 @@ def _load_runtime(
if base_container_image is not None:
config.sandbox.base_container_image = base_container_image
+ config.sandbox.runtime_container_image = None
file_store = get_file_store(config.file_store, config.file_store_path)
event_stream = EventStream(sid, file_store)