Skip to content
Open
1 change: 1 addition & 0 deletions server/app/model/user/privacy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
11 changes: 4 additions & 7 deletions src/components/ChatBox/BottomBox/InputBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,6 @@ export interface InputboxProps {
textareaRef?: React.RefObject<HTMLTextAreaElement>;
/** Allow drag and drop */
allowDragDrop?: boolean;
/** Privacy mode enabled */
privacy?: boolean;
/** Use cloud model in dev */
useCloudModelInDev?: boolean;
}
Expand Down Expand Up @@ -123,7 +121,6 @@ export const Inputbox = ({
className,
textareaRef: externalTextareaRef,
allowDragDrop = false,
privacy = true,
useCloudModelInDev = false,
}: InputboxProps) => {
const { t } = useTranslation();
Expand Down Expand Up @@ -211,7 +208,7 @@ export const Inputbox = ({
};

const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
if (!allowDragDrop || !privacy || useCloudModelInDev) return;
if (!allowDragDrop || useCloudModelInDev) return;
if (!isFileDrag(e)) return;
e.preventDefault();
e.stopPropagation();
Expand All @@ -220,7 +217,7 @@ export const Inputbox = ({
};

const handleDragEnter = (e: React.DragEvent<HTMLDivElement>) => {
if (!allowDragDrop || !privacy || useCloudModelInDev) return;
if (!allowDragDrop || useCloudModelInDev) return;
if (!isFileDrag(e)) return;
e.preventDefault();
e.stopPropagation();
Expand All @@ -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;
Expand Down Expand Up @@ -452,7 +449,7 @@ export const Inputbox = ({
size="icon"
className="rounded-full"
onClick={onAddFile}
disabled={disabled || !privacy || useCloudModelInDev}
disabled={disabled || useCloudModelInDev}
>
<Plus size={16} className="text-icon-primary" />
</Button>
Expand Down
168 changes: 13 additions & 155 deletions src/components/ChatBox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -40,13 +39,10 @@ export default function ChatBox(): JSX.Element {
const { t } = useTranslation();
const textareaRef = useRef<HTMLTextAreaElement>(null);
const scrollContainerRef = useRef<HTMLDivElement>(null);
const [privacy, setPrivacy] = useState<any>(false);
const [isPrivacyLoaded, setIsPrivacyLoaded] = useState<boolean>(false);
const [_hasSearchKey, setHasSearchKey] = useState<any>(false);
const [hasModel, setHasModel] = useState<any>(false);
const [isConfigLoaded, setIsConfigLoaded] = useState<boolean>(false);
const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null);
// const [privacyDialogOpen, setPrivacyDialogOpen] = useState(false);
const { modelType } = useAuthStore();
const [useCloudModelInDev, setUseCloudModelInDev] = useState(false);
const location = useLocation();
Expand Down Expand Up @@ -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 : [];
Expand All @@ -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');

Expand Down Expand Up @@ -279,17 +242,15 @@ 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;

return false;
}, [
chatStore?.activeTaskId,
chatStore?.tasks,
privacy,
hasModel,
useCloudModelInDev,
isTaskBusy,
Expand All @@ -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];
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -1064,7 +1000,6 @@ export default function ChatBox(): JSX.Element {
disabled: isInputDisabled,
textareaRef: textareaRef,
allowDragDrop: true,
privacy: privacy,
useCloudModelInDev: useCloudModelInDev,
}}
/>
Expand Down Expand Up @@ -1108,7 +1043,6 @@ export default function ChatBox(): JSX.Element {
disabled: isInputDisabled,
textareaRef: textareaRef,
allowDragDrop: false,
privacy: privacy,
useCloudModelInDev: useCloudModelInDev,
}}
/>
Expand All @@ -1129,83 +1063,7 @@ export default function ChatBox(): JSX.Element {
</div>
</div>
) : null}
{hasModel && !privacy ? (
<div className="flex items-center gap-2">
<div
onClick={(e) => {
// 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"
>
<div className="shrink-0">
{privacy ? (
<SquareCheckBig
size={20}
className="shrink-0 text-icon-success"
/>
) : (
<div className="relative flex shrink-0 items-center justify-center">
<Square
size={20}
className="text-icon-information transition-opacity group-hover:opacity-0"
/>
<SquareCheckBig
size={20}
className="absolute inset-0 text-icon-information opacity-0 transition-opacity group-hover:opacity-50"
/>
</div>
)}
</div>
<span className="flex-1 cursor-pointer text-label-xs font-medium leading-normal text-text-information">
{t('layout.by-messaging-eigent')}{' '}
<a
href="https://www.eigent.ai/terms-of-use"
target="_blank"
className="text-text-information underline"
onClick={(e) => e.stopPropagation()}
rel="noreferrer"
>
{t('layout.terms-of-use')}
</a>{' '}
{t('layout.and')}{' '}
<a
href="https://www.eigent.ai/privacy-policy"
target="_blank"
className="text-text-information underline"
onClick={(e) => e.stopPropagation()}
rel="noreferrer"
>
{t('layout.privacy-policy')}
</a>
.
</span>
</div>
</div>
) : null}
{privacy && hasModel && (
{hasModel && (
<div className="mr-2 flex flex-col items-center gap-2">
{[
{
Expand Down
Loading