255 lines
9.6 KiB
JavaScript
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",
|
|
);
|
|
}
|