From cdd8360cbb14371e484bd0858ee62ff15be325f3 Mon Sep 17 00:00:00 2001 From: eureka928 Date: Sat, 7 Feb 2026 04:57:08 +0100 Subject: [PATCH 01/10] feat: add Radix UI Checkbox component Create reusable checkbox component following the existing switch.tsx pattern with React.forwardRef and design token classes. --- src/components/ui/checkbox.tsx | 42 ++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/components/ui/checkbox.tsx diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx new file mode 100644 index 000000000..2b7432a18 --- /dev/null +++ b/src/components/ui/checkbox.tsx @@ -0,0 +1,42 @@ +// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. ========= +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. ========= + +import * as CheckboxPrimitives from '@radix-ui/react-checkbox'; +import { Check } from 'lucide-react'; +import * as React from 'react'; + +import { cn } from '@/lib/utils'; + +const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)); +Checkbox.displayName = CheckboxPrimitives.Root.displayName; + +export { Checkbox }; From 214ad7f141bfcef99501d627e40e3647dfde2255 Mon Sep 17 00:00:00 2001 From: eureka928 Date: Sat, 7 Feb 2026 04:57:19 +0100 Subject: [PATCH 02/10] feat: add i18n keys for privacy consent and help improve toggle Add agree-to-terms and please-accept-terms keys to all layout.json locale files. Add help-improve-eigent keys to all setting.json files. --- src/i18n/locales/ar/layout.json | 4 +++- src/i18n/locales/ar/setting.json | 4 +++- src/i18n/locales/de/layout.json | 4 +++- src/i18n/locales/de/setting.json | 4 +++- src/i18n/locales/es/layout.json | 4 +++- src/i18n/locales/es/setting.json | 4 +++- src/i18n/locales/fr/layout.json | 4 +++- src/i18n/locales/fr/setting.json | 4 +++- src/i18n/locales/it/layout.json | 4 +++- src/i18n/locales/it/setting.json | 4 +++- src/i18n/locales/ja/layout.json | 4 +++- src/i18n/locales/ja/setting.json | 4 +++- src/i18n/locales/ko/layout.json | 4 +++- src/i18n/locales/ko/setting.json | 4 +++- src/i18n/locales/ru/layout.json | 4 +++- src/i18n/locales/ru/setting.json | 4 +++- src/i18n/locales/zh-Hans/layout.json | 4 +++- src/i18n/locales/zh-Hans/setting.json | 4 +++- src/i18n/locales/zh-Hant/layout.json | 4 +++- src/i18n/locales/zh-Hant/setting.json | 4 +++- 20 files changed, 60 insertions(+), 20 deletions(-) diff --git a/src/i18n/locales/ar/layout.json b/src/i18n/locales/ar/layout.json index 75f758ab6..6387f89e5 100644 --- a/src/i18n/locales/ar/layout.json +++ b/src/i18n/locales/ar/layout.json @@ -166,5 +166,7 @@ "days-ago": "أيام مضت", "delete-project": "حذف المشروع", "delete-project-confirmation": "هل أنت متأكد من أنك تريد حذف هذا المشروع وجميع مهامه؟ لا يمكن التراجع عن هذا الإجراء.", - "please-select-model": "يرجى اختيار نموذج في الإعدادات > النماذج للمتابعة." + "please-select-model": "يرجى اختيار نموذج في الإعدادات > النماذج للمتابعة.", + "agree-to-terms": "I agree to the", + "please-accept-terms": "Please accept the Terms of Use and Privacy Policy to continue" } diff --git a/src/i18n/locales/ar/setting.json b/src/i18n/locales/ar/setting.json index 1a76d627e..70046b158 100644 --- a/src/i18n/locales/ar/setting.json +++ b/src/i18n/locales/ar/setting.json @@ -361,5 +361,7 @@ "preferred-ide": "IDE المفضل", "preferred-ide-description": "اختر التطبيق الذي سيتم استخدامه عند فتح مجلدات مشاريع الوكيل.", - "system-file-manager": "مدير ملفات النظام" + "system-file-manager": "مدير ملفات النظام", + "help-improve-eigent": "Help Improve Eigent", + "help-improve-eigent-description": "Allow Eigent to collect anonymous error logs and usage data to improve the service. This is optional and can be changed at any time." } diff --git a/src/i18n/locales/de/layout.json b/src/i18n/locales/de/layout.json index 4cc133adb..a00f9b372 100644 --- a/src/i18n/locales/de/layout.json +++ b/src/i18n/locales/de/layout.json @@ -166,5 +166,7 @@ "days-ago": "Tage zuvor", "delete-project": "Projekt löschen", "delete-project-confirmation": "Sind Sie sicher, dass Sie dieses Projekt und alle seine Aufgaben löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", - "please-select-model": "Bitte wählen Sie ein Modell unter Einstellungen > Modelle aus, um fortzufahren." + "please-select-model": "Bitte wählen Sie ein Modell unter Einstellungen > Modelle aus, um fortzufahren.", + "agree-to-terms": "I agree to the", + "please-accept-terms": "Please accept the Terms of Use and Privacy Policy to continue" } diff --git a/src/i18n/locales/de/setting.json b/src/i18n/locales/de/setting.json index 993ffa0be..611e50df7 100644 --- a/src/i18n/locales/de/setting.json +++ b/src/i18n/locales/de/setting.json @@ -271,5 +271,7 @@ "preferred-ide": "Bevorzugte IDE", "preferred-ide-description": "Wählen Sie die Anwendung aus, die beim Öffnen von Agent-Projektordnern verwendet werden soll.", - "system-file-manager": "System-Dateimanager" + "system-file-manager": "System-Dateimanager", + "help-improve-eigent": "Help Improve Eigent", + "help-improve-eigent-description": "Allow Eigent to collect anonymous error logs and usage data to improve the service. This is optional and can be changed at any time." } diff --git a/src/i18n/locales/es/layout.json b/src/i18n/locales/es/layout.json index 3f700d514..d20a4ad5a 100644 --- a/src/i18n/locales/es/layout.json +++ b/src/i18n/locales/es/layout.json @@ -166,5 +166,7 @@ "days-ago": "días atrás", "delete-project": "Eliminar Proyecto", "delete-project-confirmation": "¿Estás seguro de que quieres eliminar este proyecto y todas sus tareas? Esta acción no se puede deshacer.", - "please-select-model": "Por favor, selecciona un modelo en Configuración > Modelos para continuar." + "please-select-model": "Por favor, selecciona un modelo en Configuración > Modelos para continuar.", + "agree-to-terms": "I agree to the", + "please-accept-terms": "Please accept the Terms of Use and Privacy Policy to continue" } diff --git a/src/i18n/locales/es/setting.json b/src/i18n/locales/es/setting.json index 703238905..20e14d119 100644 --- a/src/i18n/locales/es/setting.json +++ b/src/i18n/locales/es/setting.json @@ -271,5 +271,7 @@ "preferred-ide": "IDE preferido", "preferred-ide-description": "Elija qué aplicación usar al abrir las carpetas de proyectos del agente.", - "system-file-manager": "Administrador de archivos del sistema" + "system-file-manager": "Administrador de archivos del sistema", + "help-improve-eigent": "Help Improve Eigent", + "help-improve-eigent-description": "Allow Eigent to collect anonymous error logs and usage data to improve the service. This is optional and can be changed at any time." } diff --git a/src/i18n/locales/fr/layout.json b/src/i18n/locales/fr/layout.json index d2415b021..00a406620 100644 --- a/src/i18n/locales/fr/layout.json +++ b/src/i18n/locales/fr/layout.json @@ -166,5 +166,7 @@ "days-ago": "jours auparavant", "delete-project": "Supprimer le Projet", "delete-project-confirmation": "Êtes-vous sûr de vouloir supprimer ce projet et toutes ses tâches ? Cette action ne peut pas être annulée.", - "please-select-model": "Veuillez sélectionner un modèle dans Paramètres > Modèles pour continuer." + "please-select-model": "Veuillez sélectionner un modèle dans Paramètres > Modèles pour continuer.", + "agree-to-terms": "I agree to the", + "please-accept-terms": "Please accept the Terms of Use and Privacy Policy to continue" } diff --git a/src/i18n/locales/fr/setting.json b/src/i18n/locales/fr/setting.json index f35b8a71d..cc6acd28d 100644 --- a/src/i18n/locales/fr/setting.json +++ b/src/i18n/locales/fr/setting.json @@ -247,5 +247,7 @@ "preferred-ide": "IDE préféré", "preferred-ide-description": "Choisissez l'application à utiliser lors de l'ouverture des dossiers de projets d'agent.", - "system-file-manager": "Gestionnaire de fichiers système" + "system-file-manager": "Gestionnaire de fichiers système", + "help-improve-eigent": "Help Improve Eigent", + "help-improve-eigent-description": "Allow Eigent to collect anonymous error logs and usage data to improve the service. This is optional and can be changed at any time." } diff --git a/src/i18n/locales/it/layout.json b/src/i18n/locales/it/layout.json index 1b5b365d0..0b40e211a 100644 --- a/src/i18n/locales/it/layout.json +++ b/src/i18n/locales/it/layout.json @@ -166,5 +166,7 @@ "days-ago": "giorni fa", "delete-project": "Elimina Progetto", "delete-project-confirmation": "Sei sicuro di voler eliminare questo progetto e tutte le sue attività? Questa azione non può essere annullata.", - "please-select-model": "Seleziona un modello in Impostazioni > Modelli per continuare." + "please-select-model": "Seleziona un modello in Impostazioni > Modelli per continuare.", + "agree-to-terms": "I agree to the", + "please-accept-terms": "Please accept the Terms of Use and Privacy Policy to continue" } diff --git a/src/i18n/locales/it/setting.json b/src/i18n/locales/it/setting.json index 94477a028..482192a4c 100644 --- a/src/i18n/locales/it/setting.json +++ b/src/i18n/locales/it/setting.json @@ -271,5 +271,7 @@ "preferred-ide": "IDE preferito", "preferred-ide-description": "Scegli quale applicazione utilizzare per aprire le cartelle dei progetti dell'agente.", - "system-file-manager": "Gestione file di sistema" + "system-file-manager": "Gestione file di sistema", + "help-improve-eigent": "Help Improve Eigent", + "help-improve-eigent-description": "Allow Eigent to collect anonymous error logs and usage data to improve the service. This is optional and can be changed at any time." } diff --git a/src/i18n/locales/ja/layout.json b/src/i18n/locales/ja/layout.json index 6c0e9e45c..ee1adb2f6 100644 --- a/src/i18n/locales/ja/layout.json +++ b/src/i18n/locales/ja/layout.json @@ -166,5 +166,7 @@ "days-ago": "日前", "delete-project": "プロジェクトを削除", "delete-project-confirmation": "このプロジェクトとそのすべてのタスクを削除してもよろしいですか?この操作は元に戻せません。", - "please-select-model": "続行するには、設定 > モデルでモデルを選択してください。" + "please-select-model": "続行するには、設定 > モデルでモデルを選択してください。", + "agree-to-terms": "I agree to the", + "please-accept-terms": "Please accept the Terms of Use and Privacy Policy to continue" } diff --git a/src/i18n/locales/ja/setting.json b/src/i18n/locales/ja/setting.json index ce0f5449e..9934a900a 100644 --- a/src/i18n/locales/ja/setting.json +++ b/src/i18n/locales/ja/setting.json @@ -271,5 +271,7 @@ "preferred-ide": "優先IDE", "preferred-ide-description": "エージェントプロジェクトフォルダを開くときに使用するアプリケーションを選択します。", - "system-file-manager": "システムファイルマネージャー" + "system-file-manager": "システムファイルマネージャー", + "help-improve-eigent": "Help Improve Eigent", + "help-improve-eigent-description": "Allow Eigent to collect anonymous error logs and usage data to improve the service. This is optional and can be changed at any time." } diff --git a/src/i18n/locales/ko/layout.json b/src/i18n/locales/ko/layout.json index d6678e942..054b18c43 100644 --- a/src/i18n/locales/ko/layout.json +++ b/src/i18n/locales/ko/layout.json @@ -166,5 +166,7 @@ "days-ago": "일 전", "delete-project": "프로젝트 삭제", "delete-project-confirmation": "이 프로젝트와 모든 작업을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.", - "please-select-model": "계속하려면 설정 > 모델에서 모델을 선택하세요." + "please-select-model": "계속하려면 설정 > 모델에서 모델을 선택하세요.", + "agree-to-terms": "I agree to the", + "please-accept-terms": "Please accept the Terms of Use and Privacy Policy to continue" } diff --git a/src/i18n/locales/ko/setting.json b/src/i18n/locales/ko/setting.json index c551af975..ca465ddc8 100644 --- a/src/i18n/locales/ko/setting.json +++ b/src/i18n/locales/ko/setting.json @@ -271,5 +271,7 @@ "preferred-ide": "선호 IDE", "preferred-ide-description": "에이전트 프로젝트 폴더를 열 때 사용할 애플리케이션을 선택합니다.", - "system-file-manager": "시스템 파일 관리자" + "system-file-manager": "시스템 파일 관리자", + "help-improve-eigent": "Help Improve Eigent", + "help-improve-eigent-description": "Allow Eigent to collect anonymous error logs and usage data to improve the service. This is optional and can be changed at any time." } diff --git a/src/i18n/locales/ru/layout.json b/src/i18n/locales/ru/layout.json index bc93cfc18..463639134 100644 --- a/src/i18n/locales/ru/layout.json +++ b/src/i18n/locales/ru/layout.json @@ -166,5 +166,7 @@ "days-ago": "дней назад", "delete-project": "Удалить проект", "delete-project-confirmation": "Вы уверены, что хотите удалить этот проект и все его задачи? Это действие нельзя отменить.", - "please-select-model": "Пожалуйста, выберите модель в Настройки > Модели, чтобы продолжить." + "please-select-model": "Пожалуйста, выберите модель в Настройки > Модели, чтобы продолжить.", + "agree-to-terms": "I agree to the", + "please-accept-terms": "Please accept the Terms of Use and Privacy Policy to continue" } diff --git a/src/i18n/locales/ru/setting.json b/src/i18n/locales/ru/setting.json index cef404dd7..e91fc628c 100644 --- a/src/i18n/locales/ru/setting.json +++ b/src/i18n/locales/ru/setting.json @@ -271,5 +271,7 @@ "preferred-ide": "Предпочитаемая IDE", "preferred-ide-description": "Выберите приложение для открытия папок проектов агента.", - "system-file-manager": "Системный файловый менеджер" + "system-file-manager": "Системный файловый менеджер", + "help-improve-eigent": "Help Improve Eigent", + "help-improve-eigent-description": "Allow Eigent to collect anonymous error logs and usage data to improve the service. This is optional and can be changed at any time." } diff --git a/src/i18n/locales/zh-Hans/layout.json b/src/i18n/locales/zh-Hans/layout.json index 4a28cc55e..da1e302db 100644 --- a/src/i18n/locales/zh-Hans/layout.json +++ b/src/i18n/locales/zh-Hans/layout.json @@ -168,5 +168,7 @@ "days-ago": "天前", "delete-project": "删除项目", "delete-project-confirmation": "您确定要删除此项目及其所有任务吗?此操作无法撤销。", - "please-select-model": "请在设置 > 模型中选择一个模型以继续。" + "please-select-model": "请在设置 > 模型中选择一个模型以继续。", + "agree-to-terms": "I agree to the", + "please-accept-terms": "Please accept the Terms of Use and Privacy Policy to continue" } diff --git a/src/i18n/locales/zh-Hans/setting.json b/src/i18n/locales/zh-Hans/setting.json index 88e29ab5e..adeb1a262 100644 --- a/src/i18n/locales/zh-Hans/setting.json +++ b/src/i18n/locales/zh-Hans/setting.json @@ -215,5 +215,7 @@ "preferred-ide": "首选 IDE", "preferred-ide-description": "选择打开智能体项目文件夹时使用的应用程序。", - "system-file-manager": "系统文件管理器" + "system-file-manager": "系统文件管理器", + "help-improve-eigent": "Help Improve Eigent", + "help-improve-eigent-description": "Allow Eigent to collect anonymous error logs and usage data to improve the service. This is optional and can be changed at any time." } diff --git a/src/i18n/locales/zh-Hant/layout.json b/src/i18n/locales/zh-Hant/layout.json index 1a33236cd..f4bfe2cc2 100644 --- a/src/i18n/locales/zh-Hant/layout.json +++ b/src/i18n/locales/zh-Hant/layout.json @@ -168,5 +168,7 @@ "days-ago": "天前", "delete-project": "刪除專案", "delete-project-confirmation": "您確定要刪除此專案及其所有任務嗎?此操作無法撤銷。", - "please-select-model": "請在設定 > 模型中選擇一個模型以繼續。" + "please-select-model": "請在設定 > 模型中選擇一個模型以繼續。", + "agree-to-terms": "I agree to the", + "please-accept-terms": "Please accept the Terms of Use and Privacy Policy to continue" } diff --git a/src/i18n/locales/zh-Hant/setting.json b/src/i18n/locales/zh-Hant/setting.json index 74b94d071..145d68e37 100644 --- a/src/i18n/locales/zh-Hant/setting.json +++ b/src/i18n/locales/zh-Hant/setting.json @@ -193,5 +193,7 @@ "proxy-restart-hint": "需要重新啟動應用程式以套用代理變更。", "preferred-ide": "偏好 IDE", "preferred-ide-description": "選擇開啟智能體專案資料夾時使用的應用程式。", - "system-file-manager": "系統檔案管理器" + "system-file-manager": "系統檔案管理器", + "help-improve-eigent": "Help Improve Eigent", + "help-improve-eigent-description": "Allow Eigent to collect anonymous error logs and usage data to improve the service. This is optional and can be changed at any time." } From 1b0beaf5eb7ce6ce95525e10b6642ce7679b7ff7 Mon Sep 17 00:00:00 2001 From: eureka928 Date: Sat, 7 Feb 2026 04:57:26 +0100 Subject: [PATCH 03/10] feat: add privacy consent checkbox to Login page Add mandatory terms acceptance checkbox that gates login and OAuth flows. Call acceptPrivacy API after successful authentication. --- src/pages/Login.tsx | 72 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 58 insertions(+), 14 deletions(-) diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index dde498fb4..2d167b77a 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -20,11 +20,12 @@ import { useLocation, useNavigate } from 'react-router-dom'; import { Input } from '@/components/ui/input'; -import { proxyFetchPost } from '@/api/http'; +import { proxyFetchPost, proxyFetchPut } from '@/api/http'; import eyeOff from '@/assets/eye-off.svg'; import eye from '@/assets/eye.svg'; import github2 from '@/assets/github2.svg'; import google from '@/assets/google.svg'; +import { Checkbox } from '@/components/ui/checkbox'; import WindowControls from '@/components/WindowControls'; import { hasStackKeys } from '@/lib'; import { useTranslation } from 'react-i18next'; @@ -53,6 +54,7 @@ export default function Login() { }); const [isLoading, setIsLoading] = useState(false); const [generalError, setGeneralError] = useState(''); + const [termsAccepted, setTermsAccepted] = useState(false); const titlebarRef = useRef(null); const [platform, setPlatform] = useState(''); @@ -61,6 +63,19 @@ export default function Login() { return emailRegex.test(email); }; + const acceptPrivacy = async () => { + try { + await proxyFetchPut('/api/user/privacy', { + take_screenshot: true, + access_local_software: true, + access_your_address: true, + password_storage: true, + }); + } catch (err) { + console.error('Failed to set privacy:', err); + } + }; + const validateForm = () => { const newErrors = { email: '', @@ -142,6 +157,10 @@ export default function Login() { // const handleLogin = async () => { + if (!termsAccepted) { + setGeneralError(t('layout.please-accept-terms')); + return; + } if (!validateForm()) { return; } @@ -165,6 +184,7 @@ export default function Login() { // Record VITE_USE_LOCAL_PROXY value at login const localProxyValue = import.meta.env.VITE_USE_LOCAL_PROXY || null; setLocalProxyValue(localProxyValue); + acceptPrivacy(); navigate('/'); } catch (error: any) { console.error('Login failed:', error); @@ -197,6 +217,7 @@ export default function Login() { // Record VITE_USE_LOCAL_PROXY value at login const localProxyValue = import.meta.env.VITE_USE_LOCAL_PROXY || null; setLocalProxyValue(localProxyValue); + acceptPrivacy(); navigate('/'); } catch (error: any) { console.error('Login failed:', error); @@ -225,6 +246,10 @@ export default function Login() { console.error('Stack app not initialized'); return; } + if (!termsAccepted) { + setGeneralError(t('layout.please-accept-terms')); + return; + } console.log('handleReloadBtn1', type); const cookies = document.cookie.split('; '); cookies.forEach((cookie) => { @@ -486,19 +511,38 @@ export default function Login() { - +
+ setTermsAccepted(checked === true)} + /> + +
From e681db1de9b45e8e094b1b297e9ea66c173d2740 Mon Sep 17 00:00:00 2001 From: eureka928 Date: Sat, 7 Feb 2026 04:57:36 +0100 Subject: [PATCH 04/10] feat: add privacy consent checkbox to SignUp page Add mandatory terms acceptance checkbox that gates registration and OAuth flows. Call acceptPrivacy API after successful OAuth login. --- src/pages/SignUp.tsx | 71 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 57 insertions(+), 14 deletions(-) diff --git a/src/pages/SignUp.tsx b/src/pages/SignUp.tsx index de282beb5..c2202d887 100644 --- a/src/pages/SignUp.tsx +++ b/src/pages/SignUp.tsx @@ -20,13 +20,14 @@ import { useLocation, useNavigate } from 'react-router-dom'; import { Input } from '@/components/ui/input'; -import { proxyFetchPost } from '@/api/http'; +import { proxyFetchPost, proxyFetchPut } from '@/api/http'; import background from '@/assets/background.png'; import eyeOff from '@/assets/eye-off.svg'; import eye from '@/assets/eye.svg'; import github2 from '@/assets/github2.svg'; import google from '@/assets/google.svg'; import eigentLogo from '@/assets/logo/eigent_icon.png'; +import { Checkbox } from '@/components/ui/checkbox'; import WindowControls from '@/components/WindowControls'; import { hasStackKeys } from '@/lib'; import { useTranslation } from 'react-i18next'; @@ -54,6 +55,7 @@ export default function SignUp() { }); const [isLoading, setIsLoading] = useState(false); const [generalError, setGeneralError] = useState(''); + const [termsAccepted, setTermsAccepted] = useState(false); const titlebarRef = useRef(null); const [platform, setPlatform] = useState(''); @@ -62,6 +64,19 @@ export default function SignUp() { return emailRegex.test(email); }; + const acceptPrivacy = async () => { + try { + await proxyFetchPut('/api/user/privacy', { + take_screenshot: true, + access_local_software: true, + access_your_address: true, + password_storage: true, + }); + } catch (err) { + console.error('Failed to set privacy:', err); + } + }; + const validateForm = () => { const newErrors = { email: '', @@ -104,6 +119,10 @@ export default function SignUp() { }; const handleRegister = async () => { + if (!termsAccepted) { + setGeneralError(t('layout.please-accept-terms')); + return; + } if (!validateForm()) { return; } @@ -170,6 +189,7 @@ export default function SignUp() { } console.log('data', data); setAuth({ email: formData.email, ...data }); + acceptPrivacy(); navigate('/'); } catch (error: any) { console.error('Login failed:', error); @@ -184,6 +204,10 @@ export default function SignUp() { ); const handleReloadBtn = async (type: string) => { + if (!termsAccepted) { + setGeneralError(t('layout.please-accept-terms')); + return; + } localStorage.setItem('invite_code', formData.invite_code); console.log('handleReloadBtn1', type); const cookies = document.cookie.split('; '); @@ -434,19 +458,38 @@ export default function SignUp() { - +
+ setTermsAccepted(checked === true)} + /> + +
From 8ab936b80a2d3b56d3a2ba4d6da5d13abdd90e0a Mon Sep 17 00:00:00 2001 From: eureka928 Date: Sat, 7 Feb 2026 04:57:47 +0100 Subject: [PATCH 05/10] refactor: remove privacy gating from ChatBox Remove privacy state, API fetch, consent banner, and all privacy checks from ChatBox. Privacy consent is now handled at login. --- src/components/ChatBox/index.tsx | 168 +++---------------------------- 1 file changed, 13 insertions(+), 155 deletions(-) diff --git a/src/components/ChatBox/index.tsx b/src/components/ChatBox/index.tsx index 495b2fcd9..b086f950f 100644 --- a/src/components/ChatBox/index.tsx +++ b/src/components/ChatBox/index.tsx @@ -18,13 +18,12 @@ import { fetchPut, proxyFetchDelete, proxyFetchGet, - proxyFetchPut, } from '@/api/http'; import useChatStoreAdapter from '@/hooks/useChatStoreAdapter'; import { generateUniqueId, replayActiveTask } from '@/lib'; import { useAuthStore } from '@/store/authStore'; import { AgentStep, ChatTaskStatus } from '@/types/constants'; -import { Square, SquareCheckBig, TriangleAlert } from 'lucide-react'; +import { TriangleAlert } from 'lucide-react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'; @@ -40,13 +39,10 @@ export default function ChatBox(): JSX.Element { const { t } = useTranslation(); const textareaRef = useRef(null); const scrollContainerRef = useRef(null); - const [privacy, setPrivacy] = useState(false); - const [isPrivacyLoaded, setIsPrivacyLoaded] = useState(false); const [_hasSearchKey, setHasSearchKey] = useState(false); const [hasModel, setHasModel] = useState(false); const [isConfigLoaded, setIsConfigLoaded] = useState(false); const scrollTimeoutRef = useRef(null); - // const [privacyDialogOpen, setPrivacyDialogOpen] = useState(false); const { modelType } = useAuthStore(); const [useCloudModelInDev, setUseCloudModelInDev] = useState(false); const location = useLocation(); @@ -87,20 +83,6 @@ export default function ChatBox(): JSX.Element { } }, [modelType]); useEffect(() => { - proxyFetchGet('/api/user/privacy') - .then((res) => { - let _privacy = 0; - Object.keys(res).forEach((key) => { - if (!res[key]) { - _privacy++; - return; - } - }); - setPrivacy(_privacy === 0 ? true : false); - }) - .catch((err) => console.error('Failed to fetch settings:', err)) - .finally(() => setIsPrivacyLoaded(true)); - proxyFetchGet('/api/configs') .then((configsRes) => { const configs = Array.isArray(configsRes) ? configsRes : []; @@ -120,42 +102,23 @@ export default function ChatBox(): JSX.Element { // Re-check model config when returning from settings page useEffect(() => { // Check when location changes (user navigates) - if (location.pathname === '/' && privacy) { + if (location.pathname === '/') { checkModelConfig(); } - }, [location.pathname, privacy, checkModelConfig]); + }, [location.pathname, checkModelConfig]); // Also check when window gains focus (user returns from settings) useEffect(() => { const handleFocus = () => { - if (privacy) { - checkModelConfig(); - } + checkModelConfig(); }; window.addEventListener('focus', handleFocus); return () => { window.removeEventListener('focus', handleFocus); }; - }, [privacy, checkModelConfig]); - - // Refresh privacy status when dialog closes - // useEffect(() => { - // if (!privacyDialogOpen) { - // proxyFetchGet("/api/user/privacy") - // .then((res) => { - // let _privacy = 0; - // Object.keys(res).forEach((key) => { - // if (!res[key]) { - // _privacy++; - // return; - // } - // }); - // setPrivacy(_privacy === 0 ? true : false); - // }) - // .catch((err) => console.error("Failed to fetch settings:", err)); - // } - // }, [privacyDialogOpen]); + }, [checkModelConfig]); + const [searchParams] = useSearchParams(); const share_token = searchParams.get('share_token'); @@ -279,9 +242,8 @@ export default function ChatBox(): JSX.Element { if (isTaskBusy) return true; - // Standard checks - check model first, then privacy + // Standard checks if (!hasModel) return true; - if (!privacy) return true; if (useCloudModelInDev) return true; if (task.isContextExceeded) return true; @@ -289,7 +251,6 @@ export default function ChatBox(): JSX.Element { }, [ chatStore?.activeTaskId, chatStore?.tasks, - privacy, hasModel, useCloudModelInDev, isTaskBusy, @@ -310,10 +271,6 @@ export default function ChatBox(): JSX.Element { navigate('/setting/models'); return; } - if (!privacy) { - toast.error('Please accept the privacy policy first.'); - return; - } let _token: string = token.split('__')[0]; let taskId: string = token.split('__')[1]; @@ -343,15 +300,15 @@ export default function ChatBox(): JSX.Element { } } }, - [chatStore, projectStore.activeProjectId, hasModel, privacy, navigate] + [chatStore, projectStore.activeProjectId, hasModel, navigate] ); useEffect(() => { - // Wait for both config and privacy to be loaded before handling share token - if (share_token && isConfigLoaded && isPrivacyLoaded) { + // Wait for config to be loaded before handling share token + if (share_token && isConfigLoaded) { handleSendShare(share_token); } - }, [share_token, isConfigLoaded, isPrivacyLoaded, handleSendShare]); + }, [share_token, isConfigLoaded, handleSendShare]); useEffect(() => { if (!chatStore) return; @@ -407,16 +364,12 @@ export default function ChatBox(): JSX.Element { const _taskId = taskId || chatStore.activeTaskId; if (message.trim() === '' && !messageStr) return; - // Check model first, then privacy + // Check model configuration if (!hasModel) { toast.error('Please select a model first.'); navigate('/setting/models'); return; } - if (!privacy) { - toast.error('Please accept the privacy policy first.'); - return; - } const tempMessageContent = messageStr || message; chatStore.setHasMessages(_taskId as string, true); @@ -603,23 +556,6 @@ export default function ChatBox(): JSX.Element { setMessage(''); } } else { - if (!privacy) { - const API_FIELDS = [ - 'take_screenshot', - 'access_local_software', - 'access_your_address', - 'password_storage', - ]; - const requestData = { - [API_FIELDS[0]]: true, - [API_FIELDS[1]]: true, - [API_FIELDS[2]]: true, - [API_FIELDS[3]]: true, - }; - proxyFetchPut('/api/user/privacy', requestData); - setPrivacy(true); - } - setTimeout(() => { scrollToBottom(); }, 200); @@ -1064,7 +1000,6 @@ export default function ChatBox(): JSX.Element { disabled: isInputDisabled, textareaRef: textareaRef, allowDragDrop: true, - privacy: privacy, useCloudModelInDev: useCloudModelInDev, }} /> @@ -1108,7 +1043,6 @@ export default function ChatBox(): JSX.Element { disabled: isInputDisabled, textareaRef: textareaRef, allowDragDrop: false, - privacy: privacy, useCloudModelInDev: useCloudModelInDev, }} /> @@ -1129,83 +1063,7 @@ export default function ChatBox(): JSX.Element { ) : null} - {hasModel && !privacy ? ( -
-
{ - // Check if the click target is an anchor tag - const target = e.target as HTMLElement; - if (target.tagName === 'A') { - // Let the anchor tag handle the click naturally - return; - } - // Toggle privacy if not already enabled - if (!privacy) { - // Enable privacy permissions - const API_FIELDS = [ - 'take_screenshot', - 'access_local_software', - 'access_your_address', - 'password_storage', - ]; - const requestData = { - [API_FIELDS[0]]: true, - [API_FIELDS[1]]: true, - [API_FIELDS[2]]: true, - [API_FIELDS[3]]: true, - }; - proxyFetchPut('/api/user/privacy', requestData); - setPrivacy(true); - } - }} - className="group flex max-w-64 cursor-pointer items-center justify-center gap-2 rounded-xl bg-surface-information px-md py-xs transition-all duration-200 hover:bg-surface-tertiary" - > -
- {privacy ? ( - - ) : ( -
- - -
- )} -
- - {t('layout.by-messaging-eigent')}{' '} - e.stopPropagation()} - rel="noreferrer" - > - {t('layout.terms-of-use')} - {' '} - {t('layout.and')}{' '} - e.stopPropagation()} - rel="noreferrer" - > - {t('layout.privacy-policy')} - - . - -
-
- ) : null} - {privacy && hasModel && ( + {hasModel && (
{[ { From f3ad42260d539b1c7abff954b65dded07c3f0813 Mon Sep 17 00:00:00 2001 From: eureka928 Date: Sat, 7 Feb 2026 04:57:57 +0100 Subject: [PATCH 06/10] refactor: remove privacy prop from InputBox Remove privacy prop from interface, destructured props, drag-drop guards, and add-file button disabled condition. --- src/components/ChatBox/BottomBox/InputBox.tsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/components/ChatBox/BottomBox/InputBox.tsx b/src/components/ChatBox/BottomBox/InputBox.tsx index 580342286..05ae6c60e 100644 --- a/src/components/ChatBox/BottomBox/InputBox.tsx +++ b/src/components/ChatBox/BottomBox/InputBox.tsx @@ -66,8 +66,6 @@ export interface InputboxProps { textareaRef?: React.RefObject; /** Allow drag and drop */ allowDragDrop?: boolean; - /** Privacy mode enabled */ - privacy?: boolean; /** Use cloud model in dev */ useCloudModelInDev?: boolean; } @@ -123,7 +121,6 @@ export const Inputbox = ({ className, textareaRef: externalTextareaRef, allowDragDrop = false, - privacy = true, useCloudModelInDev = false, }: InputboxProps) => { const { t } = useTranslation(); @@ -211,7 +208,7 @@ export const Inputbox = ({ }; const handleDragOver = (e: React.DragEvent) => { - if (!allowDragDrop || !privacy || useCloudModelInDev) return; + if (!allowDragDrop || useCloudModelInDev) return; if (!isFileDrag(e)) return; e.preventDefault(); e.stopPropagation(); @@ -220,7 +217,7 @@ export const Inputbox = ({ }; const handleDragEnter = (e: React.DragEvent) => { - if (!allowDragDrop || !privacy || useCloudModelInDev) return; + if (!allowDragDrop || useCloudModelInDev) return; if (!isFileDrag(e)) return; e.preventDefault(); e.stopPropagation(); @@ -240,7 +237,7 @@ export const Inputbox = ({ e.stopPropagation(); setIsDragging(false); dragCounter.current = 0; - if (!allowDragDrop || !privacy || useCloudModelInDev) return; + if (!allowDragDrop || useCloudModelInDev) return; try { const dropped = Array.from(e.dataTransfer?.files || []); if (dropped.length === 0) return; @@ -452,7 +449,7 @@ export const Inputbox = ({ size="icon" className="rounded-full" onClick={onAddFile} - disabled={disabled || !privacy || useCloudModelInDev} + disabled={disabled || useCloudModelInDev} > From f5d7d2bf9125c68fcba8244fa2a187ad64fcc6ee Mon Sep 17 00:00:00 2001 From: eureka928 Date: Sat, 7 Feb 2026 04:58:08 +0100 Subject: [PATCH 07/10] refactor: replace Settings Privacy 4-toggle with Help Improve toggle Replace the four individual privacy permission toggles with a single optional Help Improve Eigent toggle for telemetry data collection. --- src/pages/Setting/Privacy.tsx | 123 ++++------------------------------ 1 file changed, 14 insertions(+), 109 deletions(-) diff --git a/src/pages/Setting/Privacy.tsx b/src/pages/Setting/Privacy.tsx index 30b2967ea..fe139e8f9 100644 --- a/src/pages/Setting/Privacy.tsx +++ b/src/pages/Setting/Privacy.tsx @@ -15,124 +15,29 @@ import { proxyFetchGet, proxyFetchPut } from '@/api/http'; import { Button } from '@/components/ui/button'; import { Switch } from '@/components/ui/switch'; -import { useAuthStore } from '@/store/authStore'; import { ChevronDown } from 'lucide-react'; -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; export default function SettingPrivacy() { - const { email } = useAuthStore(); - const [_privacy, setPrivacy] = useState(false); + const [helpImprove, setHelpImprove] = useState(false); const { t } = useTranslation(); - const API_FIELDS = useMemo( - () => [ - 'take_screenshot', - 'access_local_software', - 'access_your_address', - 'password_storage', - ], - [] - ); - const [settings, setSettings] = useState([ - { - title: t('setting.allow-agent-to-take-screenshots'), - description: t('setting.allow-agent-to-take-screenshots-description'), - checked: false, - }, - { - title: t('setting.allow-agent-to-access-local-software'), - description: t( - 'setting.allow-agent-to-access-local-software-description' - ), - checked: false, - }, - { - title: t('setting.allow-agent-to-access-your-address'), - description: t('setting.allow-agent-to-access-your-address-description'), - checked: false, - }, - { - title: t('setting.password-storage'), - description: t('setting.password-storage-description'), - checked: false, - }, - ]); + const [isHowWeHandleOpen, setIsHowWeHandleOpen] = useState(false); + useEffect(() => { proxyFetchGet('/api/user/privacy') .then((res) => { - let hasFalse = false; - setSettings((prev) => - prev.map((item, index) => { - if (!res[API_FIELDS[index]]) { - hasFalse = true; - } - return { - ...item, - checked: res[API_FIELDS[index]] || false, - }; - }) - ); - setPrivacy(!hasFalse); + setHelpImprove(res.help_improve || false); }) .catch((err) => console.error('Failed to fetch settings:', err)); - }, [API_FIELDS]); - - const handleTurnOnAll = (type: boolean) => { - const newSettings = settings.map((item) => ({ - ...item, - checked: type, - })); - setSettings(newSettings); - setPrivacy(type); - const requestData = { - [API_FIELDS[0]]: type, - [API_FIELDS[1]]: type, - [API_FIELDS[2]]: type, - [API_FIELDS[3]]: type, - }; - - proxyFetchPut('/api/user/privacy', requestData); - }; - - const _handleToggle = (index: number) => { - setSettings((prev) => { - const newSettings = [...prev]; - newSettings[index] = { - ...newSettings[index], - checked: !newSettings[index].checked, - }; - return newSettings; - }); + }, []); - const requestData = { - [API_FIELDS[0]]: settings[0].checked, - [API_FIELDS[1]]: settings[1].checked, - [API_FIELDS[2]]: settings[2].checked, - [API_FIELDS[3]]: settings[3].checked, - }; - - requestData[API_FIELDS[index]] = !settings[index].checked; - - proxyFetchPut('/api/user/privacy', requestData).catch((err) => + const handleToggleHelpImprove = (checked: boolean) => { + setHelpImprove(checked); + proxyFetchPut('/api/user/privacy', { help_improve: checked }).catch((err) => console.error('Failed to update settings:', err) ); }; - const [logFolder, setLogFolder] = useState(''); - const [isHowWeHandleOpen, setIsHowWeHandleOpen] = useState(false); - useEffect(() => { - window.ipcRenderer - .invoke('get-log-folder', email) - .then((logFolder: any) => { - setLogFolder(logFolder); - }); - }, [email]); - - const _handleOpenFolder = () => { - if (logFolder) { - window.ipcRenderer.invoke('reveal-in-folder', logFolder + '/'); - } - }; - return (
{/* Header Section */} @@ -206,21 +111,21 @@ export default function SettingPrivacy() { )}
- {/* Enable Privacy Permissions Settings Section */} + {/* Help Improve Eigent Section */}
- {t('setting.enable-privacy-permissions-settings')} + {t('setting.help-improve-eigent')}
- {t('setting.enable-privacy-permissions-settings-description')} + {t('setting.help-improve-eigent-description')}
handleTurnOnAll(!_privacy)} + checked={helpImprove} + onCheckedChange={handleToggleHelpImprove} />
From c6f0f652f64bda44c093790e7ce4ad8a12ea2f86 Mon Sep 17 00:00:00 2001 From: eureka928 Date: Sat, 7 Feb 2026 04:58:16 +0100 Subject: [PATCH 08/10] chore: delete unused Dialog/Privacy component --- src/components/Dialog/Privacy.tsx | 201 ------------------------------ 1 file changed, 201 deletions(-) delete mode 100644 src/components/Dialog/Privacy.tsx diff --git a/src/components/Dialog/Privacy.tsx b/src/components/Dialog/Privacy.tsx deleted file mode 100644 index f2e9d9b1a..000000000 --- a/src/components/Dialog/Privacy.tsx +++ /dev/null @@ -1,201 +0,0 @@ -// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. ========= -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. ========= - -import { proxyFetchGet, proxyFetchPut } from '@/api/http'; -import { Button } from '@/components/ui/button'; -import { - Dialog, - DialogClose, - DialogContent, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from '@/components/ui/dialog'; -import { Switch } from '@/components/ui/switch'; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from '@/components/ui/tooltip'; -import { AlertCircle } from 'lucide-react'; -import { useEffect, useMemo, useState } from 'react'; -import { useTranslation } from 'react-i18next'; - -interface PrivacyDialogProps { - open: boolean; - onOpenChange: (open: boolean) => void; - trigger?: React.ReactNode; -} - -export function PrivacyDialog({ - open, - onOpenChange, - trigger, -}: PrivacyDialogProps) { - const { t } = useTranslation(); - const API_FIELDS = useMemo( - () => [ - 'take_screenshot', - 'access_local_software', - 'access_your_address', - 'password_storage', - ], - [] - ); - - const [settings, setSettings] = useState([ - { - title: t('layout.allow-agent-to-take-screenshots'), - description: t('layout.permit-the-agent-to-capture'), - checked: false, - }, - { - title: t('layout.allow-agent-to-access-local-software'), - description: t('layout.grant-the-agent-permission'), - checked: false, - }, - { - title: t('layout.allow-agent-to-access-your-address'), - description: t('layout.authorize-the-agent-to-view'), - checked: false, - }, - { - title: t('layout.password-storage'), - description: t('layout.determine-how-passwords-are-handled'), - checked: false, - }, - ]); - - useEffect(() => { - proxyFetchGet('/api/user/privacy') - .then((res) => { - setSettings((prev) => - prev.map((item, index) => ({ - ...item, - checked: res[API_FIELDS[index]] || false, - })) - ); - }) - .catch((err) => console.error('Failed to fetch settings:', err)); - }, [API_FIELDS]); - - const handleToggle = (index: number) => { - setSettings((prev) => { - const newSettings = [...prev]; - newSettings[index] = { - ...newSettings[index], - checked: !newSettings[index].checked, - }; - return newSettings; - }); - - const requestData = { - [API_FIELDS[0]]: settings[0].checked, - [API_FIELDS[1]]: settings[1].checked, - [API_FIELDS[2]]: settings[2].checked, - [API_FIELDS[3]]: settings[3].checked, - }; - - requestData[API_FIELDS[index]] = !settings[index].checked; - - proxyFetchPut('/api/user/privacy', requestData).catch((err) => - console.error('Failed to update settings:', err) - ); - }; - - const handleTurnOnAll = () => { - const newSettings = settings.map((item) => ({ - ...item, - checked: true, - })); - setSettings(newSettings); - - const requestData = { - [API_FIELDS[0]]: true, - [API_FIELDS[1]]: true, - [API_FIELDS[2]]: true, - [API_FIELDS[3]]: true, - }; - - proxyFetchPut('/api/user/privacy', requestData) - .then(() => { - onOpenChange(false); - }) - .catch((err) => console.error('Failed to update settings:', err)); - }; - - return ( - - {trigger && {trigger}} - - - -
-
- {t('layout.turn-on-all-privacy-settings')} -
- - - - - - -

- {t('layout.eigent-is-a-desktop-software')} -

-
-
-
-
-
-
- -
- {settings.map((item, index) => ( -
-
-
- {item.title} -
-
{item.description}
-
-
- handleToggle(index)} - /> -
-
- ))} -
- - - - - - - -
-
- ); -} From add791741dce0ad2dddc1c181fd0f8555dbd8a4e Mon Sep 17 00:00:00 2001 From: eureka928 Date: Sat, 7 Feb 2026 04:58:21 +0100 Subject: [PATCH 09/10] feat: add help_improve field to UserPrivacySettings --- server/app/model/user/privacy.py | 1 + 1 file changed, 1 insertion(+) diff --git a/server/app/model/user/privacy.py b/server/app/model/user/privacy.py index 164a2a72d..d3e400873 100644 --- a/server/app/model/user/privacy.py +++ b/server/app/model/user/privacy.py @@ -30,6 +30,7 @@ class UserPrivacySettings(BaseModel): access_local_software: bool | None = False access_your_address: bool | None = False password_storage: bool | None = False + help_improve: bool | None = False @classmethod def default_settings(cls) -> dict: From 64e81eb640c113dfcd67a1305c5f99c9b9f10807 Mon Sep 17 00:00:00 2001 From: eureka928 Date: Sat, 7 Feb 2026 04:58:27 +0100 Subject: [PATCH 10/10] test: update ChatBox tests for privacy consent migration Remove privacy-related test cases from ChatBox tests since privacy is now handled at login. Update mocks and assertions accordingly. --- .../components/ChatBox.integration.test.tsx | 22 +-- test/unit/components/ChatBox.test.tsx | 135 +++++------------- 2 files changed, 41 insertions(+), 116 deletions(-) diff --git a/test/integration/components/ChatBox.integration.test.tsx b/test/integration/components/ChatBox.integration.test.tsx index 103b7bb49..83b3d0e8c 100644 --- a/test/integration/components/ChatBox.integration.test.tsx +++ b/test/integration/components/ChatBox.integration.test.tsx @@ -455,33 +455,23 @@ describe('ChatBox Integration Tests - Different ChatStore Configurations', () => expect(sendButton).toBeDisabled(); }); - it('should display layout.terms-of-use and layout.privacy-policy links', async () => { + it('should not display privacy consent links (moved to login)', async () => { render( ); - const termsLink = screen.getByRole('link', { + // Privacy consent links are now in Login/SignUp, not in ChatBox + const termsLink = screen.queryByRole('link', { name: /layout.terms-of-use/i, }); - const privacyLink = screen.getByRole('link', { + const privacyLink = screen.queryByRole('link', { name: /layout.privacy-policy/i, }); - expect(termsLink).toBeInTheDocument(); - expect(termsLink).toHaveAttribute( - 'href', - 'https://www.eigent.ai/terms-of-use' - ); - expect(termsLink).toHaveAttribute('target', '_blank'); - - expect(privacyLink).toBeInTheDocument(); - expect(privacyLink).toHaveAttribute( - 'href', - 'https://www.eigent.ai/privacy-policy' - ); - expect(privacyLink).toHaveAttribute('target', '_blank'); + expect(termsLink).not.toBeInTheDocument(); + expect(privacyLink).not.toBeInTheDocument(); }); }); diff --git a/test/unit/components/ChatBox.test.tsx b/test/unit/components/ChatBox.test.tsx index 371aa7e88..b58127460 100644 --- a/test/unit/components/ChatBox.test.tsx +++ b/test/unit/components/ChatBox.test.tsx @@ -17,24 +17,33 @@ import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { BrowserRouter } from 'react-router-dom'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import * as fetchApi from '../../../src/api/http'; +import { + fetchDelete, + fetchPost, + fetchPut, + proxyFetchDelete, + proxyFetchGet, +} from '../../../src/api/http'; import ChatBox from '../../../src/components/ChatBox/index'; import { useAuthStore } from '../../../src/store/authStore'; -const { fetchPost, proxyFetchGet } = fetchApi; // Mock dependencies (use the same relative paths as the imports above) vi.mock('../../../src/store/authStore', () => ({ useAuthStore: vi.fn() })); vi.mock('../../../src/api/http', () => ({ fetchPost: vi.fn(), + fetchPut: vi.fn(), + fetchDelete: vi.fn(), proxyFetchGet: vi.fn(), - proxyFetchPut: vi.fn(), + proxyFetchDelete: vi.fn(), })); // Also mock the alias paths the component uses so the component picks up these mocks vi.mock('@/store/authStore', () => ({ useAuthStore: vi.fn() })); vi.mock('@/api/http', () => ({ fetchPost: vi.fn(), + fetchPut: vi.fn(), + fetchDelete: vi.fn(), proxyFetchGet: vi.fn(), - proxyFetchPut: vi.fn(), + proxyFetchDelete: vi.fn(), })); vi.mock('../../../src/lib', () => ({ generateUniqueId: vi.fn(() => 'test-unique-id'), @@ -124,21 +133,13 @@ vi.mock('../../../src/components/ChatBox/TypeCardSkeleton', () => ({ TypeCardSkeleton: vi.fn(() =>
Loading...
), })); -vi.mock('../../../src/components/Dialog/Privacy', () => ({ - PrivacyDialog: vi.fn(({ open, onOpenChange }: any) => - open ? ( -
- Privacy Dialog - -
- ) : null - ), -})); - describe('ChatBox Component', async () => { const mockUseAuthStore = vi.mocked(useAuthStore); - const mockFetchPost = vi.mocked(fetchPost); + const _mockFetchPost = vi.mocked(fetchPost); + const _mockFetchPut = vi.mocked(fetchPut); + const _mockFetchDelete = vi.mocked(fetchDelete); const mockProxyFetchGet = vi.mocked(proxyFetchGet); + const _mockProxyFetchDelete = vi.mocked(proxyFetchDelete); // Import the mocked hook const mockUseChatStoreAdapter = vi.mocked( @@ -248,12 +249,8 @@ describe('ChatBox Component', async () => { // Setup default API responses mockProxyFetchGet.mockImplementation((url: string) => { - if (url === '/api/user/privacy') { - return Promise.resolve({ - dataCollection: true, - analytics: true, - marketing: true, - }); + if (url === '/api/user/key') { + return Promise.resolve({ value: 'test-api-key' }); } if (url === '/api/configs') { return Promise.resolve([ @@ -264,7 +261,7 @@ describe('ChatBox Component', async () => { return Promise.resolve({}); }); - mockFetchPost.mockResolvedValue({ success: true }); + _mockFetchPost.mockResolvedValue({ success: true }); // Mock import.meta.env Object.defineProperty(import.meta, 'env', { @@ -299,11 +296,11 @@ describe('ChatBox Component', async () => { expect(screen.getByTestId('bottom-box')).toBeInTheDocument(); }); - it('should fetch privacy settings on mount', async () => { + it('should not fetch privacy settings on mount', async () => { renderChatBox(); await waitFor(() => { - expect(mockProxyFetchGet).toHaveBeenCalledWith('/api/user/privacy'); + expect(mockProxyFetchGet).not.toHaveBeenCalledWith('/api/user/privacy'); }); }); @@ -316,73 +313,15 @@ describe('ChatBox Component', async () => { }); }); - describe('Privacy Dialog', () => { - it('should automatically accept privacy settings when incomplete', async () => { - mockProxyFetchGet.mockImplementation((url: string) => { - if (url === '/api/user/privacy') { - return Promise.resolve({ - dataCollection: false, - analytics: true, - marketing: true, - }); - } - return Promise.resolve([]); - }); - - const mockProxyFetchPut = vi.fn().mockResolvedValue({}); - vi.mocked(fetchApi.proxyFetchPut).mockImplementation(mockProxyFetchPut); - - const user = userEvent.setup(); + describe('Privacy', () => { + it('should not fetch privacy settings on mount', async () => { renderChatBox(); - // Type a message and send it - const input = screen.getByPlaceholderText('Type your message...'); - await user.type(input, 'Test message'); - const sendButton = screen.getByTestId('send-button'); - await user.click(sendButton); - - // When privacy is incomplete, it should automatically accept all permissions + // Privacy is now handled at login, not in ChatBox await waitFor(() => { - expect(mockProxyFetchPut).toHaveBeenCalledWith('/api/user/privacy', { - take_screenshot: true, - access_local_software: true, - access_your_address: true, - password_storage: true, - }); + expect(mockProxyFetchGet).not.toHaveBeenCalledWith('/api/user/privacy'); }); }); - - it('should not auto-accept privacy when already complete', async () => { - mockProxyFetchGet.mockImplementation((url: string) => { - if (url === '/api/user/privacy') { - return Promise.resolve({ - dataCollection: true, - analytics: true, - marketing: true, - }); - } - return Promise.resolve([]); - }); - - const mockProxyFetchPut = vi.fn().mockResolvedValue({}); - vi.mocked(fetchApi.proxyFetchPut).mockImplementation(mockProxyFetchPut); - - const user = userEvent.setup(); - renderChatBox(); - - // Type a message and send it - const input = screen.getByPlaceholderText('Type your message...'); - await user.type(input, 'Test message'); - const sendButton = screen.getByTestId('send-button'); - await user.click(sendButton); - - // Should not call privacy update when already complete - await new Promise((resolve) => setTimeout(resolve, 100)); - expect(mockProxyFetchPut).not.toHaveBeenCalledWith( - '/api/user/privacy', - expect.anything() - ); - }); }); describe('Chat Interface', () => { @@ -469,7 +408,7 @@ describe('ChatBox Component', async () => { // The component should call fetchPost for continuing conversation await waitFor(() => { - expect(mockFetchPost).toHaveBeenCalled(); + expect(_mockFetchPost).toHaveBeenCalled(); }); }); @@ -680,7 +619,7 @@ describe('ChatBox Component', async () => { await waitFor(() => { // The API call now uses project ID instead of task ID - expect(mockFetchPost).toHaveBeenCalledWith( + expect(_mockFetchPost).toHaveBeenCalledWith( '/chat/test-project-id/human-reply', { agent: 'test-agent', @@ -731,7 +670,7 @@ describe('ChatBox Component', async () => { const storeCalled = (storeObj.setActiveAskList as any).mock.calls.length > 0 || (storeObj.addMessages as any).mock.calls.length > 0; - const apiCalled = (mockFetchPost as any).mock.calls.length > 0; + const apiCalled = (_mockFetchPost as any).mock.calls.length > 0; expect(storeCalled || apiCalled).toBe(true); }); }); @@ -763,11 +702,9 @@ describe('ChatBox Component', async () => { it('should show search key warning when missing API keys', async () => { mockProxyFetchGet.mockImplementation((url: string) => { - if (url === '/api/user/privacy') { + if (url === '/api/providers') { return Promise.resolve({ - dataCollection: true, - analytics: true, - marketing: true, + items: [{ id: 'test-provider', name: 'Test' }], }); } if (url === '/api/configs') { @@ -796,11 +733,9 @@ describe('ChatBox Component', async () => { describe('Example Prompts', () => { beforeEach(() => { mockProxyFetchGet.mockImplementation((url: string) => { - if (url === '/api/user/privacy') { + if (url === '/api/providers') { return Promise.resolve({ - dataCollection: true, - analytics: true, - marketing: true, + items: [{ id: 'test-provider', name: 'Test' }], }); } if (url === '/api/configs') { @@ -888,7 +823,7 @@ describe('ChatBox Component', async () => { it('should handle API errors gracefully', async () => { const user = userEvent.setup(); // Instead of asserting on console.error (environment dependent), ensure the API was called and the UI didn't crash - mockFetchPost.mockRejectedValue(new Error('API Error')); + _mockFetchPost.mockRejectedValue(new Error('API Error')); // Force a code path that calls fetchPost by setting activeAsk on the task mockUseChatStoreAdapter.mockReturnValue({ @@ -914,7 +849,7 @@ describe('ChatBox Component', async () => { await user.click(sendButton); await waitFor(() => { - expect((mockFetchPost as any).mock.calls.length).toBeGreaterThan(0); + expect((_mockFetchPost as any).mock.calls.length).toBeGreaterThan(0); }); });