uncloseai.com/public/languages/javascript/nodejs/uncloseai.js
russell@unturf.com 9d191f24d1 mandatory vault gate: encrypt all localStorage with password before chat
Vault password prompt is now the first thing shown when opening
uncloseai. No chat, no greeting, no data written until the user
creates or unlocks their vault. Each site has its own vault with
AES-256 encryption using password + device-specific salt.

- Add vault gate overlay blocking modal until password entered
- Remove plaintext conversation fallback from storage.js
- Route TTS mode, selected voice, language through vault
- Route custom API config reads through vault in models.js
- Add vault gate i18n strings (26 languages) explaining per-site
  encryption of chat history, API keys, and settings
- Add missing keys to migration (voice, TTS mode)
- Re-show vault gate on lock from settings
- Permacomputer headers across all source files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 15:38:54 -05:00

344 lines
10 KiB
JavaScript

// PUBLIC DOMAIN - NO LICENSE, NO WARRANTY
//
// This is free public domain software for the public good of a permacomputer
// hosted at permacomputer.com, an always-on computer by the people, for the
// people. One which is durable, easy to repair, and distributed like tap water
// for machine learning intelligence.
//
// The permacomputer is community-owned infrastructure optimized around
// four values:
//
// TRUTH - First principles, math & science, open source code
// FREEDOM - Voluntary partnerships, freedom from tyranny & corporate control
// HARMONY - Minimal waste, self-renewing systems with diverse connections
// LOVE - Be yourself without hurting others, cooperation through natural law
//
// This software contributes to that vision by making machine learning
// accessible to everyone through a free, open, embeddable chat interface.
// Code is seeds to sprout on any abandoned technology.
//
// Learn more: https://www.permacomputer.com
//
// Anyone is free to copy, modify, publish, use, compile, sell, or distribute
// this software, either in source code form or as a compiled binary, for any
// purpose, commercial or non-commercial, and by any means.
//
// NO WARRANTY. THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
//
// Copyright 2025-2026 TimeHexOn & foxhop & russell@unturf
// https://www.permacomputer.com
const https = require('https');
const fs = require('fs');
console.log('=== Node.js AI API Examples (Dynamic Model Discovery) ===\n');
function getJSON(url) {
return new Promise((resolve, reject) => {
const urlObj = new URL(url);
const options = {
hostname: urlObj.hostname,
port: 443,
path: urlObj.pathname,
method: 'GET',
timeout: 10000
};
const req = https.request(options, (res) => {
let body = '';
res.on('data', (chunk) => body += chunk);
res.on('end', () => {
if (res.statusCode === 200) {
resolve(JSON.parse(body));
} else {
reject(new Error(`HTTP ${res.statusCode}`));
}
});
});
req.on('error', reject);
req.on('timeout', () => {
req.destroy();
reject(new Error('Request timeout'));
});
req.end();
});
}
function postJSON(url, data) {
return new Promise((resolve, reject) => {
const jsonData = JSON.stringify(data);
const urlObj = new URL(url);
const options = {
hostname: urlObj.hostname,
port: 443,
path: urlObj.pathname,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': jsonData.length
},
timeout: 30000
};
const req = https.request(options, (res) => {
let body = '';
res.on('data', (chunk) => body += chunk);
res.on('end', () => {
if (res.headers['content-type']?.includes('application/json')) {
resolve(JSON.parse(body));
} else {
resolve(body);
}
});
});
req.on('error', reject);
req.on('timeout', () => {
req.destroy();
reject(new Error('Request timeout'));
});
req.write(jsonData);
req.end();
});
}
function postJSONBinary(url, data) {
return new Promise((resolve, reject) => {
const jsonData = JSON.stringify(data);
const urlObj = new URL(url);
const options = {
hostname: urlObj.hostname,
port: 443,
path: urlObj.pathname,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': jsonData.length
},
timeout: 30000
};
const req = https.request(options, (res) => {
const chunks = [];
res.on('data', (chunk) => chunks.push(chunk));
res.on('end', () => resolve(Buffer.concat(chunks)));
});
req.on('error', reject);
req.on('timeout', () => {
req.destroy();
reject(new Error('Request timeout'));
});
req.write(jsonData);
req.end();
});
}
async function discoverModels() {
console.log('Discovering models from environment variables...');
const models = [];
const ttsEndpoints = [];
// Discover chat/code models from MODEL_ENDPOINT_1..9999
for (let i = 1; i < 10000; i++) {
const endpoint = process.env[`MODEL_ENDPOINT_${i}`];
if (!endpoint) break;
console.log(`Discovering from: ${endpoint}`);
try {
const response = await getJSON(`${endpoint}/models`);
for (const model of response.data || []) {
// Filter out modelperm-* and chatcmpl-* entries
if (model.id.startsWith('modelperm-') || model.id.startsWith('chatcmpl-')) {
continue;
}
models.push({
id: model.id,
endpoint: endpoint,
max_tokens: model.max_model_len || 8192
});
}
} catch (error) {
console.log(` Error: ${error.message}`);
}
}
// Discover TTS endpoints from TTS_ENDPOINT_1..9999
for (let i = 1; i < 10000; i++) {
const endpoint = process.env[`TTS_ENDPOINT_${i}`];
if (!endpoint) break;
console.log(`Discovering TTS from: ${endpoint}`);
ttsEndpoints.push(endpoint);
}
console.log('');
console.log(`Discovered ${models.length} model(s) and ${ttsEndpoints.length} TTS endpoint(s)`);
console.log('');
return { models, ttsEndpoints };
}
async function chatExample(model, systemMsg, userMsg, maxTokens = 100) {
console.log('\n=== Non-Streaming Chat ===');
console.log(`Model: ${model.id}`);
console.log(`Endpoint: ${model.endpoint}`);
console.log('');
const request = {
model: model.id,
messages: [
{ role: 'system', content: systemMsg },
{ role: 'user', content: userMsg }
],
max_tokens: maxTokens
};
try {
const response = await postJSON(`${model.endpoint}/chat/completions`, request);
console.log('Response:');
console.log(response.choices[0].message.content);
} catch (error) {
console.log('Error:', error.message);
}
}
async function chatStreamExample(model, systemMsg, userMsg, maxTokens = 500) {
console.log('\n=== Streaming Chat ===');
console.log(`Model: ${model.id}`);
console.log(`Endpoint: ${model.endpoint}`);
console.log('');
const request = {
model: model.id,
messages: [
{ role: 'system', content: systemMsg },
{ role: 'user', content: userMsg }
],
max_tokens: maxTokens,
stream: true
};
return new Promise((resolve, reject) => {
const jsonData = JSON.stringify(request);
const urlObj = new URL(`${model.endpoint}/chat/completions`);
const options = {
hostname: urlObj.hostname,
port: 443,
path: urlObj.pathname,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': jsonData.length
},
timeout: 60000
};
const req = https.request(options, (res) => {
process.stdout.write('Response: ');
let buffer = '';
res.on('data', (chunk) => {
buffer += chunk.toString();
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6).trim();
if (data === '[DONE]') {
process.stdout.write('\n');
resolve();
return;
}
try {
const parsed = JSON.parse(data);
if (parsed.choices?.[0]?.delta?.content) {
process.stdout.write(parsed.choices[0].delta.content);
}
} catch {
// Ignore parse errors
}
}
}
});
res.on('end', () => {
process.stdout.write('\n');
resolve();
});
});
req.on('error', reject);
req.on('timeout', () => {
req.destroy();
reject(new Error('Request timeout'));
});
req.write(jsonData);
req.end();
});
}
async function ttsExample(endpoint) {
console.log('');
console.log('---');
console.log('');
console.log('=== TTS Speech Generation Example ===');
console.log(`Endpoint: ${endpoint}`);
console.log('');
const request = {
model: 'tts-1',
voice: 'alloy',
input: 'Hello from Node.js! This is a text to speech example with dynamic model discovery and streaming support.'
};
try {
const audioData = await postJSONBinary(`${endpoint}/audio/speech`, request);
fs.writeFileSync('speech.mp3', audioData);
console.log(`✓ Speech file created: speech.mp3 (${audioData.length} bytes)`);
} catch (error) {
console.log('✗ Error:', error.message);
}
}
async function main() {
const { models, ttsEndpoints } = await discoverModels();
if (models.length === 0) {
console.log('ERROR: No models discovered. Set environment variables:');
console.log(' MODEL_ENDPOINT_1, MODEL_ENDPOINT_2, etc.');
process.exit(1);
}
await chatExample(models[0], 'You are a helpful AI assistant.', 'Explain quantum computing in one sentence.');
const modelIdx = models.length > 1 ? 1 : 0;
await chatStreamExample(models[modelIdx], 'You are a coding assistant.', 'Write a JavaScript function to check if a number is prime', 200);
if (ttsEndpoints.length > 0) {
await ttsExample(ttsEndpoints[0]);
} else {
console.log('\n=== TTS Speech Generation Example ===');
console.log('ERROR: No TTS endpoints available. Set TTS_ENDPOINT_1');
}
console.log('');
console.log('=== Examples Complete ===');
}
main().catch(console.error);