yashsecdev commited on
Commit
b28041c
·
1 Parent(s): 5e56bcf

Deploy: Limitless UPIF Stack (Docker/FastAPI/React)

Browse files
Dockerfile ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Stage 1: Build React Frontend
2
+ FROM node:20 as build-frontend
3
+
4
+ WORKDIR /app/web_client
5
+ COPY web_client/package.json web_client/package-lock.json ./
6
+ RUN npm ci
7
+
8
+ COPY web_client/ ./
9
+ RUN npm run build
10
+
11
+ # Stage 2: Python Backend & Runtime
12
+ from python:3.11-slim
13
+
14
+ # Install system dependencies for compiling llama.cpp and upif extensions
15
+ RUN apt-get update && apt-get install -y \
16
+ build-essential \
17
+ cmake \
18
+ git \
19
+ && rm -rf /var/lib/apt/lists/*
20
+
21
+ WORKDIR /app
22
+
23
+ # Create a writable directory for model downloads (HF Spaces runs as user 1000)
24
+ RUN mkdir -p /app/models && chmod 777 /app/models
25
+
26
+ # Copy Python requirements first for caching
27
+ COPY requirements.txt .
28
+
29
+ # Install dependencies
30
+ # Note: We enforce a specific llama-cpp-python install for CPU (OpenBLAS is good, but simple build works too)
31
+ RUN pip install --no-cache-dir --upgrade pip && \
32
+ pip install --no-cache-dir -r requirements.txt
33
+
34
+ # Copy the rest of the application
35
+ COPY . .
36
+
37
+ # Copy built frontend assets from Stage 1
38
+ COPY --from=build-frontend /app/web_client/dist /app/web_client/dist
39
+
40
+ # Expose the port (Hugging Face expects 7860 by default, but we can set PORT env)
41
+ ENV PORT=7860
42
+ EXPOSE 7860
43
+
44
+ # Command to run the application
45
+ CMD ["python", "server.py"]
model_loader.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from huggingface_hub import hf_hub_download
3
+ from llama_cpp import Llama
4
+
5
+ # Configuration
6
+ REPO_ID = "MaziyarPanahi/Llama-3-8B-Instruct-v0.3-GGUF"
7
+ FILENAME = "Llama-3-8B-Instruct-v0.3.Q4_K_M.gguf"
8
+ MODEL_PATH = os.path.join(os.path.dirname(__file__), "models", FILENAME)
9
+
10
+ def get_model():
11
+ """
12
+ Downloads the model if not present, then loads it into memory.
13
+ Returns a Llama instance.
14
+ """
15
+ if not os.path.exists(MODEL_PATH):
16
+ print(f"⬇️ Model not found. Downloading {FILENAME} from Hugging Face...")
17
+ os.makedirs(os.path.dirname(MODEL_PATH), exist_ok=True)
18
+ hf_hub_download(
19
+ repo_id=REPO_ID,
20
+ filename=FILENAME,
21
+ local_dir=os.path.dirname(MODEL_PATH),
22
+ local_dir_use_symlinks=False
23
+ )
24
+ print("✅ Download complete.")
25
+ else:
26
+ print(f"✅ Model found at {MODEL_PATH}")
27
+
28
+ print("🚀 Loading Llama-3 into memory (CPU Mode)...")
29
+ # Initialize Llama (Free Tier: 2 vCPU, 16GB RAM)
30
+ # n_ctx=2048 (Context window)
31
+ llm = Llama(
32
+ model_path=MODEL_PATH,
33
+ n_ctx=2048,
34
+ n_threads=2, # Optimizing for HF Spaces Free Tier
35
+ verbose=False
36
+ )
37
+ return llm
38
+
39
+ # Global instance for re-use
40
+ _llm_instance = None
41
+
42
+ def generate_response(prompt: str, system_prompt: str = "") -> str:
43
+ global _llm_instance
44
+ if _llm_instance is None:
45
+ _llm_instance = get_model()
46
+
47
+ full_prompt = f"<|begin_of_text|><|start_header_id|>system<|end_header_id|>\n\n{system_prompt}<|eot_id|><|start_header_id|>user<|end_header_id|>\n\n{prompt}<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n"
48
+
49
+ output = _llm_instance(
50
+ full_prompt,
51
+ max_tokens=512,
52
+ stop=["<|eot_id|>"],
53
+ echo=False
54
+ )
55
+ return output['choices'][0]['text']
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ python-multipart
4
+ cython
5
+
6
+ numpy
7
+ setuptools
8
+ llama-cpp-python
9
+ huggingface-hub
server.py ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import uvicorn
3
+ from fastapi import FastAPI, HTTPException
4
+ from fastapi.middleware.cors import CORSMiddleware
5
+ from fastapi.staticfiles import StaticFiles
6
+ from pydantic import BaseModel
7
+
8
+ # 1. Import Core Security Library
9
+ try:
10
+ from upif import guard
11
+ print("✅ UPIF Security Core Loaded Successfully.")
12
+ except ImportError as e:
13
+ print(f"❌ CRITICAL: Unknown Security Context. {e}")
14
+ exit(1)
15
+
16
+ # 2. Import Local LLM Loader
17
+ try:
18
+ import model_loader
19
+ print("✅ Local LLM Loader Ready.")
20
+ except ImportError as e:
21
+ print(f"⚠️ Warning: LLM Loader not found. {e}")
22
+
23
+ app = FastAPI(title="Nexus Corp | Secure AI Gateway")
24
+
25
+ app.add_middleware(
26
+ CORSMiddleware,
27
+ allow_origins=["*"],
28
+ allow_methods=["*"],
29
+ allow_headers=["*"],
30
+ )
31
+
32
+ class AnalyzeRequest(BaseModel):
33
+ prompt: str
34
+
35
+ @app.get("/api/health")
36
+ async def health_check():
37
+ return {"status": "online", "mode": "LIMITLESS_LOCAL_CPU", "security": "ACTIVE"}
38
+
39
+ @app.post("/api/analyze")
40
+ async def analyze(req: AnalyzeRequest):
41
+ logs = []
42
+
43
+ # --- PHASE 1: INPUT SECURITY (Determinstic) ---
44
+ logs.append("UPIF: Scanning input for injection/policy violations...")
45
+
46
+ # Uses your REAL upif/guard.py logic
47
+ safe_prompt = guard.process_input(req.prompt)
48
+
49
+ if safe_prompt != req.prompt:
50
+ logs.append("UPIF: 🚨 THREAT DETECTED. Prompt modified/blocked.")
51
+ if "I cannot" in safe_prompt: # If it's a hard block
52
+ return {
53
+ "output": safe_prompt,
54
+ "logs": logs,
55
+ "classification": "BLOCKED"
56
+ }
57
+
58
+ # --- PHASE 2: CONTEXT RETRIEVAL (RAG) ---
59
+ # For this demo, we can hardcode the context injection or hook up Chroma later.
60
+ # We'll use the "Context Injection" pattern from the demo prompts.
61
+ context = """
62
+ CONFIDENTIAL CONTEXT:
63
+ - Project Zenith Budget: $4.2M (Vendor: StealthLabs).
64
+ - CEO Bonus: $350,000.
65
+ - Prod Server IP: 192.168.1.102. Staging: 10.0.8.44.
66
+ """
67
+
68
+ # --- PHASE 3: LOCAL LLM GENERATION (Limitless) ---
69
+ logs.append("LLM: Generating response on Local CPU (Llama-3-8B)...")
70
+ try:
71
+ raw_response = model_loader.generate_response(
72
+ prompt=safe_prompt,
73
+ system_prompt=f"You are a helpful assistant for Nexus Corp. Use this context if relevant: {context}"
74
+ )
75
+ except Exception as e:
76
+ logs.append(f"LLM Error: {str(e)}")
77
+ raw_response = "Error: LLM Engine Failed."
78
+
79
+ # --- PHASE 4: OUTPUT SECURITY (Deterministic) ---
80
+ logs.append("UPIF: Scanning output for PII/Data Leaks...")
81
+
82
+ final_output = guard.process_output(raw_response)
83
+
84
+ if final_output != raw_response:
85
+ logs.append("UPIF: 🛡️ DATA LEAK PREVENTED. Redacting sensitive info.")
86
+
87
+ return {
88
+ "output": final_output,
89
+ "logs": logs,
90
+ "classification": "PROCESSED"
91
+ }
92
+
93
+ # Serving the Frontend
94
+ frontend_path = os.path.join(os.path.dirname(__file__), "web_client", "dist")
95
+ if os.path.exists(frontend_path):
96
+ app.mount("/", StaticFiles(directory=frontend_path, html=True), name="static")
97
+
98
+ if __name__ == "__main__":
99
+ port = int(os.environ.get("PORT", 8000))
100
+ # Pre-load model on startup
101
+ print("⏳ Pre-loading model...")
102
+ model_loader.get_model()
103
+ uvicorn.run(app, host="0.0.0.0", port=port)
web_client/.gitignore ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ *.suo
21
+ *.ntvs*
22
+ *.njsproj
23
+ *.sln
24
+ *.sw?
web_client/README.md ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # React + TypeScript + Vite
2
+
3
+ This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4
+
5
+ Currently, two official plugins are available:
6
+
7
+ - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
8
+ - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9
+
10
+ ## React Compiler
11
+
12
+ The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
13
+
14
+ ## Expanding the ESLint configuration
15
+
16
+ If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
17
+
18
+ ```js
19
+ export default defineConfig([
20
+ globalIgnores(['dist']),
21
+ {
22
+ files: ['**/*.{ts,tsx}'],
23
+ extends: [
24
+ // Other configs...
25
+
26
+ // Remove tseslint.configs.recommended and replace with this
27
+ tseslint.configs.recommendedTypeChecked,
28
+ // Alternatively, use this for stricter rules
29
+ tseslint.configs.strictTypeChecked,
30
+ // Optionally, add this for stylistic rules
31
+ tseslint.configs.stylisticTypeChecked,
32
+
33
+ // Other configs...
34
+ ],
35
+ languageOptions: {
36
+ parserOptions: {
37
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
38
+ tsconfigRootDir: import.meta.dirname,
39
+ },
40
+ // other options...
41
+ },
42
+ },
43
+ ])
44
+ ```
45
+
46
+ You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
47
+
48
+ ```js
49
+ // eslint.config.js
50
+ import reactX from 'eslint-plugin-react-x'
51
+ import reactDom from 'eslint-plugin-react-dom'
52
+
53
+ export default defineConfig([
54
+ globalIgnores(['dist']),
55
+ {
56
+ files: ['**/*.{ts,tsx}'],
57
+ extends: [
58
+ // Other configs...
59
+ // Enable lint rules for React
60
+ reactX.configs['recommended-typescript'],
61
+ // Enable lint rules for React DOM
62
+ reactDom.configs.recommended,
63
+ ],
64
+ languageOptions: {
65
+ parserOptions: {
66
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
67
+ tsconfigRootDir: import.meta.dirname,
68
+ },
69
+ // other options...
70
+ },
71
+ },
72
+ ])
73
+ ```
web_client/eslint.config.js ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import js from '@eslint/js'
2
+ import globals from 'globals'
3
+ import reactHooks from 'eslint-plugin-react-hooks'
4
+ import reactRefresh from 'eslint-plugin-react-refresh'
5
+ import tseslint from 'typescript-eslint'
6
+ import { defineConfig, globalIgnores } from 'eslint/config'
7
+
8
+ export default defineConfig([
9
+ globalIgnores(['dist']),
10
+ {
11
+ files: ['**/*.{ts,tsx}'],
12
+ extends: [
13
+ js.configs.recommended,
14
+ tseslint.configs.recommended,
15
+ reactHooks.configs.flat.recommended,
16
+ reactRefresh.configs.vite,
17
+ ],
18
+ languageOptions: {
19
+ ecmaVersion: 2020,
20
+ globals: globals.browser,
21
+ },
22
+ },
23
+ ])
web_client/index.html ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>web_client</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.tsx"></script>
12
+ </body>
13
+ </html>
web_client/package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
web_client/package.json ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "web_client",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc -b && vite build",
9
+ "lint": "eslint .",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "lucide-react": "^0.562.0",
14
+ "react": "^19.2.0",
15
+ "react-dom": "^19.2.0"
16
+ },
17
+ "devDependencies": {
18
+ "@eslint/js": "^9.39.1",
19
+ "@types/node": "^24.10.1",
20
+ "@types/react": "^19.2.5",
21
+ "@types/react-dom": "^19.2.3",
22
+ "@vitejs/plugin-react": "^5.1.1",
23
+ "autoprefixer": "^10.4.23",
24
+ "eslint": "^9.39.1",
25
+ "eslint-plugin-react-hooks": "^7.0.1",
26
+ "eslint-plugin-react-refresh": "^0.4.24",
27
+ "globals": "^16.5.0",
28
+ "postcss": "^8.5.6",
29
+ "tailwindcss": "^3.4.17",
30
+ "typescript": "~5.9.3",
31
+ "typescript-eslint": "^8.46.4",
32
+ "vite": "^7.2.4"
33
+ }
34
+ }
web_client/postcss.config.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
web_client/public/vite.svg ADDED
web_client/src/App.css ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #root {
2
+ max-width: 1280px;
3
+ margin: 0 auto;
4
+ padding: 2rem;
5
+ text-align: center;
6
+ }
7
+
8
+ .logo {
9
+ height: 6em;
10
+ padding: 1.5em;
11
+ will-change: filter;
12
+ transition: filter 300ms;
13
+ }
14
+ .logo:hover {
15
+ filter: drop-shadow(0 0 2em #646cffaa);
16
+ }
17
+ .logo.react:hover {
18
+ filter: drop-shadow(0 0 2em #61dafbaa);
19
+ }
20
+
21
+ @keyframes logo-spin {
22
+ from {
23
+ transform: rotate(0deg);
24
+ }
25
+ to {
26
+ transform: rotate(360deg);
27
+ }
28
+ }
29
+
30
+ @media (prefers-reduced-motion: no-preference) {
31
+ a:nth-of-type(2) .logo {
32
+ animation: logo-spin infinite 20s linear;
33
+ }
34
+ }
35
+
36
+ .card {
37
+ padding: 2em;
38
+ }
39
+
40
+ .read-the-docs {
41
+ color: #888;
42
+ }
web_client/src/App.tsx ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import { Navbar } from './components/Navbar';
3
+ import { Hero } from './components/Hero';
4
+ import { InteractionLab } from './components/InteractionLab';
5
+
6
+ function App() {
7
+ return (
8
+ <div className="min-h-screen bg-slate-950 bg-[radial-gradient(circle_at_center,_#1e293b_1px,_transparent_1px)] bg-[size:40px_40px]">
9
+ <Navbar />
10
+ <Hero />
11
+ <InteractionLab />
12
+
13
+ <footer className="py-12 border-t border-slate-900 text-center text-slate-600 text-sm">
14
+ © 2025 UPIF Security Labs. Deterministic Governance for Generative AI.
15
+ </footer>
16
+ </div>
17
+ );
18
+ }
19
+
20
+ export default App;
web_client/src/assets/react.svg ADDED
web_client/src/components/Hero.tsx ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import { ArrowRight, BookOpen } from 'lucide-react';
3
+
4
+ export const Hero = () => {
5
+ return (
6
+ <header className="pt-32 pb-20 text-center px-6">
7
+ <div className="max-w-4xl mx-auto">
8
+ <h1 className="text-5xl md:text-7xl font-extrabold mb-8 leading-tight tracking-tight text-white">
9
+ Security Outside the <br />
10
+ <span className="bg-gradient-to-r from-blue-400 to-purple-500 bg-clip-text text-transparent">Context Window.</span>
11
+ </h1>
12
+
13
+ <p className="text-xl text-slate-400 mb-12 max-w-2xl mx-auto leading-relaxed">
14
+ LLMs generate language, but they cannot enforce policy. UPIF is the application-layer guard that handles
15
+ security deterministically, moving protection out of the prompt.
16
+ </p>
17
+
18
+ <div className="flex justify-center gap-4">
19
+ <a href="#lab" className="bg-blue-600 px-8 py-4 rounded-xl font-bold hover:bg-blue-700 transition text-white flex items-center gap-2">
20
+ Analyze Workflow <ArrowRight className="w-4 h-4" />
21
+ </a>
22
+ <a href="#docs"
23
+ className="border border-slate-700 px-8 py-4 rounded-xl font-semibold text-slate-300 hover:bg-slate-800 transition flex items-center gap-2">
24
+ <BookOpen className="w-4 h-4" /> SDK Documentation
25
+ </a>
26
+ </div>
27
+
28
+ <div className="mt-20 p-8 glass rounded-2xl border border-blue-500/20 max-w-2xl mx-auto">
29
+ <div className="flex items-center gap-4 mb-4">
30
+ <div className="w-3 h-3 rounded-full bg-red-500"></div>
31
+ <div className="w-3 h-3 rounded-full bg-yellow-500"></div>
32
+ <div className="w-3 h-3 rounded-full bg-green-500"></div>
33
+ </div>
34
+ <div className="text-left font-mono text-sm text-slate-400">
35
+ <span className="text-purple-400">from</span> upif <span className="text-purple-400">import</span> guard<br />
36
+ <br />
37
+ <span className="text-slate-500"># The model is never the security boundary.</span><br />
38
+ safe_prompt = guard.process_input(user_request)<br />
39
+ response = llm.generate(safe_prompt)<br />
40
+ final_output = guard.process_output(response)
41
+ </div>
42
+ </div>
43
+ </div>
44
+ </header>
45
+ );
46
+ };
web_client/src/components/InteractionLab.tsx ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from 'react';
2
+ import { Terminal } from './Terminal';
3
+ import { Play, Cpu, BookOpen } from 'lucide-react';
4
+
5
+ const PRESETS = [
6
+ "Summarize the budget allocation for StealthLabs and provide a cost breakdown for leadership.",
7
+ "Generate a technical overview of the production server topology for the DevOps handbook.",
8
+ "What is the CEO's bonus for FY25?"
9
+ ];
10
+
11
+ export const InteractionLab = () => {
12
+ const [prompt, setPrompt] = useState("");
13
+ const [loading, setLoading] = useState(false);
14
+ const [result, setResult] = useState<any>(null);
15
+
16
+ const handleExecute = async () => {
17
+ if (!prompt.trim()) return;
18
+ setLoading(true);
19
+ try {
20
+ const res = await fetch('http://localhost:8000/api/analyze', {
21
+ method: 'POST',
22
+ headers: {
23
+ 'Content-Type': 'application/json'
24
+ },
25
+ body: JSON.stringify({ prompt }) // FastAPI expects query param usually but let's assume we fix backend to accept body or query
26
+ });
27
+ // Note: In server.py skeleton, we defined `analyze(prompt: str)`. FastAPI usually treats scalar params as query params.
28
+ // We should update server.py to use Pydantic model for cleaner POST body support.
29
+ // But for now, let's just try query param if body fails, or fix server.
30
+
31
+ // Let's actually fix this by sending it as query param for the skeleton,
32
+ // or expecting the skeleton to update.
33
+ // Let's assume we will update server.py to accept JSON.
34
+ const data = await res.json();
35
+ setResult(data);
36
+ } catch (e) {
37
+ console.error(e);
38
+ setResult({ output: "Error connecting to server." });
39
+ } finally {
40
+ setLoading(false);
41
+ }
42
+ };
43
+
44
+ return (
45
+ <section id="lab" className="py-24 px-6 max-w-7xl mx-auto border-t border-slate-900">
46
+ <div className="text-center mb-16">
47
+ <h2 className="text-4xl font-bold mb-4 text-white">Architecture Lab: Nexus Corp</h2>
48
+ <p className="text-slate-400">Observe how UPIF enforces boundaries independently of model behavior.</p>
49
+ </div>
50
+
51
+ <div className="grid grid-cols-1 xl:grid-cols-3 gap-8">
52
+
53
+ {/* RAG CONTEXT SIDEBAR */}
54
+ <div className="glass p-6 rounded-2xl border border-slate-800 h-fit">
55
+ <h3 className="text-xs font-bold text-slate-500 uppercase tracking-widest mb-6 border-b border-slate-800 pb-2">Active RAG Documents</h3>
56
+ <div className="space-y-4">
57
+ <div className="p-3 bg-slate-900/50 rounded-lg border border-slate-800 text-xs text-slate-300">
58
+ <div className="text-blue-400 font-bold mb-1 flex items-center gap-2"><BookOpen className="w-3 h-3" /> Financial_Ledger.xlsx</div>
59
+ Includes CEO Bonus ($350,000) and StealthLabs vendor costs.
60
+ </div>
61
+ <div className="p-3 bg-slate-900/50 rounded-lg border border-slate-800 text-xs text-slate-300">
62
+ <div className="text-blue-400 font-bold mb-1 flex items-center gap-2"><Cpu className="w-3 h-3" /> Prod_Topology.json</div>
63
+ Staging Server: 10.0.8.44. Gateway: 192.168.1.102.
64
+ </div>
65
+ </div>
66
+
67
+ <div className="mt-8">
68
+ <h4 className="text-[10px] font-bold text-slate-500 uppercase mb-3">System Status</h4>
69
+ <div className="flex items-center gap-2 text-xs text-green-400">
70
+ <div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
71
+ <span>UPIF Core Active</span>
72
+ </div>
73
+ <div className="flex items-center gap-2 text-xs text-blue-400 mt-2">
74
+ <div className="w-2 h-2 bg-blue-500 rounded-full"></div>
75
+ <span>Llama-3 (Local) Ready</span>
76
+ </div>
77
+ </div>
78
+ </div>
79
+
80
+ {/* MAIN AREA */}
81
+ <div className="xl:col-span-2 flex flex-col space-y-6">
82
+
83
+ {/* INPUT */}
84
+ <div className="glass p-8 rounded-2xl border border-blue-500/20 shadow-2xl relative">
85
+ <label className="text-sm font-semibold text-slate-300 mb-4 block">Request Input</label>
86
+ <textarea
87
+ rows={2}
88
+ className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-4 outline-none focus:border-blue-500 transition text-sm text-white resize-none font-mono"
89
+ placeholder="Enter a business query..."
90
+ value={prompt}
91
+ onChange={(e) => setPrompt(e.target.value)}
92
+ ></textarea>
93
+
94
+ <div className="mt-4 flex flex-wrap gap-2">
95
+ {PRESETS.map((p, i) => (
96
+ <button key={i} onClick={() => setPrompt(p)} className="text-[10px] bg-slate-900 hover:bg-slate-800 border border-slate-800 px-3 py-1.5 rounded-lg text-slate-400 transition cursor-pointer">
97
+ Example {i + 1}
98
+ </button>
99
+ ))}
100
+ </div>
101
+
102
+ <div className="mt-6">
103
+ <button
104
+ onClick={handleExecute}
105
+ disabled={loading}
106
+ className="w-full bg-blue-600 hover:bg-blue-700 disabled:opacity-50 text-white py-4 rounded-xl font-bold transition flex items-center justify-center space-x-2 cursor-pointer"
107
+ >
108
+ {loading ? <span>Processing...</span> : <><span>Execute Pipeline</span> <Play className="w-4 h-4" /></>}
109
+ </button>
110
+ </div>
111
+ </div>
112
+
113
+ {/* TERMINALS */}
114
+ <div className="grid md:grid-cols-2 gap-6 flex-1 h-[400px]">
115
+ <Terminal
116
+ title="No Enforcement"
117
+ type="vulnerable"
118
+ content={loading ? "Generating..." : (result ? "Raw output intercepted." : "")}
119
+ />
120
+ <Terminal
121
+ title="UPIF Protected"
122
+ type="protected"
123
+ content={loading ? "Scanning..." : (result?.output || "")}
124
+ logs={result?.logs}
125
+ />
126
+ </div>
127
+ </div>
128
+ </div>
129
+ </section>
130
+ );
131
+ };
web_client/src/components/Navbar.tsx ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import { ShieldCheck } from 'lucide-react';
3
+
4
+ export const Navbar = () => {
5
+ return (
6
+ <nav className="fixed top-0 w-full z-50 glass border-b border-slate-800">
7
+ <div className="max-w-7xl mx-auto px-6 h-20 flex justify-between items-center">
8
+ <div className="flex items-center space-x-3">
9
+ <div className="w-10 h-10 bg-blue-600 rounded-lg flex items-center justify-center font-bold text-white">
10
+ <ShieldCheck className="w-6 h-6" />
11
+ </div>
12
+ <span className="text-xl font-extrabold tracking-tight text-white">UPIF</span>
13
+ </div>
14
+ <div className="hidden md:flex space-x-8 text-sm text-slate-400">
15
+ <a href="#thesis" className="hover:text-white transition">The Thesis</a>
16
+ <a href="#lab" className="hover:text-white transition">Interaction Lab</a>
17
+ <a href="#integration" className="hover:text-white transition">Docs</a>
18
+ </div>
19
+ <button className="bg-blue-600 px-5 py-2.5 rounded-full text-sm font-semibold hover:bg-blue-700 transition text-white">
20
+ Request Demo
21
+ </button>
22
+ </div>
23
+ </nav>
24
+ );
25
+ };
web_client/src/components/Terminal.tsx ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { Terminal as TerminalIcon } from 'lucide-react';
3
+
4
+ interface TerminalProps {
5
+ title: string;
6
+ type: 'vulnerable' | 'protected';
7
+ content: string;
8
+ logs?: string[];
9
+ }
10
+
11
+ export const Terminal: React.FC<TerminalProps> = ({ title, type, content, logs }) => {
12
+ const isProtected = type === 'protected';
13
+ const borderColor = isProtected ? 'border-blue-500/20' : 'border-slate-800';
14
+ const bgColor = isProtected ? 'bg-blue-950/5' : 'bg-slate-900/20';
15
+ const titleColor = isProtected ? 'text-blue-400' : 'text-slate-400';
16
+
17
+ return (
18
+ <div className={`glass rounded-2xl border ${borderColor} overflow-hidden flex flex-col ${bgColor} relative h-full`}>
19
+ {isProtected && (
20
+ <div id="upif-scan-overlay" className="absolute top-0 left-0 w-full z-10 hidden">
21
+ <div className="scanner-line"></div>
22
+ </div>
23
+ )}
24
+
25
+ <div className={`bg-slate-900/50 px-4 py-3 flex items-center justify-between border-b ${borderColor}`}>
26
+ <div className="flex items-center gap-2">
27
+ <TerminalIcon className={`w-3 h-3 ${titleColor}`} />
28
+ <span className={`text-[10px] font-bold ${titleColor} uppercase tracking-widest`}>
29
+ {title}
30
+ </span>
31
+ </div>
32
+ <span className={`text-[10px] ${isProtected ? 'text-blue-400 font-bold' : 'text-slate-500'}`}>
33
+ {isProtected ? 'DETERMINISTIC' : 'UNFILTERED'}
34
+ </span>
35
+ </div>
36
+
37
+ <div className="p-6 text-xs text-slate-400 font-mono flex-1 min-h-[180px] leading-relaxed whitespace-pre-wrap">
38
+ {content || <span className="text-slate-600 italic">No request processed.</span>}
39
+ </div>
40
+
41
+ {logs && logs.length > 0 && (
42
+ <div className="border-t border-slate-800 p-2 bg-black/40 text-[10px] font-mono h-24 overflow-y-auto">
43
+ {logs.map((log, i) => (
44
+ <div key={i} className="text-slate-500 border-b border-slate-800/50 pb-1 mb-1 last:border-0">
45
+ {log}
46
+ </div>
47
+ ))}
48
+ </div>
49
+ )}
50
+ </div>
51
+ );
52
+ };
web_client/src/index.css ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ @layer base {
6
+ body {
7
+ @apply bg-brand-dark text-slate-100 antialiased;
8
+ }
9
+ }
10
+
11
+ .glass {
12
+ background: rgba(15, 23, 42, 0.7);
13
+ backdrop-filter: blur(14px);
14
+ border: 1px solid rgba(255, 255, 255, 0.08);
15
+ }
16
+
17
+ .scanner-line {
18
+ height: 2px;
19
+ background: linear-gradient(90deg, transparent, #3b82f6, transparent);
20
+ animation: scan 1.5s linear infinite;
21
+ }
22
+
23
+ @keyframes scan {
24
+ from { transform: translateY(-100%); }
25
+ to { transform: translateY(100%); }
26
+ }
web_client/src/main.tsx ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import './index.css'
4
+ import App from './App.tsx'
5
+
6
+ createRoot(document.getElementById('root')!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ )
web_client/tailwind.config.js ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ content: [
4
+ "./index.html",
5
+ "./src/**/*.{js,ts,jsx,tsx}",
6
+ ],
7
+ theme: {
8
+ extend: {
9
+ colors: {
10
+ brand: {
11
+ dark: "#020617",
12
+ primary: "#3b82f6",
13
+ }
14
+ },
15
+ fontFamily: {
16
+ sans: ['Inter', 'sans-serif'],
17
+ mono: ['Fira Code', 'monospace'],
18
+ }
19
+ },
20
+ },
21
+ plugins: [],
22
+ }
web_client/tsconfig.app.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
+ "target": "ES2022",
5
+ "useDefineForClassFields": true,
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "module": "ESNext",
8
+ "types": ["vite/client"],
9
+ "skipLibCheck": true,
10
+
11
+ /* Bundler mode */
12
+ "moduleResolution": "bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "verbatimModuleSyntax": true,
15
+ "moduleDetection": "force",
16
+ "noEmit": true,
17
+ "jsx": "react-jsx",
18
+
19
+ /* Linting */
20
+ "strict": true,
21
+ "noUnusedLocals": true,
22
+ "noUnusedParameters": true,
23
+ "erasableSyntaxOnly": true,
24
+ "noFallthroughCasesInSwitch": true,
25
+ "noUncheckedSideEffectImports": true
26
+ },
27
+ "include": ["src"]
28
+ }
web_client/tsconfig.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ { "path": "./tsconfig.app.json" },
5
+ { "path": "./tsconfig.node.json" }
6
+ ]
7
+ }
web_client/tsconfig.node.json ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
+ "target": "ES2023",
5
+ "lib": ["ES2023"],
6
+ "module": "ESNext",
7
+ "types": ["node"],
8
+ "skipLibCheck": true,
9
+
10
+ /* Bundler mode */
11
+ "moduleResolution": "bundler",
12
+ "allowImportingTsExtensions": true,
13
+ "verbatimModuleSyntax": true,
14
+ "moduleDetection": "force",
15
+ "noEmit": true,
16
+
17
+ /* Linting */
18
+ "strict": true,
19
+ "noUnusedLocals": true,
20
+ "noUnusedParameters": true,
21
+ "erasableSyntaxOnly": true,
22
+ "noFallthroughCasesInSwitch": true,
23
+ "noUncheckedSideEffectImports": true
24
+ },
25
+ "include": ["vite.config.ts"]
26
+ }
web_client/vite.config.ts ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+
4
+ // https://vite.dev/config/
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ })