uncloseai.com/public/uncloseai.js

255 lines
9.6 KiB
JavaScript

// This is free 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, & 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 freely distributed
// FREEDOM Voluntary partnerships, freedom from tyranny & corporate control
// HARMONY Minimal waste, self-renewing systems with diverse thriving 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.
/*
* uncloseai.js - Modular AI Integration Library
*
* Main entry point that imports all modules and exports the public API
* This maintains backward compatibility while enabling better code organization
*/
import * as Chat from "./src/chat.js";
// Internal modules
import * as Config from "./src/config.js";
import * as Content from "./src/content.js";
import * as FileUpload from "./src/file-upload.js";
import * as Models from "./src/models.js";
import * as PageReader from "./src/page-reader.js";
import * as Storage from "./src/storage.js";
import * as Translation from "./src/translation.js";
import * as TTS from "./src/tts.js";
import * as UI from "./src/ui.js";
import { UncloseVault } from "./src/vault.js";
// Duplicate load guard: if a page already includes uncloseai.js natively,
// the extension should not re-initialize. Module exports remain available,
// but window globals, event listeners, and DOM side effects only run once.
const __alreadyLoaded = window.__UNCLOSEAI_LOADED__ === true;
window.__UNCLOSEAI_LOADED__ = true;
// -------------------------
// Vault Support
// -------------------------
// Load CryptoJS dynamically for vault encryption
async function ensureCryptoJS() {
if (typeof CryptoJS !== 'undefined') return true;
return new Promise((resolve) => {
const script = document.createElement('script');
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js';
script.onload = () => resolve(true);
script.onerror = () => {
console.warn('uncloseai.js: Failed to load CryptoJS - vault features disabled');
resolve(false);
};
document.head.appendChild(script);
});
}
// Export vault for external use
export { UncloseVault };
// -------------------------
// Public API - Main Functions
// -------------------------
// Chat functionality
export const sendMessage = Chat.sendMessage;
export const handleUserInput = Chat.handleUserInput;
// TTS functionality
export const speakText = TTS.speakText;
export const processContentWithHermes = TTS.processContentWithHermes;
export const extractSpokenTokens = TTS.extractSpokenTokens;
export const generateTitleForTTS = TTS.generateTitleForTTS;
// File upload
export const uploadFile = FileUpload.uploadFile;
export const integrateMegafarceResponse = FileUpload.integrateMegafarceResponse;
export const showProgressIndicator = FileUpload.showProgressIndicator;
export const hideProgressIndicator = FileUpload.hideProgressIndicator;
// Page reading
export const readPageWithHermes = PageReader.readPageWithHermes;
export const extractWebpageContent = Content.extractWebpageContent;
// Model management
export const getSelectedModel = Models.getSelectedModel;
export const getSelectedModelEndpoint = Models.getSelectedModelEndpoint;
export const fetchModelsFromEndpoints = Models.fetchModelsFromEndpoints;
export const createModelSelectionDropdown = Models.createModelSelectionDropdown;
export const addRefreshModelsButton = Models.addRefreshModelsButton;
// Storage
export const saveConversationHistory = Storage.saveConversationHistory;
export const loadConversationHistory = Storage.loadConversationHistory;
export const clearConversationHistory = Storage.clearConversationHistory;
export const getPageSpecificKey = Storage.getPageSpecificKey;
// UI creation
export const createFloatingAIButton = UI.createFloatingAIButton;
export const initializeUncloseaiElements = UI.initializeUncloseaiElements;
export const createFullInterface = UI.createFullInterface;
export const createCustomInterface = UI.createCustomInterface;
export const toggleUncloseaiEmbeddedModal = UI.toggleUncloseaiEmbeddedModal;
export const openUncloseaiEmbeddedModal = UI.openUncloseaiEmbeddedModal;
export const openTTSModal = UI.openTTSModal;
export const openTranslateModal = UI.openTranslateModal;
// Translation functionality
export const translateText = Translation.translateText;
export const translateCurrentPage = Translation.translateCurrentPage;
export const translateHTML = Translation.translateHTML;
// -------------------------
// Configuration and State
// -------------------------
// Export configuration constants
export const TTS_API_URL = Config.TTS_API_URL;
export const MEGAPARCE_API_URL = Config.MEGAPARCE_API_URL;
export const API_KEY = Config.API_KEY;
export const MODEL = Config.MODEL;
export const VLLM_ENDPOINTS = Config.VLLM_ENDPOINTS;
// Export model registry
export const modelRegistry = Models.modelRegistry;
// Export chat history (for external access)
export { chatHistory } from "./src/chat.js";
// -------------------------
// Initialization
// -------------------------
// -------------------------
// Global Scope Exports (for backward compatibility)
// Side effects only run once: if the page already loaded uncloseai.js,
// the extension copy skips initialization but module exports still work.
// -------------------------
if (!__alreadyLoaded) {
// Export ALL functions under window.uncloseai namespace to avoid clobbering
// host page globals (e.g. a page's own sendMessage, speakText, etc.)
window.uncloseai = {
handleUserInput: Chat.handleUserInput,
readPageWithHermes: PageReader.readPageWithHermes,
sendMessage: Chat.sendMessage,
speakText: TTS.speakText,
uploadFile: FileUpload.uploadFile,
openTTSModal: UI.openTTSModal,
openTranslateModal: UI.openTranslateModal,
toggleUncloseaiEmbeddedModal: async () =>
await UI.toggleUncloseaiEmbeddedModal(),
extractWebpageContent: Content.extractWebpageContent,
getSelectedModel: Models.getSelectedModel,
getSelectedModelEndpoint: Models.getSelectedModelEndpoint,
showProgressIndicator: FileUpload.showProgressIndicator,
hideProgressIndicator: FileUpload.hideProgressIndicator,
handleTTSFromElement: UI.handleTTSFromElement,
handleUploadFromElement: UI.handleUploadFromElement,
handleSmartTranslate: UI.handleSmartTranslate,
UncloseVault: UncloseVault,
};
// Only set uniquely-named globals that internal extension modules depend on.
// NEVER set generic names (sendMessage, speakText, handleUserInput, uploadFile,
// getSelectedModel, etc.) directly on window — they clobber host page functions.
window.UncloseVault = UncloseVault;
window.toggleUncloseaiEmbeddedModal = async () =>
await UI.toggleUncloseaiEmbeddedModal();
window.openTTSModal = UI.openTTSModal;
window.openTranslateModal = UI.openTranslateModal;
window.handleTTSFromElement = UI.handleTTSFromElement;
window.handleUploadFromElement = UI.handleUploadFromElement;
window.handleSmartTranslate = UI.handleSmartTranslate;
// Initialize on page load
window.addEventListener("load", async () => {
console.log("uncloseai.js: window.onload event fired.");
// Load CryptoJS and initialize vault early
await ensureCryptoJS();
if (UncloseVault.isAvailable()) {
const restored = UncloseVault.init();
console.log(
"uncloseai.js: Vault initialized, session restored:",
restored,
);
}
// Check skip init flag first
if (window.UNCLOSEAI_SKIP_INIT === true) {
console.log(
"uncloseai.js: Skipping full initialization as requested by flag.",
);
console.log(
"uncloseai.js: Creating floating button only for preview windows.",
);
// Still create floating button for preview windows, but skip other initialization
const SHOW_FLOATING_BUTTON =
window.UNCLOSEAI_FLOATING_BUTTON !== false;
if (SHOW_FLOATING_BUTTON) {
UI.createFloatingAIButton();
}
return;
}
UI.initializeSystem();
});
// Export helper functions for class-based integrations
window.uncloseai.handleCustomChat = async (button) => {
const container = button.parentElement;
const input = container.querySelector("[data-chat-input]");
const chatBox = container.querySelector("[data-chat-box]");
const message = input.value.trim();
if (!message) return;
// Add user message
chatBox.innerHTML += `<div style="margin: 2px 0; padding: 2px; background: #e3f2fd; border-radius: 2px; font-size: 0.9em;"><strong>You:</strong> ${message}</div>`;
input.value = "";
// Add AI thinking indicator
const thinkingDiv = document.createElement("div");
thinkingDiv.style.cssText =
"margin: 2px 0; padding: 2px; background: #f5f5f5; border-radius: 2px; font-size: 0.9em;";
thinkingDiv.innerHTML = "<strong>AI:</strong> <em>thinking...</em>";
chatBox.appendChild(thinkingDiv);
chatBox.scrollTop = chatBox.scrollHeight;
try {
let response = "";
for await (const chunk of Chat.sendMessage(message)) {
response += chunk;
thinkingDiv.innerHTML = `<strong>AI:</strong> ${response}`;
chatBox.scrollTop = chatBox.scrollHeight;
}
} catch (error) {
thinkingDiv.innerHTML = `<strong>Error:</strong> ${error.message}`;
}
};
console.log("uncloseai.js: Modular version loaded successfully");
} else {
console.log(
"uncloseai.js: page already has uncloseai loaded, skipping duplicate initialization",
);
}