diff --git a/admin.html b/admin.html index 538da88..52dbff7 100644 --- a/admin.html +++ b/admin.html @@ -84,6 +84,13 @@ Links + + 🌍 Globe + + + 🤖 Chat AI + + @@ -1036,6 +1043,144 @@
+ + + +
+

🌍 GLOBE CONFIGURATION

+ + +

◈ SERVER LOCATION

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +

⚙ GLOBE SETTINGS

+
+
+ + +
+
+ + +
+
+
+
+ +
+ + +
+
+
+ + +
+
+ + +

⟐ ARC CITIES

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + + + + + + + + + + + +
CITYLATLNGACTIONS
+
+ +
+ +
+
+ + + + +
+

🤖 CHAT AI CONFIGURATION

+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+ +
+
+ diff --git a/api/app.py b/api/app.py index 4c34b43..4b55731 100644 --- a/api/app.py +++ b/api/app.py @@ -737,6 +737,36 @@ def venice_chat(): except Exception as e: return jsonify({'error': str(e)}), 500 +# ─── Globe Config ──────────────────────────────────── +@app.route('/api/globe') +def get_globe(): + return jsonify(load_json('globe.json')) + +@app.route('/api/globe', methods=['POST']) +@require_auth +def save_globe(): + try: + d = request.get_json(force=True) + save_json('globe.json', d) + return jsonify(d) + except Exception as e: + return jsonify({'error': str(e)}), 500 + +# ─── Chat AI Config ────────────────────────────────── +@app.route('/api/chat-config') +def get_chat_config(): + return jsonify(load_json('chat_config.json')) + +@app.route('/api/chat-config', methods=['POST']) +@require_auth +def save_chat_config(): + try: + d = request.get_json(force=True) + save_json('chat_config.json', d) + return jsonify(d) + except Exception as e: + return jsonify({'error': str(e)}), 500 + # ─── Backups ───────────────────────────────────────── @app.route('/api/backups/posts') @require_auth diff --git a/api/data/chat_config.json b/api/data/chat_config.json new file mode 100644 index 0000000..41a1c0f --- /dev/null +++ b/api/data/chat_config.json @@ -0,0 +1,8 @@ +{ + "display_name": "JAE-AI", + "model": "venice-uncensored-1-2", + "system_prompt_summary": "AI assistant for jaeswift.xyz, knows all site sections and services", + "max_history": 20, + "auto_greeting": true, + "header_tag": "VENICE-UNCENSORED" +} diff --git a/api/data/globe.json b/api/data/globe.json new file mode 100644 index 0000000..bd78fe2 --- /dev/null +++ b/api/data/globe.json @@ -0,0 +1,20 @@ +{ + "server_lat": 53.4808, + "server_lng": -2.2426, + "server_label": "MANCHESTER", + "rotation_speed": 0.3, + "hex_polygon_opacity": 0.55, + "hex_polygon_color": "rgba(0, 220, 50, 0.55)", + "atmosphere_color": "#00cc33", + "atmosphere_altitude": 0.2, + "arc_cities": [ + {"name": "New York", "lat": 40.7128, "lng": -74.006}, + {"name": "Tokyo", "lat": 35.6762, "lng": 139.6503}, + {"name": "Paris", "lat": 48.8566, "lng": 2.3522}, + {"name": "Sydney", "lat": -33.8688, "lng": 151.2093}, + {"name": "Berlin", "lat": 52.52, "lng": 13.405}, + {"name": "Mumbai", "lat": 19.076, "lng": 72.8777}, + {"name": "São Paulo", "lat": -23.5505, "lng": -46.6333}, + {"name": "Seoul", "lat": 37.5665, "lng": 126.978} + ] +} diff --git a/js/admin.js b/js/admin.js index 00cb8ce..18b8d6c 100644 --- a/js/admin.js +++ b/js/admin.js @@ -11,6 +11,8 @@ const AdminApp = { servicesData: [], navData: [], linksData: [], + globeData: null, + chatAIData: null, themeDefaults: { accent: '#d0d0d0', bg: '#111111', @@ -119,6 +121,31 @@ const AdminApp = { valFont.textContent = fontSlider.value; }); } + + // Globe slider sync + ['globeRotationSpeed', 'globeHexOpacity', 'globeAtmosphereAlt'].forEach(id => { + const slider = document.getElementById(id); + const valEl = document.getElementById(id + 'Val'); + if (slider && valEl) { + slider.addEventListener('input', () => { + valEl.textContent = slider.value; + }); + } + }); + + // Globe atmosphere colour sync + const globeAtmoPicker = document.getElementById('globeAtmosphereColor'); + const globeAtmoHex = document.getElementById('globeAtmosphereColorHex'); + if (globeAtmoPicker && globeAtmoHex) { + globeAtmoPicker.addEventListener('input', () => { + globeAtmoHex.value = globeAtmoPicker.value; + }); + globeAtmoHex.addEventListener('input', () => { + if (/^#[0-9a-fA-F]{6}$/.test(globeAtmoHex.value)) { + globeAtmoPicker.value = globeAtmoHex.value; + } + }); + } }, async login(e) { @@ -218,7 +245,9 @@ const AdminApp = { 'section-theme': 'Theme', 'section-seo': 'SEO', 'section-contact': 'Contact', - 'section-backups': 'Backups' + 'section-backups': 'Backups', + 'section-globe': 'Globe', + 'section-chatai': 'Chat AI' }; const topTitle = document.getElementById('topbarTitle') || document.querySelector('.topbar-title'); if (topTitle) topTitle.textContent = titleMap[name] || name.replace('section-', '').toUpperCase(); @@ -237,6 +266,8 @@ const AdminApp = { case 'section-theme': this.loadTheme(); break; case 'section-seo': this.loadSeo(); break; case 'section-contact': this.loadContactSettings(); break; + case 'section-globe': this.loadGlobe(); break; + case 'section-chatai': this.loadChatAI(); break; } // Close mobile sidebar @@ -1541,6 +1572,169 @@ const AdminApp = { } }, + /* ─────────────────── GLOBE ─────────────────── */ + + async loadGlobe() { + try { + const res = await fetch(this.API + '/globe', { headers: this.authHeaders() }); + if (!res.ok) throw new Error('Failed to load globe config'); + const data = await res.json(); + this.globeData = data; + + // Server location + this.setVal('globeServerLat', data.server_lat); + this.setVal('globeServerLng', data.server_lng); + this.setVal('globeServerLabel', data.server_label); + + // Globe settings + const rotSpeed = data.rotation_speed || 0.3; + this.setVal('globeRotationSpeed', rotSpeed); + const rotVal = document.getElementById('globeRotationSpeedVal'); + if (rotVal) rotVal.textContent = rotSpeed; + + const hexOp = data.hex_polygon_opacity || 0.55; + this.setVal('globeHexOpacity', hexOp); + const hexVal = document.getElementById('globeHexOpacityVal'); + if (hexVal) hexVal.textContent = hexOp; + + // Atmosphere + const atmoColor = data.atmosphere_color || '#00cc33'; + this.setVal('globeAtmosphereColor', atmoColor); + this.setVal('globeAtmosphereColorHex', atmoColor); + + const atmoAlt = data.atmosphere_altitude || 0.2; + this.setVal('globeAtmosphereAlt', atmoAlt); + const atmoVal = document.getElementById('globeAtmosphereAltVal'); + if (atmoVal) atmoVal.textContent = atmoAlt; + + // Arc cities table + this.renderArcCities(data.arc_cities || []); + } catch (err) { + console.error('Globe load error:', err); + this.notify('Failed to load globe config', 'error'); + } + }, + + renderArcCities(cities) { + const tbody = document.getElementById('arcCitiesBody'); + if (!tbody) return; + if (!cities || cities.length === 0) { + tbody.innerHTML = 'No arc cities configured'; + return; + } + tbody.innerHTML = cities.map((c, i) => { + return ` + ${this.escapeHtml(c.name || '')} + ${c.lat || 0} + ${c.lng || 0} + + `; + }).join(''); + }, + + addArcCity() { + const name = this.getVal('arcCityName'); + const lat = parseFloat(this.getVal('arcCityLat')); + const lng = parseFloat(this.getVal('arcCityLng')); + + if (!name || isNaN(lat) || isNaN(lng)) { + this.notify('City name, latitude and longitude are required', 'error'); + return; + } + + if (!this.globeData) this.globeData = {}; + if (!this.globeData.arc_cities) this.globeData.arc_cities = []; + + this.globeData.arc_cities.push({ name, lat, lng }); + this.renderArcCities(this.globeData.arc_cities); + + // Clear inputs + this.setVal('arcCityName', ''); + this.setVal('arcCityLat', ''); + this.setVal('arcCityLng', ''); + this.notify('City added — save to persist', 'info'); + }, + + removeArcCity(index) { + if (!this.globeData || !this.globeData.arc_cities) return; + if (!confirm('Remove this city?')) return; + this.globeData.arc_cities.splice(index, 1); + this.renderArcCities(this.globeData.arc_cities); + this.notify('City removed — save to persist', 'info'); + }, + + async saveGlobe() { + const payload = { + server_lat: parseFloat(this.getVal('globeServerLat')) || 53.4808, + server_lng: parseFloat(this.getVal('globeServerLng')) || -2.2426, + server_label: this.getVal('globeServerLabel') || 'MANCHESTER', + rotation_speed: parseFloat(this.getVal('globeRotationSpeed')) || 0.3, + hex_polygon_opacity: parseFloat(this.getVal('globeHexOpacity')) || 0.55, + hex_polygon_color: (this.globeData && this.globeData.hex_polygon_color) || 'rgba(0, 220, 50, 0.55)', + atmosphere_color: this.getVal('globeAtmosphereColorHex') || this.getVal('globeAtmosphereColor') || '#00cc33', + atmosphere_altitude: parseFloat(this.getVal('globeAtmosphereAlt')) || 0.2, + arc_cities: (this.globeData && this.globeData.arc_cities) || [] + }; + + try { + const res = await fetch(this.API + '/globe', { + method: 'POST', + headers: this.authHeaders(), + body: JSON.stringify(payload) + }); + if (!res.ok) throw new Error('Save failed'); + this.notify('Globe config saved'); + } catch (err) { + console.error('Save globe error:', err); + this.notify('Failed to save globe config', 'error'); + } + }, + + /* ─────────────────── CHAT AI ─────────────────── */ + + async loadChatAI() { + try { + const res = await fetch(this.API + '/chat-config', { headers: this.authHeaders() }); + if (!res.ok) throw new Error('Failed to load chat config'); + const data = await res.json(); + this.chatAIData = data; + + this.setVal('chatDisplayName', data.display_name); + this.setVal('chatModel', data.model); + this.setVal('chatHeaderTag', data.header_tag); + this.setVal('chatMaxHistory', data.max_history); + this.setChecked('chatAutoGreeting', data.auto_greeting !== false); + this.setVal('chatSystemPrompt', data.system_prompt_summary); + } catch (err) { + console.error('Chat AI load error:', err); + this.notify('Failed to load chat config', 'error'); + } + }, + + async saveChatAI() { + const payload = { + display_name: this.getVal('chatDisplayName') || 'JAE-AI', + model: this.getVal('chatModel') || 'venice-uncensored-1-2', + header_tag: this.getVal('chatHeaderTag') || 'VENICE-UNCENSORED', + max_history: parseInt(this.getVal('chatMaxHistory')) || 20, + auto_greeting: this.getChecked('chatAutoGreeting'), + system_prompt_summary: this.getVal('chatSystemPrompt') || '' + }; + + try { + const res = await fetch(this.API + '/chat-config', { + method: 'POST', + headers: this.authHeaders(), + body: JSON.stringify(payload) + }); + if (!res.ok) throw new Error('Save failed'); + this.notify('Chat AI config saved'); + } catch (err) { + console.error('Save chat AI error:', err); + this.notify('Failed to save chat config', 'error'); + } + }, + /* ─────────────────── UTILITIES ─────────────────── */ escapeHtml(str) { diff --git a/js/globe.js b/js/globe.js index 3a58219..d4ee1e5 100644 --- a/js/globe.js +++ b/js/globe.js @@ -29,7 +29,7 @@ .width(size) .height(size) .showAtmosphere(true) - .atmosphereColor('#00cc33') + .atmosphereColor('#00ff44') .atmosphereAltitude(0.2) // Hex polygons for land masses .hexPolygonsData([]) @@ -79,8 +79,8 @@ }); if (globeMesh && globeMesh.material) { globeMesh.material.color.setHex(0x0a1a0a); - globeMesh.material.emissive.setHex(0x006600); - globeMesh.material.emissiveIntensity = 0.6; + globeMesh.material.emissive.setHex(0x008800); + globeMesh.material.emissiveIntensity = 0.8; } }) (container); @@ -106,7 +106,10 @@ .hexPolygonResolution(3) .hexPolygonMargin(0.4) .hexPolygonColor(function () { - return 'rgba(0, 204, 51, 0.45)'; + return 'rgba(0, 220, 50, 0.55)'; + }) + .hexPolygonSideColor(function () { + return 'rgba(0, 255, 60, 0.6)'; }) .hexPolygonAltitude(0.005); }) @@ -140,7 +143,7 @@ startLng: city.lng, endLat: SERVER_LAT, endLng: SERVER_LNG, - color: ['rgba(0, 204, 51, 0.6)', 'rgba(0, 204, 51, 0.05)'] + color: ['rgba(0, 255, 60, 0.7)', 'rgba(0, 255, 60, 0.08)'] }); } return arcs;