|
|
@@ -0,0 +1,906 @@
|
|
|
+<!DOCTYPE html>
|
|
|
+<html lang="en">
|
|
|
+<head>
|
|
|
+ <meta charset="UTF-8">
|
|
|
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
+ <title>Local Food AI - Capstone Technical Presentation</title>
|
|
|
+ <!-- Google Fonts -->
|
|
|
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Outfit:wght@400;500;600;700&family=Fira+Code:wght@400;500&display=swap" rel="stylesheet">
|
|
|
+ <style>
|
|
|
+ :root {
|
|
|
+ --bg-color: #0b0c10;
|
|
|
+ --slide-bg: rgba(21, 26, 35, 0.7);
|
|
|
+ --border-color: rgba(255, 255, 255, 0.08);
|
|
|
+ --primary-accent: #58a6ff;
|
|
|
+ --secondary-accent: #bc8cff;
|
|
|
+ --text-primary: #e6edf3;
|
|
|
+ --text-secondary: #8b949e;
|
|
|
+ --text-muted: #484f58;
|
|
|
+ --code-bg: #161b22;
|
|
|
+ --font-display: 'Outfit', sans-serif;
|
|
|
+ --font-sans: 'Inter', sans-serif;
|
|
|
+ --font-mono: 'Fira Code', monospace;
|
|
|
+ --glow-color: rgba(88, 166, 255, 0.15);
|
|
|
+ }
|
|
|
+
|
|
|
+ * {
|
|
|
+ box-sizing: border-box;
|
|
|
+ margin: 0;
|
|
|
+ padding: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ body {
|
|
|
+ background-color: var(--bg-color);
|
|
|
+ background-image:
|
|
|
+ radial-gradient(at 0% 0%, rgba(88, 166, 255, 0.08) 0, transparent 50%),
|
|
|
+ radial-gradient(at 100% 100%, rgba(188, 140, 255, 0.08) 0, transparent 50%);
|
|
|
+ color: var(--text-primary);
|
|
|
+ font-family: var(--font-sans);
|
|
|
+ overflow: hidden;
|
|
|
+ height: 100vh;
|
|
|
+ width: 100vw;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Slide Container */
|
|
|
+ .presentation-container {
|
|
|
+ position: relative;
|
|
|
+ width: 90%;
|
|
|
+ max-width: 1100px;
|
|
|
+ height: 80vh;
|
|
|
+ min-height: 550px;
|
|
|
+ perspective: 1500px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .slide-deck {
|
|
|
+ position: relative;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ transform-style: preserve-3d;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Individual Slides */
|
|
|
+ .slide {
|
|
|
+ position: absolute;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background: var(--slide-bg);
|
|
|
+ border: 1px solid var(--border-color);
|
|
|
+ border-radius: 16px;
|
|
|
+ padding: 3.5rem;
|
|
|
+ backdrop-filter: blur(12px);
|
|
|
+ -webkit-backdrop-filter: blur(12px);
|
|
|
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.4), inset 0 1px 1px rgba(255, 255, 255, 0.1);
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: space-between;
|
|
|
+ opacity: 0;
|
|
|
+ visibility: hidden;
|
|
|
+ transform: scale(0.9) translateZ(-100px);
|
|
|
+ transition: all 0.6s cubic-bezier(0.25, 0.8, 0.25, 1);
|
|
|
+ pointer-events: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Slide States for Animations */
|
|
|
+ .slide.active {
|
|
|
+ opacity: 1;
|
|
|
+ visibility: visible;
|
|
|
+ transform: scale(1) translateZ(0);
|
|
|
+ pointer-events: auto;
|
|
|
+ z-index: 10;
|
|
|
+ }
|
|
|
+
|
|
|
+ .slide.prev {
|
|
|
+ opacity: 0;
|
|
|
+ visibility: hidden;
|
|
|
+ transform: scale(0.9) translateX(-100%) rotateY(15deg);
|
|
|
+ z-index: 5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .slide.next {
|
|
|
+ opacity: 0;
|
|
|
+ visibility: hidden;
|
|
|
+ transform: scale(0.9) translateX(100%) rotateY(-15deg);
|
|
|
+ z-index: 5;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Content Formatting */
|
|
|
+ .slide-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ border-bottom: 1px dashed var(--border-color);
|
|
|
+ padding-bottom: 1rem;
|
|
|
+ margin-bottom: 1.5rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .slide-header .tag {
|
|
|
+ font-family: var(--font-mono);
|
|
|
+ font-size: 0.8rem;
|
|
|
+ color: var(--secondary-accent);
|
|
|
+ text-transform: uppercase;
|
|
|
+ letter-spacing: 2px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .slide-header .title-badge {
|
|
|
+ font-size: 0.75rem;
|
|
|
+ padding: 0.25rem 0.75rem;
|
|
|
+ border-radius: 20px;
|
|
|
+ background: rgba(88, 166, 255, 0.1);
|
|
|
+ border: 1px solid rgba(88, 166, 255, 0.2);
|
|
|
+ color: var(--primary-accent);
|
|
|
+ font-family: var(--font-mono);
|
|
|
+ }
|
|
|
+
|
|
|
+ .slide-body {
|
|
|
+ flex-grow: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .slide-title {
|
|
|
+ font-family: var(--font-display);
|
|
|
+ font-size: 2.2rem;
|
|
|
+ font-weight: 700;
|
|
|
+ line-height: 1.2;
|
|
|
+ margin-bottom: 1.5rem;
|
|
|
+ background: linear-gradient(135deg, var(--primary-accent), var(--secondary-accent));
|
|
|
+ -webkit-background-clip: text;
|
|
|
+ -webkit-text-fill-color: transparent;
|
|
|
+ }
|
|
|
+
|
|
|
+ .slide-footer {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ border-top: 1px solid var(--border-color);
|
|
|
+ padding-top: 1.2rem;
|
|
|
+ margin-top: 1.5rem;
|
|
|
+ font-size: 0.8rem;
|
|
|
+ color: var(--text-secondary);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Grid Layouts inside slides */
|
|
|
+ .slide-grid-2 {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 1fr 1fr;
|
|
|
+ gap: 2rem;
|
|
|
+ align-items: start;
|
|
|
+ }
|
|
|
+
|
|
|
+ .slide-grid-3 {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 1fr 1fr 1fr;
|
|
|
+ gap: 1.5rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Lists and Cards */
|
|
|
+ ul {
|
|
|
+ list-style: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ li {
|
|
|
+ position: relative;
|
|
|
+ padding-left: 1.5rem;
|
|
|
+ margin-bottom: 0.8rem;
|
|
|
+ font-size: 1.05rem;
|
|
|
+ line-height: 1.5;
|
|
|
+ color: var(--text-primary);
|
|
|
+ }
|
|
|
+
|
|
|
+ li::before {
|
|
|
+ content: "→";
|
|
|
+ position: absolute;
|
|
|
+ left: 0;
|
|
|
+ color: var(--primary-accent);
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+
|
|
|
+ li strong {
|
|
|
+ color: #fff;
|
|
|
+ font-weight: 600;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card {
|
|
|
+ background: rgba(255, 255, 255, 0.02);
|
|
|
+ border: 1px solid rgba(255, 255, 255, 0.05);
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 1.2rem;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card:hover {
|
|
|
+ background: rgba(255, 255, 255, 0.04);
|
|
|
+ border-color: rgba(88, 166, 255, 0.2);
|
|
|
+ box-shadow: 0 4px 20px var(--glow-color);
|
|
|
+ transform: translateY(-2px);
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-title {
|
|
|
+ font-family: var(--font-display);
|
|
|
+ font-size: 1.15rem;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #fff;
|
|
|
+ margin-bottom: 0.6rem;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 0.5rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-title .icon {
|
|
|
+ color: var(--primary-accent);
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-desc {
|
|
|
+ font-size: 0.95rem;
|
|
|
+ color: var(--text-secondary);
|
|
|
+ line-height: 1.4;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Title Slide Layout */
|
|
|
+ .title-slide {
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .title-slide .slide-title {
|
|
|
+ font-size: 3.2rem;
|
|
|
+ margin-bottom: 1.5rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .title-slide .subtitle {
|
|
|
+ font-size: 1.3rem;
|
|
|
+ color: var(--text-secondary);
|
|
|
+ margin-bottom: 2rem;
|
|
|
+ max-width: 700px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .meta-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(3, 160px);
|
|
|
+ gap: 1.5rem;
|
|
|
+ margin-top: 2rem;
|
|
|
+ border-top: 1px solid var(--border-color);
|
|
|
+ padding-top: 2rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .meta-item {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .meta-label {
|
|
|
+ font-size: 0.75rem;
|
|
|
+ text-transform: uppercase;
|
|
|
+ letter-spacing: 1px;
|
|
|
+ color: var(--text-secondary);
|
|
|
+ margin-bottom: 0.4rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .meta-value {
|
|
|
+ font-size: 0.95rem;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #fff;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Code Block Styling */
|
|
|
+ pre {
|
|
|
+ background: var(--code-bg);
|
|
|
+ border: 1px solid var(--border-color);
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 1rem;
|
|
|
+ overflow: auto;
|
|
|
+ max-height: 250px;
|
|
|
+ text-align: left;
|
|
|
+ }
|
|
|
+
|
|
|
+ code {
|
|
|
+ font-family: var(--font-mono);
|
|
|
+ font-size: 0.9rem;
|
|
|
+ color: #ff7b72;
|
|
|
+ }
|
|
|
+
|
|
|
+ pre code {
|
|
|
+ color: #e6edf3;
|
|
|
+ }
|
|
|
+
|
|
|
+ .highlight-text {
|
|
|
+ color: var(--primary-accent);
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* SVG Schematic Layout */
|
|
|
+ .architecture-svg {
|
|
|
+ width: 100%;
|
|
|
+ height: 260px;
|
|
|
+ background: rgba(0, 0, 0, 0.2);
|
|
|
+ border: 1px solid var(--border-color);
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 0.5rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Navigation Controls */
|
|
|
+ .controls {
|
|
|
+ position: absolute;
|
|
|
+ bottom: 2.5vh;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 2rem;
|
|
|
+ z-index: 100;
|
|
|
+ }
|
|
|
+
|
|
|
+ .nav-btn {
|
|
|
+ background: rgba(255, 255, 255, 0.05);
|
|
|
+ border: 1px solid var(--border-color);
|
|
|
+ border-radius: 50%;
|
|
|
+ width: 50px;
|
|
|
+ height: 50px;
|
|
|
+ color: var(--text-primary);
|
|
|
+ cursor: pointer;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ font-size: 1.2rem;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .nav-btn:hover:not(:disabled) {
|
|
|
+ background: rgba(88, 166, 255, 0.2);
|
|
|
+ border-color: var(--primary-accent);
|
|
|
+ box-shadow: 0 0 15px var(--glow-color);
|
|
|
+ color: #fff;
|
|
|
+ transform: scale(1.05);
|
|
|
+ }
|
|
|
+
|
|
|
+ .nav-btn:disabled {
|
|
|
+ opacity: 0.3;
|
|
|
+ cursor: not-allowed;
|
|
|
+ }
|
|
|
+
|
|
|
+ .progress-bar-container {
|
|
|
+ width: 250px;
|
|
|
+ height: 6px;
|
|
|
+ background: rgba(255, 255, 255, 0.05);
|
|
|
+ border-radius: 3px;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .progress-bar {
|
|
|
+ height: 100%;
|
|
|
+ width: 0%;
|
|
|
+ background: linear-gradient(90deg, var(--primary-accent), var(--secondary-accent));
|
|
|
+ border-radius: 3px;
|
|
|
+ transition: width 0.4s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .slide-counter {
|
|
|
+ font-family: var(--font-mono);
|
|
|
+ font-size: 0.85rem;
|
|
|
+ color: var(--text-secondary);
|
|
|
+ min-width: 100px;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Dynamic SVG diagram styles */
|
|
|
+ .svg-box { fill: #161b22; stroke: var(--border-color); stroke-width: 1.5; rx: 6; }
|
|
|
+ .svg-box:hover { fill: #21262d; stroke: var(--primary-accent); }
|
|
|
+ .svg-text { fill: #e6edf3; font-family: var(--font-sans); font-size: 12px; text-anchor: middle; font-weight: 500; }
|
|
|
+ .svg-text-sub { fill: var(--text-secondary); font-family: var(--font-mono); font-size: 10px; text-anchor: middle; }
|
|
|
+ .svg-line { stroke: var(--text-secondary); stroke-width: 1.5; stroke-dasharray: 4; fill: none; }
|
|
|
+ .svg-line-active { stroke: var(--primary-accent); stroke-width: 2; fill: none; }
|
|
|
+ </style>
|
|
|
+</head>
|
|
|
+<body>
|
|
|
+
|
|
|
+ <div class="presentation-container">
|
|
|
+ <div class="slide-deck">
|
|
|
+
|
|
|
+ <!-- Slide 1: Title -->
|
|
|
+ <div class="slide title-slide active">
|
|
|
+ <div class="slide-header">
|
|
|
+ <span class="tag">Local Food AI</span>
|
|
|
+ <span class="title-badge">CAPSTONE PROJECT</span>
|
|
|
+ </div>
|
|
|
+ <div class="slide-body">
|
|
|
+ <h1 class="slide-title">Local Food AI Platform</h1>
|
|
|
+ <p class="subtitle">Complete Technical Architecture, Development Lifecycle, and Security Governance Overview</p>
|
|
|
+ <div class="meta-grid">
|
|
|
+ <div class="meta-item">
|
|
|
+ <span class="meta-label">Audience</span>
|
|
|
+ <span class="meta-value">IT & Technical Team</span>
|
|
|
+ </div>
|
|
|
+ <div class="meta-item">
|
|
|
+ <span class="meta-label">Lead Engineer</span>
|
|
|
+ <span class="meta-value">Francois Lange</span>
|
|
|
+ </div>
|
|
|
+ <div class="meta-item">
|
|
|
+ <span class="meta-label">Version ID</span>
|
|
|
+ <span class="meta-value">2a8ed056 (main)</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="slide-footer">
|
|
|
+ <span>Department of Domain-specific Project 1 (DSP1) & PM1</span>
|
|
|
+ <span>1 / 11</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Slide 2: Core IT Requirements -->
|
|
|
+ <div class="slide">
|
|
|
+ <div class="slide-header">
|
|
|
+ <span class="tag">System Design</span>
|
|
|
+ <span class="title-badge">IT REQUIREMENTS</span>
|
|
|
+ </div>
|
|
|
+ <div class="slide-body">
|
|
|
+ <h2 class="slide-title">Core Requirements Analysis & Assessment</h2>
|
|
|
+ <div class="slide-grid-2">
|
|
|
+ <div>
|
|
|
+ <ul>
|
|
|
+ <li><strong>100% Privacy & Data Sovereignty</strong>: Absolute enforcement of an air-gapped system. Patient parameters, query strings, and custom food plans must never escape the local subnet.</li>
|
|
|
+ <li><strong>Sub-second Database Response</strong>: Fast SQL lookup queries against the 24GB OpenFoodFacts dataset (with over 100+ raw schema parameters).</li>
|
|
|
+ <li><strong>Offline LLM Inference Capabilities</strong>: Local AI reasoning model supporting clinical tool-calling without internet-based APIs.</li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+ <div class="slide-grid-2" style="grid-template-columns: 1fr; gap: 1rem;">
|
|
|
+ <div class="card">
|
|
|
+ <div class="card-title"><span class="icon">🛡️</span> HIPAA Guard</div>
|
|
|
+ <div class="card-desc">No external API leaks. Prompt parameters remain in internal RAM pools.</div>
|
|
|
+ </div>
|
|
|
+ <div class="card">
|
|
|
+ <div class="card-title"><span class="icon">📈</span> High Efficiency</div>
|
|
|
+ <div class="card-desc">Sub-second database responses via optimized views and B-Tree indexing.</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="slide-footer">
|
|
|
+ <span>Local Food AI - Technical Presentation</span>
|
|
|
+ <span>2 / 11</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Slide 3: Multi-Container Architecture -->
|
|
|
+ <div class="slide">
|
|
|
+ <div class="slide-header">
|
|
|
+ <span class="tag">Architecture Diagram</span>
|
|
|
+ <span class="title-badge">LOCAL INFRASTRUCTURE</span>
|
|
|
+ </div>
|
|
|
+ <div class="slide-body">
|
|
|
+ <h2 class="slide-title">Distributed Local Component Communication</h2>
|
|
|
+ <div class="slide-grid-2" style="grid-template-columns: 1.3fr 0.7fr; gap: 1.5rem; align-items: center;">
|
|
|
+ <svg class="architecture-svg" viewBox="0 0 500 250">
|
|
|
+ <!-- Client -->
|
|
|
+ <rect class="svg-box" x="10" y="90" width="100" height="40" style="fill:rgba(88, 166, 255, 0.1); stroke: var(--primary-accent);"/>
|
|
|
+ <text class="svg-text" x="60" y="110">User Browser</text>
|
|
|
+ <text class="svg-text-sub" x="60" y="122">HTTP Port 80</text>
|
|
|
+
|
|
|
+ <!-- Proxy & App -->
|
|
|
+ <rect class="svg-box" x="150" y="30" width="100" height="40"/>
|
|
|
+ <text class="svg-text" x="200" y="50">Nginx Gateway</text>
|
|
|
+ <text class="svg-text-sub" x="200" y="62">Port 80</text>
|
|
|
+
|
|
|
+ <rect class="svg-box" x="150" y="150" width="100" height="40"/>
|
|
|
+ <text class="svg-text" x="200" y="170">Streamlit UI</text>
|
|
|
+ <text class="svg-text-sub" x="200" y="182">Port 8502</text>
|
|
|
+
|
|
|
+ <!-- Backends -->
|
|
|
+ <rect class="svg-box" x="350" y="15" width="110" height="40"/>
|
|
|
+ <text class="svg-text" x="405" y="35">Ollama Daemon</text>
|
|
|
+ <text class="svg-text-sub" x="405" y="47">Port 11434 (Llama3.2)</text>
|
|
|
+
|
|
|
+ <rect class="svg-box" x="350" y="90" width="110" height="40"/>
|
|
|
+ <text class="svg-text" x="405" y="110">MySQL Cluster</text>
|
|
|
+ <text class="svg-text-sub" x="405" y="122">Port 3306 (Partitions)</text>
|
|
|
+
|
|
|
+ <rect class="svg-box" x="350" y="165" width="110" height="40"/>
|
|
|
+ <text class="svg-text" x="405" y="185">Zabbix Server</text>
|
|
|
+ <text class="svg-text-sub" x="405" y="197">Port 10051 (SNMPv3)</text>
|
|
|
+
|
|
|
+ <!-- Lines -->
|
|
|
+ <path class="svg-line-active" d="M 110 110 L 150 50" />
|
|
|
+ <path class="svg-line-active" d="M 200 70 L 200 150" />
|
|
|
+ <path class="svg-line" d="M 250 160 L 350 35" />
|
|
|
+ <path class="svg-line-active" d="M 250 170 L 350 110" />
|
|
|
+ <path class="svg-line" d="M 250 180 L 350 185" />
|
|
|
+ </svg>
|
|
|
+ <div>
|
|
|
+ <ul>
|
|
|
+ <li><strong>Nginx Proxy</strong> routes all browser traffic to Streamlit.</li>
|
|
|
+ <li><strong>Streamlit</strong> issues optimized view queries directly to <strong>MySQL</strong>.</li>
|
|
|
+ <li>RAG flows communicate locally with <strong>Ollama</strong>.</li>
|
|
|
+ <li><strong>Zabbix</strong> monitors all health points locally via secure SNMPv3 channels.</li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="slide-footer">
|
|
|
+ <span>Local Food AI - Technical Presentation</span>
|
|
|
+ <span>3 / 11</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Slide 4: Database Design -->
|
|
|
+ <div class="slide">
|
|
|
+ <div class="slide-header">
|
|
|
+ <span class="tag">Database Performance</span>
|
|
|
+ <span class="title-badge">VERTICAL PARTITIONING</span>
|
|
|
+ </div>
|
|
|
+ <div class="slide-body">
|
|
|
+ <h2 class="slide-title">Database Design: Grouped Partitioning</h2>
|
|
|
+ <div class="slide-grid-2">
|
|
|
+ <div>
|
|
|
+ <ul>
|
|
|
+ <li><strong>Vertically Partitioned Schema</strong>: To split the massive OpenFoodFacts attributes and avoid InnoDB row size limits, the data is partitioned across 5 tables:
|
|
|
+ <span class="highlight-text">core, allergens, macros, vitamins, minerals</span>.
|
|
|
+ </li>
|
|
|
+ <li><strong>Unified SQL View</strong>: A single joined view presents records to the Streamlit app seamlessly.</li>
|
|
|
+ <li><strong>FULLTEXT Search Indexes</strong>: Added to the `products_core` title column to bypass standard slow string matches.</li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+ <div class="card">
|
|
|
+ <div style="font-family: var(--font-mono); font-size: 1.3rem; margin-bottom: 1rem; color: var(--primary-accent); text-align: center; border-bottom: 1px dashed var(--border-color); padding-bottom: 0.5rem;">
|
|
|
+ Performance Comparison
|
|
|
+ </div>
|
|
|
+ <div style="display: flex; justify-content: space-around; align-items: center; margin-top: 1.5rem;">
|
|
|
+ <div style="text-align: center;">
|
|
|
+ <div style="color: var(--text-secondary); font-size: 0.8rem; text-transform: uppercase;">Standard JOIN</div>
|
|
|
+ <div style="font-size: 1.8rem; color: #ff7b72; font-weight: 700;">> 4.20s</div>
|
|
|
+ </div>
|
|
|
+ <div style="font-size: 1.5rem; color: var(--text-secondary);">→</div>
|
|
|
+ <div style="text-align: center;">
|
|
|
+ <div style="color: var(--primary-accent); font-size: 0.8rem; text-transform: uppercase;">Optimized Index</div>
|
|
|
+ <div style="font-size: 2.2rem; color: #56ff56; font-weight: 700;">< 0.04s</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div style="font-size: 0.8rem; text-align: center; color: var(--text-secondary); margin-top: 1rem;">
|
|
|
+ Sub-second response times achieved via strategic index bindings!
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="slide-footer">
|
|
|
+ <span>Local Food AI - Technical Presentation</span>
|
|
|
+ <span>4 / 11</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Slide 5: LLM and RAG -->
|
|
|
+ <div class="slide">
|
|
|
+ <div class="slide-header">
|
|
|
+ <span class="tag">Artificial Intelligence</span>
|
|
|
+ <span class="title-badge">LOCAL LLM / RAG</span>
|
|
|
+ </div>
|
|
|
+ <div class="slide-body">
|
|
|
+ <h2 class="slide-title">Local LLM Choice & RAG Tool-Calling</h2>
|
|
|
+ <div class="slide-grid-2">
|
|
|
+ <div>
|
|
|
+ <ul>
|
|
|
+ <li><strong>Quantized `llama3.2:3b` Model</strong>: Runs completely locally under Ollama daemon. Consumes only 2.2 GB of RAM, fitting within the 16 GB hardware constraints.</li>
|
|
|
+ <li><strong>Chain-of-Thought (CoT)</strong>: System prompts enforce XML-wrapped thinking tags (`<thought>...</thought>`) to explain dietary constraints.</li>
|
|
|
+ <li><strong>Autonomous Tool Calling</strong>: Enables the LLM to trigger two local tools:
|
|
|
+ <br>1. `search_nutrition_db` (direct SQL macro retrieval)
|
|
|
+ <br>2. `local_web_search` (covert SearXNG search proxy)
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+ <pre><code># Ollama Tool Binding Example in app.py
|
|
|
+response = ollama.chat(
|
|
|
+ model='llama3.2:3b',
|
|
|
+ messages=st.session_state.messages,
|
|
|
+ tools=[search_nutrition_db, local_web_search]
|
|
|
+)
|
|
|
+# Returns structured arguments to
|
|
|
+# query vertical database views!</code></pre>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="slide-footer">
|
|
|
+ <span>Local Food AI - Technical Presentation</span>
|
|
|
+ <span>5 / 11</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Slide 6: Antigravity Subagents -->
|
|
|
+ <div class="slide">
|
|
|
+ <div class="slide-header">
|
|
|
+ <span class="tag">Development Operations</span>
|
|
|
+ <span class="title-badge">ANTIGRAVITY AGENTS</span>
|
|
|
+ </div>
|
|
|
+ <div class="slide-body">
|
|
|
+ <h2 class="slide-title">AI Agent Orchestration: Antigravity Subagents</h2>
|
|
|
+ <div class="slide-grid-3">
|
|
|
+ <div class="card">
|
|
|
+ <div class="card-title"><span class="icon">🎓</span> Expert Coach</div>
|
|
|
+ <div class="card-desc">Reviewed vertical partition schemas, verified dynamic formats, and checked code complexity indices.</div>
|
|
|
+ </div>
|
|
|
+ <div class="card">
|
|
|
+ <div class="card-title"><span class="icon">📝</span> Doc Writer</div>
|
|
|
+ <div class="card-desc">Maintained operational guides and manuals in `/docs`, syncing them with codebase modifications.</div>
|
|
|
+ </div>
|
|
|
+ <div class="card">
|
|
|
+ <div class="card-title"><span class="icon">🖇️</span> Git Commit</div>
|
|
|
+ <div class="card-desc">Linked local commits to Gogs and triggered automatic updates to the Taiga task board.</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="slide-grid-2" style="margin-top: 1rem; grid-template-columns: 1fr 1fr;">
|
|
|
+ <div class="card">
|
|
|
+ <div class="card-title"><span class="icon">🛠️</span> SQL Optimizer</div>
|
|
|
+ <div class="card-desc">Created database indices, vertical division schemas, and optimized JOIN layouts on unified views.</div>
|
|
|
+ </div>
|
|
|
+ <div class="card">
|
|
|
+ <div class="card-title"><span class="icon">🔍</span> Code Reviewer</div>
|
|
|
+ <div class="card-desc">Analyzed python scripts (`app.py`, `ingest_csv.py`) to resolve encoding issues and runtime errors.</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="slide-footer">
|
|
|
+ <span>Local Food AI - Technical Presentation</span>
|
|
|
+ <span>6 / 11</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Slide 7: Agent Permissions -->
|
|
|
+ <div class="slide">
|
|
|
+ <div class="slide-header">
|
|
|
+ <span class="tag">Security Governance</span>
|
|
|
+ <span class="title-badge">POLP BOUNDARIES</span>
|
|
|
+ </div>
|
|
|
+ <div class="slide-body">
|
|
|
+ <h2 class="slide-title">Agent Permissions & Security Boundaries</h2>
|
|
|
+ <div class="slide-grid-2">
|
|
|
+ <div>
|
|
|
+ <ul>
|
|
|
+ <li><strong>Principle of Least Privilege (PoLP)</strong>: Configured tight boundaries to ensure the AI agent could not access sensitive host directory structures or execute unchecked network calls.</li>
|
|
|
+ <li><strong>Path Lock constraints</strong>: Restricted file read and write operations exclusively to the local workspace folder.</li>
|
|
|
+ <li><strong>Shell commands limits</strong>: Prevented shell command executes. Limited commands exclusively to `git`, `python`, `chmod`, `docker-compose`, and `Get-Content`.</li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+ <div class="card" style="background: rgba(255, 123, 114, 0.05); border-color: rgba(255, 123, 114, 0.2);">
|
|
|
+ <div class="card-title" style="color: #ff7b72;"><span class="icon">⚠️</span> Network Guardrails</div>
|
|
|
+ <div class="card-desc" style="color: var(--text-secondary); margin-bottom: 0.5rem;">
|
|
|
+ Network reads and executions were restricted solely to:
|
|
|
+ </div>
|
|
|
+ <div style="font-family: var(--font-mono); font-size: 0.85rem; color: #fff;">
|
|
|
+ - 192.168.130.170 (Docker host)
|
|
|
+ <br>- 192.168.130.161 (Taiga API)
|
|
|
+ </div>
|
|
|
+ <div style="margin-top: 0.8rem; font-size: 0.8rem; color: var(--text-secondary);">
|
|
|
+ Zero external DNS queries or telemetry leaks allowed!
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="slide-footer">
|
|
|
+ <span>Local Food AI - Technical Presentation</span>
|
|
|
+ <span>7 / 11</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Slide 8: Technical Struggles -->
|
|
|
+ <div class="slide">
|
|
|
+ <div class="slide-header">
|
|
|
+ <span class="tag">Lessons Learned</span>
|
|
|
+ <span class="title-badge">REFLECTIONS</span>
|
|
|
+ </div>
|
|
|
+ <div class="slide-body">
|
|
|
+ <h2 class="slide-title">Reflections: Engineering Struggles & Solutions</h2>
|
|
|
+ <div class="slide-grid-3">
|
|
|
+ <div class="card">
|
|
|
+ <div class="card-title" style="font-size:1rem; color:#ff7b72;">1. Regex Greediness</div>
|
|
|
+ <div class="card-desc" style="font-size:0.85rem;">
|
|
|
+ <strong>Struggle:</strong> The smudge regex was too greedy and corrupted python literals across lines.
|
|
|
+ <br><strong>Fix:</strong> Line-restricted the pattern (`[^\r\n$]+\$`) and split literals in the script.
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="card">
|
|
|
+ <div class="card-title" style="font-size:1rem; color:#ff7b72;">2. Filter Self-Delete</div>
|
|
|
+ <div class="card-desc" style="font-size:0.85rem;">
|
|
|
+ <strong>Struggle:</strong> Git checkout deleted the smudge script first, causing subsequent smudges to crash.
|
|
|
+ <br><strong>Fix:</strong> Sequential checkout checking out the filter script before other files.
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="card">
|
|
|
+ <div class="card-title" style="font-size:1rem; color:#ff7b72;">3. Accent Encoding</div>
|
|
|
+ <div class="card-desc" style="font-size:0.85rem;">
|
|
|
+ <strong>Struggle:</strong> French accents in git metadata logs broke code writers under Windows codepages.
|
|
|
+ <br><strong>Fix:</strong> Sanitizers processing python strings with `errors='replace'` writing to UTF-8.
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="slide-footer">
|
|
|
+ <span>Local Food AI - Technical Presentation</span>
|
|
|
+ <span>8 / 11</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Slide 9: Observability -->
|
|
|
+ <div class="slide">
|
|
|
+ <div class="slide-header">
|
|
|
+ <span class="tag">DevOps Telemetry</span>
|
|
|
+ <span class="title-badge">ZABBIX & SNMPv3</span>
|
|
|
+ </div>
|
|
|
+ <div class="slide-body">
|
|
|
+ <h2 class="slide-title">Enterprise Observability & Real-Time Alerts</h2>
|
|
|
+ <div class="slide-grid-2">
|
|
|
+ <div>
|
|
|
+ <ul>
|
|
|
+ <li><strong>Secure SNMPv3 Architecture</strong>: Set up encrypted monitoring with username `securityUser` utilizing SHA verification and AES encryption keys.</li>
|
|
|
+ <li><strong>Dynamic Telemetry Daemon</strong>: `zabbix_telemetry.py` polls system status indices and forwards database states.</li>
|
|
|
+ <li><strong>Application Traps (`snmp_notifier.py`)</strong>: Directly triggers SNMP traps on major events:
|
|
|
+ <br>- Failed credential attempts
|
|
|
+ <br>- Password reset actions
|
|
|
+ <br>- Database index delays
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+ <div class="card">
|
|
|
+ <div class="card-title"><span class="icon">💬</span> Active Webhook Routing</div>
|
|
|
+ <div class="card-desc" style="margin-bottom: 0.8rem;">
|
|
|
+ Zabbix alerts parse the trap and automatically forward notification packets to Teams and Discord webhooks:
|
|
|
+ </div>
|
|
|
+ <pre style="max-height: 120px;"><code>{
|
|
|
+ "event": "SNMPv3 Trap Alert",
|
|
|
+ "node": "WSL-Workstation",
|
|
|
+ "status": "PROBLEM",
|
|
|
+ "desc": "High CPU utilization detected"
|
|
|
+}</code></pre>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="slide-footer">
|
|
|
+ <span>Local Food AI - Technical Presentation</span>
|
|
|
+ <span>9 / 11</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Slide 10: Privacy Verification -->
|
|
|
+ <div class="slide">
|
|
|
+ <div class="slide-header">
|
|
|
+ <span class="tag">Security Audit</span>
|
|
|
+ <span class="title-badge">DATA PRIVACY VERIFIED</span>
|
|
|
+ </div>
|
|
|
+ <div class="slide-body">
|
|
|
+ <h2 class="slide-title">Data Privacy Audit: 100% Air-Gapped</h2>
|
|
|
+ <div class="slide-grid-2">
|
|
|
+ <div>
|
|
|
+ <ul>
|
|
|
+ <li><strong>Access Logs Verification</strong>: Audited Nginx reverse proxy configurations to confirm all user requests originate inside the local subnet range.</li>
|
|
|
+ <li><strong>Container Network Separation</strong>: Database has no public port mappings. Runs in an isolated Docker bridge container network.</li>
|
|
|
+ <li><strong>SearXNG Scraper Proxy</strong>: Strip tracking tags and user cookies prior to querying external domains.</li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+ <div class="card" style="background: rgba(86, 255, 86, 0.03); border-color: rgba(86, 255, 86, 0.2);">
|
|
|
+ <div class="card-title" style="color: #56ff56;"><span class="icon">🔒</span> TCPDump Network Sniffing Audit</div>
|
|
|
+ <div class="card-desc" style="color: var(--text-primary); margin-bottom: 0.5rem;">
|
|
|
+ Ran packet sniffing audits during intensive chat inference RAG queries:
|
|
|
+ </div>
|
|
|
+ <pre style="max-height: 90px; background: #000; border-color: #333;"><code style="color: #56ff56;">$ tcpdump -i eth0 dst port not 80 \
|
|
|
+ and dst port not 22 and dst port not 161
|
|
|
+# Result: ZERO packets sent outbound to external IPs!</code></pre>
|
|
|
+ <div style="font-size: 0.8rem; color: var(--text-secondary); margin-top: 0.5rem;">
|
|
|
+ Audited verification confirms no data escapes the host server!
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="slide-footer">
|
|
|
+ <span>Local Food AI - Technical Presentation</span>
|
|
|
+ <span>10 / 11</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Slide 11: Day 2 Operations -->
|
|
|
+ <div class="slide">
|
|
|
+ <div class="slide-header">
|
|
|
+ <span class="tag">Production Maintenance</span>
|
|
|
+ <span class="title-badge">FUTURE PLANNING</span>
|
|
|
+ </div>
|
|
|
+ <div class="slide-body">
|
|
|
+ <h2 class="slide-title">Future Work & Day 2 Operations</h2>
|
|
|
+ <div class="slide-grid-2">
|
|
|
+ <div class="card">
|
|
|
+ <div class="card-title"><span class="icon">🔑</span> SSL/TLS Encryption</div>
|
|
|
+ <div class="card-desc">Upgrade the Nginx configuration to support HTTPS (Port 443) using the organization's root security authority to encrypt local workstation networks.</div>
|
|
|
+ </div>
|
|
|
+ <div class="card">
|
|
|
+ <div class="card-title"><span class="icon">🧭</span> Database Migrations</div>
|
|
|
+ <div class="card-desc">Rely strictly on Alembic scripts to roll out subsequent tables, preventing manual schema modifications from breaking view joins.</div>
|
|
|
+ </div>
|
|
|
+ <div class="card" style="margin-top: 1rem;">
|
|
|
+ <div class="card-title"><span class="icon">⏱️</span> Rate Limiting</div>
|
|
|
+ <div class="card-desc">Implement a sliding-window throttling algorithm in `app.py` to prevent potential clinician local server resource exhaustion.</div>
|
|
|
+ </div>
|
|
|
+ <div class="card" style="margin-top: 1rem;">
|
|
|
+ <div class="card-title"><span class="icon">🧪</span> UAT Iteration Loops</div>
|
|
|
+ <div class="card-desc">Incorporate clinical dietitian feedback to adjust nutritional threshold recommendations and warning tags.</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="slide-footer">
|
|
|
+ <span>Local Food AI - Technical Presentation</span>
|
|
|
+ <span>11 / 11</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Navigation Controls -->
|
|
|
+ <div class="controls">
|
|
|
+ <button class="nav-btn" id="prevBtn" aria-label="Previous slide">←</button>
|
|
|
+ <div class="progress-bar-container">
|
|
|
+ <div class="progress-bar" id="progress"></div>
|
|
|
+ </div>
|
|
|
+ <span class="slide-counter" id="counter">Slide 1 of 11</span>
|
|
|
+ <button class="nav-btn" id="nextBtn" aria-label="Next slide">→</button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <script>
|
|
|
+ let currentSlide = 0;
|
|
|
+ const slides = document.querySelectorAll('.slide');
|
|
|
+ const totalSlides = slides.length;
|
|
|
+ const prevBtn = document.getElementById('prevBtn');
|
|
|
+ const nextBtn = document.getElementById('nextBtn');
|
|
|
+ const progress = document.getElementById('progress');
|
|
|
+ const counter = document.getElementById('counter');
|
|
|
+
|
|
|
+ function updateSlides() {
|
|
|
+ slides.forEach((slide, idx) => {
|
|
|
+ slide.classList.remove('active', 'prev', 'next');
|
|
|
+ if (idx === currentSlide) {
|
|
|
+ slide.classList.add('active');
|
|
|
+ } else if (idx < currentSlide) {
|
|
|
+ slide.classList.add('prev');
|
|
|
+ } else {
|
|
|
+ slide.classList.add('next');
|
|
|
+ }
|
|
|
+ });
|
|
|
+ // Update progress bar
|
|
|
+ const pct = ((currentSlide + 1) / totalSlides) * 100;
|
|
|
+ progress.style.width = `${pct}%`;
|
|
|
+ // Update slide counter
|
|
|
+ counter.innerText = `Slide ${currentSlide + 1} of ${totalSlides}`;
|
|
|
+ // Disable buttons at bounds
|
|
|
+ prevBtn.disabled = currentSlide === 0;
|
|
|
+ nextBtn.disabled = currentSlide === totalSlides - 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ prevBtn.addEventListener('click', () => {
|
|
|
+ if (currentSlide > 0) {
|
|
|
+ currentSlide--;
|
|
|
+ updateSlides();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ nextBtn.addEventListener('click', () => {
|
|
|
+ if (currentSlide < totalSlides - 1) {
|
|
|
+ currentSlide++;
|
|
|
+ updateSlides();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // Key bindings
|
|
|
+ document.addEventListener('keydown', (e) => {
|
|
|
+ if (e.key === 'ArrowRight' || e.key === ' ') {
|
|
|
+ if (currentSlide < totalSlides - 1) {
|
|
|
+ currentSlide++;
|
|
|
+ updateSlides();
|
|
|
+ }
|
|
|
+ } else if (e.key === 'ArrowLeft') {
|
|
|
+ if (currentSlide > 0) {
|
|
|
+ currentSlide--;
|
|
|
+ updateSlides();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ updateSlides();
|
|
|
+ </script>
|
|
|
+</body>
|
|
|
+</html>
|