Spaces:
Sleeping
Sleeping
Commit
·
48b8905
1
Parent(s):
032c9f9
Implement One-Time Privacy Notice Modal for HF Spaces - Replace persistent banner with elegant popup that shows once per user
Browse files
frontend/src/components/layout/MainWorkspace.tsx
CHANGED
|
@@ -16,7 +16,7 @@ import { ConnectionsView } from "../features/connections/ConnectionsView";
|
|
| 16 |
import { UploadView } from "../features/upload/UploadView";
|
| 17 |
import { Button } from "@/components/ui/button";
|
| 18 |
import { ArrowLeft } from "lucide-react";
|
| 19 |
-
import {
|
| 20 |
|
| 21 |
interface MainWorkspaceProps {
|
| 22 |
isSidebarCollapsed: boolean;
|
|
@@ -330,6 +330,9 @@ export function MainWorkspace({
|
|
| 330 |
<div className="fixed inset-0 bg-background z-50 flex flex-col">
|
| 331 |
{/* Global Floating Action Widget for temporal view */}
|
| 332 |
<FloatingActionWidget />
|
|
|
|
|
|
|
|
|
|
| 333 |
|
| 334 |
{/* Breadcrumb Navigation */}
|
| 335 |
<div className="border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
|
@@ -392,17 +395,8 @@ export function MainWorkspace({
|
|
| 392 |
</div>
|
| 393 |
)}
|
| 394 |
|
| 395 |
-
{/*
|
| 396 |
-
|
| 397 |
-
<div className="bg-amber-50 border-b border-amber-200 px-6 py-3 text-sm text-amber-800 flex items-center gap-2">
|
| 398 |
-
<span className="text-amber-600">🔒</span>
|
| 399 |
-
<span>
|
| 400 |
-
<strong>Privacy Notice:</strong> You're using AgentGraph on Hugging Face Spaces.
|
| 401 |
-
Your data is stored in a temporary session and will be cleared when the container restarts.
|
| 402 |
-
Data is not shared between users or sessions.
|
| 403 |
-
</span>
|
| 404 |
-
</div>
|
| 405 |
-
)}
|
| 406 |
|
| 407 |
{/* Main Content */}
|
| 408 |
<div className="flex-1 flex flex-col min-h-0">{renderView()}</div>
|
|
|
|
| 16 |
import { UploadView } from "../features/upload/UploadView";
|
| 17 |
import { Button } from "@/components/ui/button";
|
| 18 |
import { ArrowLeft } from "lucide-react";
|
| 19 |
+
import { PrivacyNoticeModal } from "@/components/shared/modals/PrivacyNoticeModal";
|
| 20 |
|
| 21 |
interface MainWorkspaceProps {
|
| 22 |
isSidebarCollapsed: boolean;
|
|
|
|
| 330 |
<div className="fixed inset-0 bg-background z-50 flex flex-col">
|
| 331 |
{/* Global Floating Action Widget for temporal view */}
|
| 332 |
<FloatingActionWidget />
|
| 333 |
+
|
| 334 |
+
{/* Privacy Notice Modal */}
|
| 335 |
+
<PrivacyNoticeModal />
|
| 336 |
|
| 337 |
{/* Breadcrumb Navigation */}
|
| 338 |
<div className="border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
|
|
|
| 395 |
</div>
|
| 396 |
)}
|
| 397 |
|
| 398 |
+
{/* Privacy Notice Modal */}
|
| 399 |
+
<PrivacyNoticeModal />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 400 |
|
| 401 |
{/* Main Content */}
|
| 402 |
<div className="flex-1 flex flex-col min-h-0">{renderView()}</div>
|
frontend/src/components/shared/modals/PrivacyNoticeModal.tsx
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState, useEffect } from "react";
|
| 2 |
+
import {
|
| 3 |
+
Dialog,
|
| 4 |
+
DialogContent,
|
| 5 |
+
DialogDescription,
|
| 6 |
+
DialogFooter,
|
| 7 |
+
DialogHeader,
|
| 8 |
+
DialogTitle,
|
| 9 |
+
} from "@/components/ui/dialog";
|
| 10 |
+
import { Button } from "@/components/ui/button";
|
| 11 |
+
import { Shield, Info, Database, RefreshCw } from "lucide-react";
|
| 12 |
+
import { IS_HF_SPACES } from "@/lib/config";
|
| 13 |
+
|
| 14 |
+
const PRIVACY_NOTICE_KEY = "agentgraph_privacy_notice_seen";
|
| 15 |
+
|
| 16 |
+
export function PrivacyNoticeModal() {
|
| 17 |
+
const [isOpen, setIsOpen] = useState(false);
|
| 18 |
+
|
| 19 |
+
useEffect(() => {
|
| 20 |
+
// Only show on HF Spaces and if not seen before
|
| 21 |
+
if (IS_HF_SPACES) {
|
| 22 |
+
const hasSeenNotice = localStorage.getItem(PRIVACY_NOTICE_KEY);
|
| 23 |
+
if (!hasSeenNotice) {
|
| 24 |
+
// Show modal after a short delay to ensure the app is loaded
|
| 25 |
+
const timer = setTimeout(() => {
|
| 26 |
+
setIsOpen(true);
|
| 27 |
+
}, 1000);
|
| 28 |
+
return () => clearTimeout(timer);
|
| 29 |
+
}
|
| 30 |
+
}
|
| 31 |
+
}, []);
|
| 32 |
+
|
| 33 |
+
const handleAccept = () => {
|
| 34 |
+
localStorage.setItem(PRIVACY_NOTICE_KEY, "true");
|
| 35 |
+
setIsOpen(false);
|
| 36 |
+
};
|
| 37 |
+
|
| 38 |
+
const handleDismiss = () => {
|
| 39 |
+
// Don't set localStorage, so it shows again next time
|
| 40 |
+
setIsOpen(false);
|
| 41 |
+
};
|
| 42 |
+
|
| 43 |
+
// Don't render anything if not on HF Spaces
|
| 44 |
+
if (!IS_HF_SPACES) {
|
| 45 |
+
return null;
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
return (
|
| 49 |
+
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
| 50 |
+
<DialogContent className="sm:max-w-[500px]">
|
| 51 |
+
<DialogHeader>
|
| 52 |
+
<DialogTitle className="flex items-center gap-2 text-lg">
|
| 53 |
+
<Shield className="h-5 w-5 text-blue-600" />
|
| 54 |
+
Privacy Notice - Hugging Face Spaces
|
| 55 |
+
</DialogTitle>
|
| 56 |
+
<DialogDescription className="text-left space-y-3 pt-2">
|
| 57 |
+
<div className="flex items-start gap-3">
|
| 58 |
+
<Database className="h-4 w-4 text-amber-600 mt-0.5 flex-shrink-0" />
|
| 59 |
+
<div className="text-sm">
|
| 60 |
+
<strong>Temporary Data Storage:</strong> Your data is stored in a private, temporary session that is automatically cleared when the container restarts.
|
| 61 |
+
</div>
|
| 62 |
+
</div>
|
| 63 |
+
|
| 64 |
+
<div className="flex items-start gap-3">
|
| 65 |
+
<Shield className="h-4 w-4 text-green-600 mt-0.5 flex-shrink-0" />
|
| 66 |
+
<div className="text-sm">
|
| 67 |
+
<strong>Complete Privacy:</strong> Your traces and analysis results are not shared between users or sessions. Each user has their own isolated environment.
|
| 68 |
+
</div>
|
| 69 |
+
</div>
|
| 70 |
+
|
| 71 |
+
<div className="flex items-start gap-3">
|
| 72 |
+
<RefreshCw className="h-4 w-4 text-blue-600 mt-0.5 flex-shrink-0" />
|
| 73 |
+
<div className="text-sm">
|
| 74 |
+
<strong>Data Lifecycle:</strong> All uploaded traces, knowledge graphs, and analysis results will be permanently deleted when the Hugging Face Spaces container restarts.
|
| 75 |
+
</div>
|
| 76 |
+
</div>
|
| 77 |
+
|
| 78 |
+
<div className="flex items-start gap-3">
|
| 79 |
+
<Info className="h-4 w-4 text-gray-600 mt-0.5 flex-shrink-0" />
|
| 80 |
+
<div className="text-sm">
|
| 81 |
+
<strong>Recommendation:</strong> For persistent data storage and analysis, consider running AgentGraph locally or on your own infrastructure.
|
| 82 |
+
</div>
|
| 83 |
+
</div>
|
| 84 |
+
</DialogDescription>
|
| 85 |
+
</DialogHeader>
|
| 86 |
+
|
| 87 |
+
<DialogFooter className="flex gap-2 sm:gap-2">
|
| 88 |
+
<Button
|
| 89 |
+
variant="outline"
|
| 90 |
+
onClick={handleDismiss}
|
| 91 |
+
className="flex-1"
|
| 92 |
+
>
|
| 93 |
+
Remind Me Later
|
| 94 |
+
</Button>
|
| 95 |
+
<Button
|
| 96 |
+
onClick={handleAccept}
|
| 97 |
+
className="flex-1 bg-blue-600 hover:bg-blue-700"
|
| 98 |
+
>
|
| 99 |
+
I Understand
|
| 100 |
+
</Button>
|
| 101 |
+
</DialogFooter>
|
| 102 |
+
</DialogContent>
|
| 103 |
+
</Dialog>
|
| 104 |
+
);
|
| 105 |
+
}
|
frontend/src/lib/config.ts
CHANGED
|
@@ -28,10 +28,12 @@ export const API_BASE = getApiBase();
|
|
| 28 |
// Check if running on Hugging Face Spaces
|
| 29 |
export const isHuggingFaceSpaces = () => {
|
| 30 |
if (typeof window === "undefined") return false;
|
| 31 |
-
|
| 32 |
-
return
|
| 33 |
-
|
| 34 |
-
|
|
|
|
|
|
|
| 35 |
};
|
| 36 |
|
| 37 |
export const IS_HF_SPACES = isHuggingFaceSpaces();
|
|
|
|
| 28 |
// Check if running on Hugging Face Spaces
|
| 29 |
export const isHuggingFaceSpaces = () => {
|
| 30 |
if (typeof window === "undefined") return false;
|
| 31 |
+
|
| 32 |
+
return (
|
| 33 |
+
window.location.hostname.includes("huggingface.co") ||
|
| 34 |
+
window.location.hostname.includes("hf.space") ||
|
| 35 |
+
window.location.hostname.endsWith(".hf.space")
|
| 36 |
+
);
|
| 37 |
};
|
| 38 |
|
| 39 |
export const IS_HF_SPACES = isHuggingFaceSpaces();
|