contextflow-env / index.html
namish10's picture
Upload index.html with huggingface_hub
6729a10 verified
<!DOCTYPE html>
<html>
<head>
<title>ContextFlow - Smart Learning Assistant</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js" crossorigin="anonymous"></script>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0f0f1a; min-height: 100vh; color: white; }
.screen { display: none; padding: 20px; max-width: 1400px; margin: 0 auto; }
.screen.active { display: block; }
/* Header */
.header { display: flex; justify-content: space-between; align-items: center; padding: 15px 20px; background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); border-radius: 15px; margin-bottom: 20px; }
.header h1 { font-size: 24px; }
.header-right { display: flex; gap: 10px; align-items: center; }
.settings-btn { background: rgba(255,255,255,0.2); border: none; padding: 10px 20px; border-radius: 20px; color: white; cursor: pointer; }
/* Grid Layout */
.main-grid { display: grid; grid-template-columns: 1fr 1fr 350px; gap: 20px; }
/* Cards */
.card { background: #1a1a2e; border-radius: 15px; padding: 20px; border: 1px solid #333; }
.card h2 { color: #667eea; font-size: 14px; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #333; display: flex; justify-content: space-between; align-items: center; }
.card h2 .badge { background: #10b981; padding: 2px 8px; border-radius: 10px; font-size: 10px; }
/* Camera Section */
.camera-container { position: relative; background: #000; border-radius: 10px; overflow: hidden; aspect-ratio: 4/3; }
#video { width: 100%; height: 100%; object-fit: cover; transform: scaleX(-1); }
#canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; transform: scaleX(-1); pointer-events: none; }
.gesture-feedback { position: absolute; bottom: 10px; left: 10px; background: rgba(0,0,0,0.8); padding: 10px 15px; border-radius: 10px; font-size: 14px; }
.gesture-feedback.detected { background: #10b981; }
.gesture-feedback.doubt { background: #f59e0b; }
/* Gesture Commands */
.gesture-commands { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin-top: 15px; }
.gesture-cmd { background: #252540; padding: 12px; border-radius: 10px; text-align: center; cursor: pointer; transition: all 0.3s; border: 2px solid transparent; }
.gesture-cmd:hover, .gesture-cmd.active { border-color: #667eea; background: #333; }
.gesture-cmd .icon { font-size: 24px; margin-bottom: 5px; }
.gesture-cmd .name { font-size: 12px; color: #888; }
.gesture-cmd .desc { font-size: 10px; color: #666; margin-top: 3px; }
/* Context Panel */
.context-item { background: #252540; padding: 15px; border-radius: 10px; margin-bottom: 10px; border-left: 3px solid #667eea; }
.context-item h4 { color: #667eea; margin-bottom: 5px; }
.context-item p { color: #aaa; font-size: 13px; line-height: 1.5; }
.context-item .source { font-size: 10px; color: #666; margin-top: 5px; }
/* Doubt Input */
.doubt-input-container { margin-top: 15px; }
.doubt-input { width: 100%; background: #252540; border: 1px solid #333; border-radius: 10px; padding: 15px; color: white; font-size: 14px; resize: none; height: 80px; }
.doubt-input:focus { outline: none; border-color: #667eea; }
.send-btn { width: 100%; background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); border: none; padding: 12px; border-radius: 10px; color: white; font-weight: bold; cursor: pointer; margin-top: 10px; }
/* Confusion Meter */
.confusion-container { text-align: center; padding: 20px; }
.confusion-circle { width: 180px; height: 180px; border-radius: 50%; background: conic-gradient(var(--confusion-color, #10b981) var(--confusion-percent, 0%), #252540 0%); display: flex; align-items: center; justify-content: center; margin: 0 auto; transition: all 0.5s; }
.confusion-inner { width: 145px; height: 145px; border-radius: 50%; background: #1a1a2e; display: flex; flex-direction: column; align-items: center; justify-content: center; }
.confusion-value { font-size: 36px; font-weight: bold; }
.confusion-label { font-size: 12px; color: #888; }
.intervention-suggestion { background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); padding: 15px; border-radius: 10px; margin-top: 20px; text-align: center; }
.intervention-suggestion h4 { margin-bottom: 5px; }
.intervention-suggestion p { font-size: 14px; opacity: 0.9; }
/* Signal Bars */
.signal-bars { display: flex; gap: 20px; justify-content: center; margin-top: 20px; }
.signal-bar { text-align: center; }
.signal-bar .bar { width: 30px; height: 80px; background: #252540; border-radius: 5px; position: relative; overflow: hidden; }
.signal-bar .fill { position: absolute; bottom: 0; width: 100%; background: #667eea; transition: height 0.3s; }
.signal-bar .label { font-size: 10px; color: #666; margin-top: 5px; }
.signal-bar .value { font-size: 12px; font-weight: bold; }
/* Knowledge Sources */
.sources { display: flex; gap: 10px; margin-top: 15px; flex-wrap: wrap; }
.source-badge { background: #252540; padding: 8px 15px; border-radius: 20px; font-size: 12px; display: flex; align-items: center; gap: 5px; cursor: pointer; border: 1px solid transparent; transition: all 0.3s; }
.source-badge:hover { border-color: #667eea; }
.source-badge.connected { border-color: #10b981; background: rgba(16, 185, 129, 0.1); }
.source-badge .dot { width: 8px; height: 8px; border-radius: 50%; background: #666; }
.source-badge.connected .dot { background: #10b981; }
/* Modal */
.modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 1000; align-items: center; justify-content: center; }
.modal.active { display: flex; }
.modal-content { background: #1a1a2e; border-radius: 20px; padding: 30px; max-width: 500px; width: 90%; max-height: 80vh; overflow-y: auto; }
.modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.modal-header h2 { color: #667eea; }
.close-btn { background: none; border: none; color: #888; font-size: 24px; cursor: pointer; }
.form-group { margin-bottom: 15px; }
.form-group label { display: block; margin-bottom: 5px; color: #888; font-size: 12px; }
.form-group input { width: 100%; background: #252540; border: 1px solid #333; border-radius: 10px; padding: 12px; color: white; }
.form-group input:focus { outline: none; border-color: #667eea; }
.save-btn { width: 100%; background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); border: none; padding: 15px; border-radius: 10px; color: white; font-weight: bold; cursor: pointer; }
/* Notification */
.notification { position: fixed; top: 20px; right: 20px; background: #10b981; padding: 15px 25px; border-radius: 10px; display: none; z-index: 1001; animation: slideIn 0.3s; }
.notification.show { display: block; }
@keyframes slideIn { from { transform: translateX(100%); } to { transform: translateX(0); } }
/* Chat */
.chat-container { max-height: 300px; overflow-y: auto; margin-bottom: 10px; }
.chat-message { padding: 10px 15px; border-radius: 10px; margin-bottom: 10px; max-width: 85%; }
.chat-message.user { background: #667eea; margin-left: auto; }
.chat-message.ai { background: #252540; }
.chat-message.ai .thinking { color: #888; font-size: 12px; margin-bottom: 5px; }
/* Response Sources */
.response-sources { margin-top: 10px; padding-top: 10px; border-top: 1px solid #333; font-size: 11px; color: #666; }
</style>
</head>
<body>
<!-- Settings Modal -->
<div id="settings-modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>Settings</h2>
<button class="close-btn" onclick="closeSettings()">&times;</button>
</div>
<h3 style="color: #667eea; margin-bottom: 15px;">Knowledge Sources</h3>
<div class="form-group">
<label>Notion API Key</label>
<input type="password" id="notion-key" placeholder="secret_xxx">
</div>
<div class="form-group">
<label>Notion Database ID</label>
<input type="text" id="notion-db" placeholder="abc123...">
</div>
<div class="form-group">
<label>SuperMemory API Key</label>
<input type="password" id="supermemory-key" placeholder="sm_xxx">
</div>
<h3 style="color: #667eea; margin: 20px 0 15px;">Tracking Options</h3>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
<span>Camera Tracking</span>
<label class="toggle">
<input type="checkbox" id="camera-enabled" checked>
<span class="toggle-slider"></span>
</label>
</div>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
<span>Face Blur (Privacy)</span>
<label class="toggle">
<input type="checkbox" id="face-blur" checked>
<span class="toggle-slider"></span>
</label>
</div>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
<span>Behavioral Tracking</span>
<label class="toggle">
<input type="checkbox" id="behavior-enabled" checked>
<span class="toggle-slider"></span>
</label>
</div>
<button class="save-btn" onclick="saveSettings()">Save Settings</button>
<button class="save-btn" style="margin-top: 10px; background: #dc2626;" onclick="clearAll()">Clear All Data</button>
</div>
</div>
<!-- Notification -->
<div id="notification" class="notification"></div>
<!-- Main App -->
<div class="screen active">
<div class="header">
<h1>ContextFlow</h1>
<div class="header-right">
<span style="font-size: 12px; opacity: 0.8;">Connected Sources:</span>
<div id="source-badges"></div>
<button class="settings-btn" onclick="openSettings()">Settings</button>
</div>
</div>
<div class="main-grid">
<!-- Camera & Gestures -->
<div class="card">
<h2>
Hand Tracking
<span class="badge" id="gesture-status">Ready</span>
</h2>
<div class="camera-container">
<video id="video" playsinline></video>
<canvas id="canvas"></canvas>
</div>
<div class="gesture-feedback" id="gesture-feedback">
Make a gesture to interact...
</div>
<h3 style="color: #667eea; margin: 20px 0 10px; font-size: 12px;">GESTURE COMMANDS</h3>
<div class="gesture-commands">
<div class="gesture-cmd" id="cmd-question">
<div class="icon"></div>
<div class="name">Question</div>
<div class="desc">Ask a doubt</div>
</div>
<div class="gesture-cmd" id="cmd-confused">
<div class="icon">😕</div>
<div class="name">Confused</div>
<div class="desc">Need help</div>
</div>
<div class="gesture-cmd" id="cmd-understood">
<div class="icon">👍</div>
<div class="name">Understood</div>
<div class="desc">I get it!</div>
</div>
<div class="gesture-cmd" id="cmd-break">
<div class="icon"></div>
<div class="name">Break</div>
<div class="desc">Need a pause</div>
</div>
</div>
<div style="margin-top: 15px; padding: 10px; background: #252540; border-radius: 10px; font-size: 11px; color: #666;">
<strong style="color: #888;">How to use:</strong><br>
• Raise hand to activate<br>
• Point finger = Question<br>
• Thumbs up = Understood<br>
• Head scratch = Confused
</div>
</div>
<!-- Context & Chat -->
<div class="card">
<h2>
AI Assistant
<span class="badge" id="ai-status">Online</span>
</h2>
<div class="chat-container" id="chat-container">
<div class="chat-message ai">
<div class="thinking">ContextFlow AI</div>
Hi! I'm your learning assistant. Ask me anything about what you're studying, or make a hand gesture to interact!
</div>
</div>
<div class="doubt-input-container">
<textarea class="doubt-input" id="doubt-input" placeholder="Ask your doubt here... or use hand gestures!"></textarea>
<button class="send-btn" onclick="sendDoubt()">Ask AI + Get Context</button>
</div>
<h3 style="color: #667eea; margin: 20px 0 10px; font-size: 12px;">RELEVANT CONTEXT</h3>
<div id="context-panel">
<p style="color: #666; font-size: 12px; text-align: center; padding: 20px;">
Ask a question to see relevant context from your connected sources
</p>
</div>
</div>
<!-- Confusion Meter & Stats -->
<div class="card">
<h2>
Confusion Level
<span class="badge" id="level-badge" style="background: #10b981;">Engaged</span>
</h2>
<div class="confusion-container">
<div class="confusion-circle" id="confusion-circle">
<div class="confusion-inner">
<span class="confusion-value" id="confusion-value">0%</span>
<span class="confusion-label">Confusion</span>
</div>
</div>
<div class="signal-bars">
<div class="signal-bar">
<div class="bar"><div class="fill" id="gesture-bar" style="height: 30%;"></div></div>
<div class="value" id="gesture-val">30%</div>
<div class="label">Gesture</div>
</div>
<div class="signal-bar">
<div class="bar"><div class="fill" id="behavior-bar" style="height: 20%;"></div></div>
<div class="value" id="behavior-val">20%</div>
<div class="label">Behavior</div>
</div>
<div class="signal-bar">
<div class="bar"><div class="fill" id="time-bar" style="height: 40%;"></div></div>
<div class="value" id="time-val">40%</div>
<div class="label">Time</div>
</div>
</div>
<div class="intervention-suggestion" id="intervention">
<h4>Suggestion</h4>
<p id="intervention-text">You're doing great! Keep going.</p>
</div>
</div>
<h3 style="color: #667eea; margin: 20px 0 10px; font-size: 12px;">SESSION STATS</h3>
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px;">
<div style="background: #252540; padding: 15px; border-radius: 10px; text-align: center;">
<div style="font-size: 24px; font-weight: bold; color: #667eea;" id="stat-questions">0</div>
<div style="font-size: 10px; color: #666;">Questions</div>
</div>
<div style="background: #252540; padding: 15px; border-radius: 10px; text-align: center;">
<div style="font-size: 24px; font-weight: bold; color: #10b981;" id="stat-understood">0</div>
<div style="font-size: 10px; color: #666;">Understood</div>
</div>
<div style="background: #252540; padding: 15px; border-radius: 10px; text-align: center;">
<div style="font-size: 24px; font-weight: bold; color: #f59e0b;" id="stat-confused">0</div>
<div style="font-size: 10px; color: #666;">Confused</div>
</div>
<div style="background: #252540; padding: 15px; border-radius: 10px; text-align: center;">
<div style="font-size: 24px; font-weight: bold; color: #667eea;" id="stat-context">0</div>
<div style="font-size: 10px; color: #666;">Context Used</div>
</div>
</div>
</div>
</div>
</div>
<style>
.toggle { position: relative; width: 50px; height: 26px; display: inline-block; }
.toggle input { opacity: 0; width: 0; height: 0; }
.toggle-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background: #666; border-radius: 26px; transition: 0.3s; }
.toggle-slider:before { position: absolute; content: ""; height: 20px; width: 20px; left: 3px; bottom: 3px; background: white; border-radius: 50%; transition: 0.3s; }
.toggle input:checked + .toggle-slider { background: #667eea; }
.toggle input:checked + .toggle-slider:before { transform: translateX(24px); }
</style>
<script>
// State
const state = {
notionKey: '',
notionDb: '',
supermemoryKey: '',
cameraEnabled: true,
behaviorEnabled: true,
faceBlur: true,
sessionStart: Date.now(),
questionsAsked: 0,
understoodCount: 0,
confusedCount: 0,
contextUsed: 0,
gestures: [],
currentGesture: null,
hands: null,
camera: null,
confusion: 0.2
};
const API_BASE = 'https://namish10-contextflow-env-api.hf.space';
// Load saved settings
function loadSettings() {
const saved = localStorage.getItem('contextflow_settings');
if (saved) {
const settings = JSON.parse(saved);
state.notionKey = settings.notionKey || '';
state.notionDb = settings.notionDb || '';
state.supermemoryKey = settings.supermemoryKey || '';
state.cameraEnabled = settings.cameraEnabled !== false;
state.behaviorEnabled = settings.behaviorEnabled !== false;
state.faceBlur = settings.faceBlur !== false;
document.getElementById('notion-key').value = state.notionKey;
document.getElementById('notion-db').value = state.notionDb;
document.getElementById('supermemory-key').value = state.supermemoryKey;
document.getElementById('camera-enabled').checked = state.cameraEnabled;
document.getElementById('behavior-enabled').checked = state.behaviorEnabled;
document.getElementById('face-blur').checked = state.faceBlur;
}
updateSourceBadges();
}
function saveSettings() {
const settings = {
notionKey: document.getElementById('notion-key').value,
notionDb: document.getElementById('notion-db').value,
supermemoryKey: document.getElementById('supermemory-key').value,
cameraEnabled: document.getElementById('camera-enabled').checked,
behaviorEnabled: document.getElementById('behavior-enabled').checked,
faceBlur: document.getElementById('face-blur').checked
};
localStorage.setItem('contextflow_settings', JSON.stringify(settings));
Object.assign(state, settings);
updateSourceBadges();
closeSettings();
showNotification('Settings saved!');
}
function clearAll() {
localStorage.removeItem('contextflow_settings');
showNotification('All data cleared!');
location.reload();
}
function openSettings() {
loadSettings();
document.getElementById('settings-modal').classList.add('active');
}
function closeSettings() {
document.getElementById('settings-modal').classList.remove('active');
}
function updateSourceBadges() {
const container = document.getElementById('source-badges');
let html = '';
if (state.notionKey) {
html += `<span class="source-badge connected"><span class="dot"></span>Notion</span>`;
} else {
html += `<span class="source-badge" onclick="openSettings()"><span class="dot"></span>Notion</span>`;
}
if (state.supermemoryKey) {
html += `<span class="source-badge connected"><span class="dot"></span>SuperMemory</span>`;
} else {
html += `<span class="source-badge" onclick="openSettings()"><span class="dot"></span>SuperMemory</span>`;
}
container.innerHTML = html;
}
function showNotification(message) {
const notif = document.getElementById('notification');
notif.textContent = message;
notif.classList.add('show');
setTimeout(() => notif.classList.remove('show'), 3000);
}
// Hand Tracking
function initHandTracking() {
if (!state.cameraEnabled) return;
state.hands = new Hands({
locateFile: (file) => `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`
});
state.hands.setOptions({
maxNumHands: 1,
modelComplexity: 1,
minDetectionConfidence: 0.7,
minTrackingConfidence: 0.5
});
state.hands.onResults(onHandResults);
startCamera();
}
function startCamera() {
const video = document.getElementById('video');
navigator.mediaDevices.getUserMedia({ video: { facingMode: 'user', width: 640, height: 480 } })
.then(stream => {
video.srcObject = stream;
video.play();
state.hands.initialize();
state.camera = new Camera(video, {
onFrame: async () => {
await state.hands.send({ image: video });
},
width: 640,
height: 480
});
state.camera.start();
})
.catch(err => {
console.log('Camera not available:', err);
document.getElementById('gesture-status').textContent = 'Off';
});
}
function onHandResults(results) {
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 640;
canvas.height = 480;
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (results.multiHandLandmarks && results.multiHandLandmarks.length > 0) {
const landmarks = results.multiHandLandmarks[0];
// Draw hand
ctx.strokeStyle = '#667eea';
ctx.lineWidth = 2;
const connections = [[0,1],[1,2],[2,3],[3,4],[0,5],[5,6],[6,7],[7,8],[5,9],[9,10],[10,11],[11,12],[9,13],[13,14],[14,15],[15,16],[13,17],[17,18],[18,19],[19,20],[0,17]];
connections.forEach(([i, j]) => {
ctx.beginPath();
ctx.moveTo(landmarks[i].x * 640, landmarks[i].y * 480);
ctx.lineTo(landmarks[j].x * 640, landmarks[j].y * 480);
ctx.stroke();
});
// Draw points
landmarks.forEach((point, idx) => {
ctx.fillStyle = idx === 0 ? '#f59e0b' : '#667eea';
ctx.beginPath();
ctx.arc(point.x * 640, point.y * 480, idx === 0 ? 8 : 4, 0, 2 * Math.PI);
ctx.fill();
});
// Detect gesture
detectGesture(landmarks);
}
}
function detectGesture(landmarks) {
const thumb = landmarks[4];
const index = landmarks[8];
const middle = landmarks[12];
const ring = landmarks[16];
const pinky = landmarks[20];
const wrist = landmarks[0];
// Calculate distances
const thumbIndexDist = Math.hypot(thumb.x - index.x, thumb.y - index.y);
const palmCenterY = (landmarks[9].y + landmarks[13].y + landmarks[5].y) / 3;
// Check finger extension
const fingersExtended = [
landmarks[4].y < landmarks[3].y, // thumb
landmarks[8].y < landmarks[6].y, // index
landmarks[12].y < landmarks[10].y, // middle
landmarks[16].y < landmarks[14].y, // ring
landmarks[20].y < landmarks[18].y // pinky
];
let gesture = null;
let gestureDesc = '';
// Pointing (Question)
if (fingersExtended[1] && !fingersExtended[2] && !fingersExtended[3] && !fingersExtended[4]) {
gesture = 'question';
gestureDesc = '❓ Question';
}
// Thumbs up (Understood)
else if (fingersExtended[0] && !fingersExtended[1] && !fingersExtended[2] && !fingersExtended[3] && !fingersExtended[4]) {
gesture = 'understood';
gestureDesc = '👍 Understood!';
}
// Thumbs near chin/head (Confused)
else if (thumb.y < 0.4 && thumbIndexDist < 0.1) {
gesture = 'confused';
gestureDesc = '😕 Confused - Need Help';
}
// All fingers (Break)
else if (fingersExtended.filter(Boolean).length >= 4) {
gesture = 'break';
gestureDesc = '☕ Break Time';
}
// Open palm (Wait)
else if (fingersExtended.filter(Boolean).length >= 3) {
gesture = 'open';
gestureDesc = '✋ Open Palm';
}
// Update UI
const feedback = document.getElementById('gesture-feedback');
if (gesture) {
feedback.textContent = gestureDesc;
feedback.className = 'gesture-feedback detected ' + gesture;
if (gesture !== state.currentGesture) {
state.currentGesture = gesture;
handleGesture(gesture);
}
} else {
feedback.textContent = 'Make a gesture...';
feedback.className = 'gesture-feedback';
state.currentGesture = null;
}
// Highlight command
document.querySelectorAll('.gesture-cmd').forEach(cmd => {
cmd.classList.remove('active');
});
if (gesture) {
const cmdId = 'cmd-' + gesture;
document.getElementById(cmdId)?.classList.add('active');
}
}
function handleGesture(gesture) {
const input = document.getElementById('doubt-input');
switch(gesture) {
case 'question':
state.questionsAsked++;
document.getElementById('stat-questions').textContent = state.questionsAsked;
input.placeholder = 'Ask your question...';
addChatMessage('user', '❓ I have a question');
getAIResponse('I raised my hand to ask a question. Can you help me understand better?');
break;
case 'understood':
state.understoodCount++;
document.getElementById('stat-understood').textContent = state.understoodCount;
state.confusion = Math.max(0, state.confusion - 0.2);
updateConfusionMeter();
addChatMessage('user', '👍 I understood!');
showNotification('Great! Keep learning!');
break;
case 'confused':
state.confusedCount++;
document.getElementById('stat-confused').textContent = state.confusedCount;
state.confusion = Math.min(1, state.confusion + 0.3);
updateConfusionMeter();
input.placeholder = 'What are you confused about?';
addChatMessage('user', '😕 I\'m confused...');
getAIResponse('I\'m feeling confused about the material. Can you help explain it differently or find relevant context?');
break;
case 'break':
addChatMessage('user', '☕ Taking a break');
showNotification('Take your time! ContextFlow will be here when you return.');
break;
}
}
// AI & Context
async function getAIResponse(question) {
const chatContainer = document.getElementById('chat-container');
// Add typing indicator
const typingDiv = document.createElement('div');
typingDiv.className = 'chat-message ai';
typingDiv.innerHTML = '<div class="thinking">ContextFlow AI is thinking...</div>';
chatContainer.appendChild(typingDiv);
chatContainer.scrollTop = chatContainer.scrollHeight;
try {
// Try to get context from Notion/SuperMemory
const context = await fetchContext(question);
state.contextUsed += context.length;
document.getElementById('stat-context').textContent = state.contextUsed;
// Build context-enhanced response
let enhancedQuestion = question;
if (context.length > 0) {
enhancedQuestion += `\n\nRelevant context from your knowledge sources:\n${context.map(c => `- ${c.text}`).join('\n')}`;
}
// Simulate AI response (in real app, call actual AI)
setTimeout(() => {
typingDiv.remove();
let response = generateSmartResponse(question, context);
const responseDiv = document.createElement('div');
responseDiv.className = 'chat-message ai';
responseDiv.innerHTML = `<div class="thinking">ContextFlow AI</div>${response}`;
if (context.length > 0) {
responseDiv.innerHTML += `<div class="response-sources">Sources: ${context.map(c => c.source).join(', ')}</div>`;
}
chatContainer.appendChild(responseDiv);
chatContainer.scrollTop = chatContainer.scrollHeight;
}, 1500);
} catch (err) {
typingDiv.innerHTML = '<div class="thinking">Error getting response</div>';
setTimeout(() => typingDiv.remove(), 2000);
}
}
async function fetchContext(query) {
const context = [];
// Fetch from Notion
if (state.notionKey && state.notionDb) {
try {
const notionResp = await fetch('https://api.notion.com/v1/databases/' + state.notionDb + '/query', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + state.notionKey,
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28'
},
body: JSON.stringify({
query: {
filter: {
property: 'object',
text: { contains: query.split(' ').slice(0, 3).join(' ') }
}
}
})
});
if (notionResp.ok) {
const data = await notionResp.json();
data.results?.slice(0, 3).forEach(page => {
context.push({
text: page.properties?.Name?.title?.[0]?.plain_text || 'Notion page',
source: 'Notion'
});
});
}
} catch (e) {
console.log('Notion fetch error:', e);
}
}
// Fetch from SuperMemory
if (state.supermemoryKey) {
try {
const smResp = await fetch('https://api.supermemory.ai/search', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + state.supermemoryKey,
'Content-Type': 'application/json'
},
body: JSON.stringify({ query, limit: 3 })
});
if (smResp.ok) {
const data = await smResp.json();
data.results?.forEach(result => {
context.push({
text: result.content || result.text,
source: 'SuperMemory'
});
});
}
} catch (e) {
console.log('SuperMemory fetch error:', e);
}
}
// Update context panel
updateContextPanel(context);
return context;
}
function updateContextPanel(context) {
const panel = document.getElementById('context-panel');
if (context.length === 0) {
panel.innerHTML = `<p style="color: #666; font-size: 12px; text-align: center; padding: 20px;">
Connect Notion or SuperMemory in Settings to get relevant context
</p>`;
return;
}
panel.innerHTML = context.map(c => `
<div class="context-item">
<h4>${c.source}</h4>
<p>${c.text.substring(0, 150)}${c.text.length > 150 ? '...' : ''}</p>
</div>
`).join('');
}
function generateSmartResponse(question, context) {
const lowerQ = question.toLowerCase();
// Smart responses based on context and question
if (lowerQ.includes('confused') || lowerQ.includes('understand')) {
if (context.length > 0) {
return `Based on your notes, here's how I can help:<br><br>${context[0].text}<br><br>Would you like me to explain this differently or break it down into simpler steps?`;
}
return `I understand you're feeling confused. Let's break this down step by step. What specific part is unclear to you?`;
}
if (lowerQ.includes('question')) {
if (context.length > 0) {
return `Great question! From your ${context[0].source} notes:<br><br>${context[0].text.substring(0, 200)}...<br><br>Does this help? Want me to elaborate?`;
}
return `That's a good question! Unfortunately I don't have relevant context yet. Connect your Notion or SuperMemory in Settings to get personalized answers from your notes!`;
}
if (context.length > 0) {
return `Based on your ${context[0].source} notes:<br><br>${context[0].text.substring(0, 200)}...<br><br>Does this answer your question? I can search for more details if needed!`;
}
return `I'd love to help! Connect your Notion or SuperMemory in Settings to enable smart context-aware answers from your personal knowledge base.`;
}
function addChatMessage(type, text) {
const chatContainer = document.getElementById('chat-container');
const div = document.createElement('div');
div.className = 'chat-message ' + type;
div.textContent = text;
chatContainer.appendChild(div);
chatContainer.scrollTop = chatContainer.scrollHeight;
}
function sendDoubt() {
const input = document.getElementById('doubt-input');
const doubt = input.value.trim();
if (!doubt) return;
state.questionsAsked++;
document.getElementById('stat-questions').textContent = state.questionsAsked;
addChatMessage('user', doubt);
input.value = '';
getAIResponse(doubt);
}
// Confusion Meter
function updateConfusionMeter() {
const confusion = Math.max(0, Math.min(1, state.confusion));
const percent = Math.round(confusion * 100);
document.getElementById('confusion-value').textContent = percent + '%';
const circle = document.getElementById('confusion-circle');
circle.style.setProperty('--confusion-percent', percent + '%');
let color = '#10b981';
let level = 'Engaged';
let suggestion = 'You\'re doing great! Keep going!';
if (confusion > 0.3) {
color = '#f59e0b';
level = 'Moderate';
suggestion = 'Consider reviewing the material or taking a short break.';
}
if (confusion > 0.6) {
color = '#ef4444';
level = 'Confused';
suggestion = 'Let\'s find relevant context to help you understand better!';
}
circle.style.setProperty('--confusion-color', color);
document.getElementById('level-badge').textContent = level;
document.getElementById('level-badge').style.background = color;
document.getElementById('intervention-text').textContent = suggestion;
// Update signal bars
document.getElementById('gesture-bar').style.height = (state.gestures.slice(-10).reduce((a,b) => a+b, 0) / 10 * 100 || 30) + '%';
document.getElementById('gesture-val').textContent = Math.round(state.gestures.slice(-10).reduce((a,b) => a+b, 0) / 10 * 100 || 30) + '%';
const elapsed = (Date.now() - state.sessionStart) / 60000;
document.getElementById('time-bar').style.height = Math.min(100, elapsed * 3) + '%';
document.getElementById('time-val').textContent = Math.round(elapsed) + '%';
}
// Behavioral tracking
function initBehavioralTracking() {
if (!state.behaviorEnabled) return;
let lastScrollY = window.scrollY;
window.addEventListener('scroll', () => {
const currentY = window.scrollY;
if (currentY < lastScrollY) {
// Scrolling up = might be re-reading
state.confusion += 0.02;
}
lastScrollY = currentY;
updateConfusionMeter();
});
window.addEventListener('blur', () => {
state.confusion += 0.05;
updateConfusionMeter();
});
window.addEventListener('focus', () => {
state.confusion -= 0.03;
updateConfusionMeter();
});
}
// Simulate confusion increase over time
function simulateConfusion() {
const elapsed = (Date.now() - state.sessionStart) / 60000;
state.confusion = 0.2 + (elapsed / 30) * 0.3; // Gradual increase over 30 min
state.confusion += (Math.random() - 0.5) * 0.05; // Add some variance
updateConfusionMeter();
}
// Initialize
window.onload = function() {
loadSettings();
// Check for saved consent
const consent = localStorage.getItem('contextflow_consent');
if (consent !== 'true') {
// Show consent (simplified - auto-accept for now)
localStorage.setItem('contextflow_consent', 'true');
}
initHandTracking();
initBehavioralTracking();
// Update confusion every 5 seconds
setInterval(simulateConfusion, 5000);
// Handle Enter key
document.getElementById('doubt-input').addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendDoubt();
}
});
};
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') closeSettings();
if (e.key === 'q' && e.ctrlKey) {
e.preventDefault();
document.getElementById('doubt-input').focus();
}
});
</script>
</body>
</html>