Spaces:
Build error
Build error
Commit ·
b28041c
1
Parent(s): 5e56bcf
Deploy: Limitless UPIF Stack (Docker/FastAPI/React)
Browse files- Dockerfile +45 -0
- model_loader.py +55 -0
- requirements.txt +9 -0
- server.py +103 -0
- web_client/.gitignore +24 -0
- web_client/README.md +73 -0
- web_client/eslint.config.js +23 -0
- web_client/index.html +13 -0
- web_client/package-lock.json +0 -0
- web_client/package.json +34 -0
- web_client/postcss.config.js +6 -0
- web_client/public/vite.svg +1 -0
- web_client/src/App.css +42 -0
- web_client/src/App.tsx +20 -0
- web_client/src/assets/react.svg +1 -0
- web_client/src/components/Hero.tsx +46 -0
- web_client/src/components/InteractionLab.tsx +131 -0
- web_client/src/components/Navbar.tsx +25 -0
- web_client/src/components/Terminal.tsx +52 -0
- web_client/src/index.css +26 -0
- web_client/src/main.tsx +10 -0
- web_client/tailwind.config.js +22 -0
- web_client/tsconfig.app.json +28 -0
- web_client/tsconfig.json +7 -0
- web_client/tsconfig.node.json +26 -0
- web_client/vite.config.ts +7 -0
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 |
+
})
|