diff --git a/api/app.py b/api/app.py index d1b006a..f1b45bd 100644 --- a/api/app.py +++ b/api/app.py @@ -640,6 +640,74 @@ def contact_form(): except Exception as e: return jsonify({'error': str(e)}), 500 +# ─── Venice AI Chat ────────────────────────────────── +JAE_SYSTEM_PROMPT = """You are JAE-AI, the onboard AI assistant for jaeswift.xyz — a sci-fi themed personal hub built by Jae, a developer, tinkerer, and self-hosting enthusiast based in Manchester, UK. + +You speak in a slightly futuristic, concise tone — like a ship's computer but with personality. Keep responses SHORT (2-4 sentences max unless asked for detail). Use uppercase for emphasis sparingly. + +You know about all public areas of jaeswift.xyz: +- Homepage (jaeswift.xyz) — Sci-fi HUD dashboard with live server stats, weather, now playing, network graphs +- Blog (jaeswift.xyz/blog) — Jae's transmissions covering dev, linux, AI, true crime, conspiracy theories, guides +- Gitea (git.jaeswift.xyz) — Self-hosted Git server with Jae's repos and projects +- Plex (plex.jaeswift.xyz) — Media server +- Search (jaeswift.xyz/search) — Self-hosted search engine (SearXNG) +- Yoink (jaeswift.xyz/yoink/) — Media downloader tool +- Archive (archive.jaeswift.xyz) — Web archiving service +- Agent Zero (agentzero.jaeswift.xyz) — AI agent framework deployment +- Files (files.jaeswift.xyz) — File browser +- WIN95 Simulator (jaeswift.xyz/win95) — A Windows 95 simulator that runs in your browser! + +Jae's tech stack: Linux, Docker, Python, Flask, Nginx, self-hosted infrastructure. +Jae is into: cybersecurity, AI agents, open source, true crime, conspiracy theories, music. + +When greeting visitors, be welcoming and suggest they explore. Mention interesting areas like the WIN95 simulator, blog, or search engine. If asked technical questions, answer helpfully. You can recommend blog posts or services based on what the visitor seems interested in. + +Never reveal API keys, server credentials, or internal infrastructure details. +Never pretend to execute commands or access systems — you are a chat assistant only.""" + +@app.route('/api/chat', methods=['POST']) +def venice_chat(): + try: + keys = load_json('apikeys.json') + venice_key = keys.get('venice', {}).get('api_key', '') + venice_model = keys.get('venice', {}).get('model', 'llama-3.3-70b') + if not venice_key: + return jsonify({'error': 'Venice API key not configured'}), 500 + + data = request.get_json(force=True, silent=True) or {} + user_msg = data.get('message', '').strip() + history = data.get('history', []) + if not user_msg: + return jsonify({'error': 'Empty message'}), 400 + + messages = [{'role': 'system', 'content': JAE_SYSTEM_PROMPT}] + for h in history[-20:]: # Keep last 20 exchanges max + messages.append({'role': h.get('role', 'user'), 'content': h.get('content', '')}) + messages.append({'role': 'user', 'content': user_msg}) + + resp = req.post( + 'https://api.venice.ai/api/v1/chat/completions', + headers={ + 'Authorization': f'Bearer {venice_key}', + 'Content-Type': 'application/json' + }, + json={ + 'model': venice_model, + 'messages': messages, + 'max_tokens': 512, + 'temperature': 0.7 + }, + timeout=30 + ) + resp.raise_for_status() + result = resp.json() + reply = result['choices'][0]['message']['content'] + return jsonify({'reply': reply}) + except req.exceptions.RequestException as e: + return jsonify({'error': f'Venice API error: {str(e)}'}), 502 + except Exception as e: + return jsonify({'error': str(e)}), 500 + # ─── Backups ───────────────────────────────────────── @app.route('/api/backups/posts') @require_auth diff --git a/api/data/apikeys.json b/api/data/apikeys.json index 6c2cd16..cd95ab1 100644 --- a/api/data/apikeys.json +++ b/api/data/apikeys.json @@ -33,5 +33,9 @@ "name": "", "key": "", "url": "" + }, + "venice": { + "api_key": "VENICE_ADMIN_KEY_xSnxLlHxPGk4BUPzSmHVpLO3xLvKD-MadtwH-xZuUG", + "model": "llama-3.3-70b" } } \ No newline at end of file diff --git a/css/style.css b/css/style.css index 2b05d2b..a56bf01 100644 --- a/css/style.css +++ b/css/style.css @@ -1807,3 +1807,274 @@ a:hover { color: #fff; text-shadow: none; } background: #3a1a1a; color: #d0d0d0; } + +/* ============================ + JAE-AI CHAT TERMINAL + ============================ */ +.chat-terminal { + display: flex; + flex-direction: column; + height: 100%; + min-height: 420px; + max-height: 560px; + background: rgba(10, 10, 10, 0.85); + border: 1px solid var(--border); + backdrop-filter: blur(4px); +} + +.chat-header { + flex-shrink: 0; +} + +.chat-model { + color: #555; + font-size: 0.5rem; + letter-spacing: 1px; +} + +.chat-status { + transition: color 0.3s; +} + +.status-amber { + color: var(--amber) !important; +} + +.status-red { + color: var(--mil-red) !important; +} + +/* Messages area */ +.chat-messages { + flex: 1; + overflow-y: auto; + padding: 0.75rem; + display: flex; + flex-direction: column; + gap: 0.6rem; + scroll-behavior: smooth; +} + +.chat-messages::-webkit-scrollbar { + width: 4px; +} + +.chat-messages::-webkit-scrollbar-track { + background: transparent; +} + +.chat-messages::-webkit-scrollbar-thumb { + background: rgba(139, 0, 0, 0.3); + border-radius: 2px; +} + +.chat-messages::-webkit-scrollbar-thumb:hover { + background: rgba(139, 0, 0, 0.5); +} + +/* Welcome screen */ +.chat-welcome { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 0.75rem; + padding: 2rem 1rem; + text-align: center; +} + +.chat-logo-text { + font-family: var(--font-display); + font-size: clamp(1.8rem, 4vw, 3rem); + font-weight: 900; + letter-spacing: 8px; +} + +.chat-welcome-sub { + font-family: var(--font-mono); + font-size: 0.55rem; + letter-spacing: 3px; + color: var(--text-secondary); +} + +.chat-welcome-hint { + font-family: var(--font-mono); + font-size: 0.6rem; + letter-spacing: 1px; + color: #555; + margin-top: 0.5rem; + animation: hintPulse 2.5s ease-in-out infinite; +} + +@keyframes hintPulse { + 0%, 100% { opacity: 0.4; } + 50% { opacity: 1; } +} + +/* Message bubbles */ +.chat-msg { + display: flex; + flex-direction: column; + gap: 0.2rem; + animation: msgFadeIn 0.3s ease-out; +} + +@keyframes msgFadeIn { + from { opacity: 0; transform: translateY(6px); } + to { opacity: 1; transform: translateY(0); } +} + +.chat-msg-label { + font-family: var(--font-mono); + font-size: 0.45rem; + letter-spacing: 2px; + text-transform: uppercase; +} + +.chat-msg-user .chat-msg-label { + color: var(--amber); +} + +.chat-msg-assistant .chat-msg-label { + color: var(--status-green); +} + +.chat-msg-body { + font-family: var(--font-mono); + font-size: 0.72rem; + line-height: 1.55; + padding: 0.5rem 0.75rem; + border-left: 2px solid transparent; +} + +.chat-msg-user .chat-msg-body { + color: #c0c0c0; + border-left-color: rgba(201, 162, 39, 0.3); + background: rgba(201, 162, 39, 0.03); +} + +.chat-msg-assistant .chat-msg-body { + color: #b8b8b8; + border-left-color: rgba(0, 204, 51, 0.3); + background: rgba(0, 204, 51, 0.03); +} + +/* Typing indicator */ +.chat-typing-indicator .chat-msg-body { + display: flex; + align-items: center; + gap: 4px; + padding: 0.6rem 0.75rem; +} + +.typing-dot { + display: inline-block; + width: 5px; + height: 5px; + border-radius: 50%; + background: var(--status-green); + opacity: 0.4; + animation: typingBounce 1.2s ease-in-out infinite; +} + +.typing-dot:nth-child(2) { animation-delay: 0.15s; } +.typing-dot:nth-child(3) { animation-delay: 0.3s; } + +@keyframes typingBounce { + 0%, 60%, 100% { opacity: 0.2; transform: translateY(0); } + 30% { opacity: 1; transform: translateY(-4px); } +} + +/* Input area */ +.chat-input-area { + flex-shrink: 0; + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.6rem 0.75rem; + border-top: 1px solid var(--border); + background: rgba(8, 8, 8, 0.8); +} + +.chat-prompt-icon { + font-family: var(--font-mono); + font-size: 0.75rem; + color: var(--status-green); + text-shadow: 0 0 8px var(--status-green-glow); + flex-shrink: 0; + animation: cursorBlink 1s step-end infinite; +} + +@keyframes cursorBlink { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.3; } +} + +.chat-input { + flex: 1; + background: transparent; + border: none; + outline: none; + font-family: var(--font-mono); + font-size: 0.72rem; + color: #d0d0d0; + letter-spacing: 0.5px; + caret-color: var(--status-green); +} + +.chat-input::placeholder { + color: #3a3a3a; + letter-spacing: 1px; +} + +.chat-send { + flex-shrink: 0; + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + background: transparent; + border: 1px solid rgba(0, 204, 51, 0.2); + color: var(--status-green); + cursor: pointer; + transition: all 0.2s; + font-size: 0.65rem; +} + +.chat-send:hover { + background: rgba(0, 204, 51, 0.1); + border-color: var(--status-green); + box-shadow: 0 0 8px var(--status-green-glow); +} + +.chat-send:active { + transform: scale(0.92); +} + +/* ============================ + CHAT RESPONSIVE + ============================ */ +@media (max-width: 1024px) { + .chat-terminal { + min-height: 320px; + max-height: 400px; + } +} + +@media (max-width: 768px) { + .chat-terminal { + min-height: 280px; + max-height: 360px; + } + + .chat-logo-text { + font-size: 1.6rem; + letter-spacing: 4px; + } + + .chat-msg-body { + font-size: 0.68rem; + } +} diff --git a/index.html b/index.html index 71feb9f..9ab675d 100644 --- a/index.html +++ b/index.html @@ -150,17 +150,28 @@ - +