UNPKG

tiny-ai-api

Version:

A customizable and extensible client api for managing conversations and AI interactions, currently supporting the **Google Gemini** API — with flexibility to support any similar AI APIs.

1,114 lines 70.6 kB
import objHash from 'object-hash'; import { EventEmitter } from 'events'; import { isJsonObject, objType } from 'tiny-essentials'; import { encode as encodeBase64 } from 'js-base64'; /** * @typedef {Object} SessionDataContent * @property {AIContentData[]} data * @property {string[]} ids * @property {{ data: Array<TokenCount>; [key: string]: * }} tokens * @property {{ data: Array<string>; [key: string]: * }} hash * @property {string|null} systemInstruction * @property {{ name: string; type: string; }[]} [customList] * @property {string|null} model * */ /** * @typedef {Record<string, any> & SessionDataContent} SessionData */ /** * @typedef {Object} AiModel * @property {any} _response * @property {number} index * @property {string|null} name * @property {string|null} id * @property {string|null} displayName * @property {string|null} version * @property {string|null} description * @property {number|null} inputTokenLimit * @property {number|null} outputTokenLimit * @property {number|null} temperature * @property {number|null} maxTemperature * @property {number|null} topP * @property {number|null} topK * @property {string[]} [supportedGenerationMethods] */ /** * @typedef {Object} AiCategory * @property {string} category * @property {string} displayName * @property {number} index * @property {AiModel[]} data */ /** * Tiny AI Server Communication API * ----------------------------- * This class is responsible for managing AI session data, including models, history, and content generation. * The script is designed to interact with the AI API, providing a complete structure for creating user interfaces (UI) or AI-powered chatbots. * It implements a session management system to help handle multiple different bots. * However, this script is not optimized for efficiently handling multiple AI instances simultaneously, which may be required for high-load scenarios or when running several AI instances at once. * * **Note**: This script does not automatically manage or track the token count for messages. Developers need to implement their own logic to monitor and manage token usage if necessary. * * Documentation written with the assistance of OpenAI's ChatGPT. */ class TinyAiInstance { /** * Important instance used to make event emitter. * @type {EventEmitter} */ #events = new EventEmitter(); /** * Important instance used to make system event emitter. * @type {EventEmitter} */ #sysEvents = new EventEmitter(); #sysEventsUsed = false; /** * Emits an event with optional arguments to all system emit. * @param {string | symbol} event - The name of the event to emit. * @param {...any} args - Arguments passed to event listeners. */ #emit(event, ...args) { this.#events.emit(event, ...args); if (this.#sysEventsUsed) this.#sysEvents.emit(event, ...args); } /** * Provides access to a secure internal EventEmitter for subclass use only. * * This method exposes a dedicated EventEmitter instance intended specifically for subclasses * that extend the main class. It prevents subclasses from accidentally or intentionally using * the primary class's public event system (`emit`), which could lead to unpredictable behavior * or interference in the base class's event flow. * * For security and consistency, this method is designed to be accessed only once. * Multiple accesses are blocked to avoid leaks or misuse of the internal event bus. * * @returns {EventEmitter} A special internal EventEmitter instance for subclass use. * @throws {Error} If the method is called more than once. */ getSysEvents() { if (this.#sysEventsUsed) throw new Error('Access denied: getSysEvents() can only be called once. ' + 'This restriction ensures subclass event isolation and prevents accidental interference ' + 'with the main class event emitter.'); this.#sysEventsUsed = true; return this.#sysEvents; } /** * @typedef {(...args: any[]) => void} ListenerCallback * A generic callback function used for event listeners. */ /** * Sets the maximum number of listeners for the internal event emitter. * * @param {number} max - The maximum number of listeners allowed. */ setMaxListeners(max) { this.#events.setMaxListeners(max); } /** * Emits an event with optional arguments. * @param {string | symbol} event - The name of the event to emit. * @param {...any} args - Arguments passed to event listeners. * @returns {boolean} `true` if the event had listeners, `false` otherwise. */ emit(event, ...args) { return this.#events.emit(event, ...args); } /** * Registers a listener for the specified event. * @param {string | symbol} event - The name of the event to listen for. * @param {ListenerCallback} listener - The callback function to invoke. * @returns {this} The current class instance (for chaining). */ on(event, listener) { this.#events.on(event, listener); return this; } /** * Registers a one-time listener for the specified event. * @param {string | symbol} event - The name of the event to listen for once. * @param {ListenerCallback} listener - The callback function to invoke. * @returns {this} The current class instance (for chaining). */ once(event, listener) { this.#events.once(event, listener); return this; } /** * Removes a listener from the specified event. * @param {string | symbol} event - The name of the event. * @param {ListenerCallback} listener - The listener to remove. * @returns {this} The current class instance (for chaining). */ off(event, listener) { this.#events.off(event, listener); return this; } /** * Alias for `on`. * @param {string | symbol} event - The name of the event. * @param {ListenerCallback} listener - The callback to register. * @returns {this} The current class instance (for chaining). */ addListener(event, listener) { this.#events.addListener(event, listener); return this; } /** * Alias for `off`. * @param {string | symbol} event - The name of the event. * @param {ListenerCallback} listener - The listener to remove. * @returns {this} The current class instance (for chaining). */ removeListener(event, listener) { this.#events.removeListener(event, listener); return this; } /** * Removes all listeners for a specific event, or all events if no event is specified. * @param {string | symbol} [event] - The name of the event. If omitted, all listeners from all events will be removed. * @returns {this} The current class instance (for chaining). */ removeAllListeners(event) { this.#events.removeAllListeners(event); return this; } /** * Returns the number of times the given `listener` is registered for the specified `event`. * If no `listener` is passed, returns how many listeners are registered for the `event`. * @param {string | symbol} eventName - The name of the event. * @param {Function} [listener] - Optional listener function to count. * @returns {number} Number of matching listeners. */ listenerCount(eventName, listener) { return this.#events.listenerCount(eventName, listener); } /** * Adds a listener function to the **beginning** of the listeners array for the specified event. * The listener is called every time the event is emitted. * @param {string | symbol} eventName - The event name. * @param {ListenerCallback} listener - The callback function. * @returns {this} The current class instance (for chaining). */ prependListener(eventName, listener) { this.#events.prependListener(eventName, listener); return this; } /** * Adds a **one-time** listener function to the **beginning** of the listeners array. * The next time the event is triggered, this listener is removed and then invoked. * @param {string | symbol} eventName - The event name. * @param {ListenerCallback} listener - The callback function. * @returns {this} The current class instance (for chaining). */ prependOnceListener(eventName, listener) { this.#events.prependOnceListener(eventName, listener); return this; } /** * Returns an array of event names for which listeners are currently registered. * @returns {(string | symbol)[]} Array of event names. */ eventNames() { return this.#events.eventNames(); } /** * Gets the current maximum number of listeners allowed for any single event. * @returns {number} The max listener count. */ getMaxListeners() { return this.#events.getMaxListeners(); } /** * Returns a copy of the listeners array for the specified event. * @param {string | symbol} eventName - The event name. * @returns {Function[]} An array of listener functions. */ listeners(eventName) { return this.#events.listeners(eventName); } /** * Returns a copy of the internal listeners array for the specified event, * including wrapper functions like those used by `.once()`. * @param {string | symbol} eventName - The event name. * @returns {Function[]} An array of raw listener functions. */ rawListeners(eventName) { return this.#events.rawListeners(eventName); } /** * @typedef {Object} AIContentData * @property {Array<Record<'text' | 'inlineData', string | { mime_type: string, data: string } | null>>} parts * @property {string|undefined} [role] * @property {string|number|undefined} [finishReason] */ /** * @typedef {{ count: number|null, hide?: boolean }} TokenCount */ /** @type {string|null} */ #_apiKey = null; /** @type {function|null} */ #_getModels = null; /** @type {function|null} */ #_countTokens = null; /** @type {function|null} */ #_genContentApi = null; /** @type {string|null} */ #_selectedHistory = null; /** @type {Record<string, function>} */ #_partTypes = {}; /** @type {function} */ #_insertIntoHistory = () => { }; /** @type {Record<string|number, string|{ text: string, hide?: boolean }>} */ _errorCode = {}; /** @type {string|null} */ _nextModelsPageToken = null; /** @type {(AiModel|AiCategory)[]} */ models = []; /** @type {Object.<string, SessionData>} */ history = {}; _isSingle = false; /** * Creates an instance of the TinyAiInstance class. * Initializes internal variables, sets up initial configurations for handling AI models, * session history, and content generation, with the option to use a single or multiple instances. * * @param {boolean} [isSingle] - If true, configures the instance to handle a single session only. */ constructor(isSingle = false) { this._isSingle = isSingle; /** * Updates an existing entry in the session history. * * @param {string} id - The session identifier. * @param {Record<string, any>} data - Data fields to update within the session. * @returns {boolean} True if the update succeeded, false otherwise. */ this.#_insertIntoHistory = function (id, data) { if (typeof id === 'string' && this.history[id]) { for (const where in data) { this.history[id][where] = data[where]; } return true; } return false; }; /** * Parsers for different part types. * @type {{ text: (input: any) => string|null, inlineData: (input: any) => { mime_type: string, data: string }|null }} */ this.#_partTypes = { text: (/** @type {string} */ text) => (typeof text === 'string' ? text : null), inlineData: (/** @type {{ mime_type: string; data: string; }} */ data) => { if (typeof data.mime_type === 'string' && typeof data.data === 'string') return data; return null; }, }; // Is single instance if (this._isSingle) { this.startDataId('main', true); // @ts-ignore this.startDataId = null; // @ts-ignore this.stopDataId = null; // @ts-ignore this.selectDataId = null; } } /** * Capitalizes the first letter of the provided string. * * @param {string} str - The input string to capitalize. * @returns {string} The string with the first character in uppercase. */ #capitalizeFirstLetter(str) { if (!str) return ''; return str.charAt(0).toUpperCase() + str.slice(1); } /** * Sets a custom value in the selected session history. * * @param {string} name - The name of the custom value to set. * @param {*} value - The value to be assigned to the custom key. * @param {number} [tokenAmount] - The token amount associated with the custom value (optional). * @param {string} [id] - The session ID. If omitted, the currently selected session history ID will be used. * @throws {Error} If the custom value name is invalid (not a non-empty string) or conflicts with existing data. * @returns {void} This method does not return a value. */ setCustomValue(name, value, tokenAmount, id) { if (typeof name === 'string' && name.length > 0 && name !== 'customList') { // Prepare value to send const sendValue = { [name]: value }; // This value is extremely important for the import process to identify which custom values are being used const selectedId = this.getId(id); if (selectedId && this.history[selectedId]) { const history = this.history[selectedId]; if (!Array.isArray(history.customList)) history.customList = []; // Validate the custom value if (value !== null) { const props = history.customList.find((/** @type {*} */ item) => item.name === name); if (!props || typeof props.type !== 'string' || typeof props.name !== 'string') { if (typeof history[name] === 'undefined') history.customList.push({ name, // @ts-ignore type: objType(value), }); else throw new Error('This value name is already being used!'); } else if (props.type !== objType(value)) throw new Error(`Invalid custom value type! ${name}: ${props.type} === ${objType(value)}`); } // Add Tokens if (typeof tokenAmount === 'number') this.history[selectedId].tokens[name] = tokenAmount; // Send custom value into the history if (value !== null) { this.#_insertIntoHistory(selectedId, sendValue); this.history[selectedId].hash[name] = objHash(value); } // Complete this.#emit(`set${this.#capitalizeFirstLetter(name)}`, value, selectedId); return; } } throw new Error('Invalid custom value!'); } /** * Resets a custom value in the selected session history. * * @param {string} name - The name of the custom value to reset. * @param {string} [id] - The session ID. If omitted, the currently selected session history ID will be used. * @throws {Error} If the custom value name is invalid or does not match an existing entry. * @returns {void} This method does not return a value. */ resetCustomValue(name, id) { if (typeof name === 'string' && name.length > 0 && name !== 'customList') { // Prepare value to send const sendValue = { [name]: null }; // This value is extremely important for the import process to identify which custom values are being used const selectedId = this.getId(id); if (selectedId && this.history[selectedId]) { const history = this.history[selectedId]; if (!Array.isArray(history.customList)) history.customList = []; // Validate the custom value const props = history.customList.find((/** @type {*} */ item) => item.name === name); if (isJsonObject(props) && typeof props.type === 'string' && typeof props.name === 'string') { // Reset Tokens if (typeof this.history[selectedId].tokens[name] !== 'undefined') delete this.history[selectedId].tokens[name]; // Reset custom value this.#_insertIntoHistory(selectedId, sendValue); if (typeof this.history[selectedId].hash[name] !== 'undefined') delete this.history[selectedId].hash[name]; // Complete this.#emit(`set${this.#capitalizeFirstLetter(name)}`, null, selectedId); return; } } throw new Error('Invalid custom value data type!'); } throw new Error('Invalid custom value!'); } /** * Completely removes a custom value from the selected session history. * * @param {string} name - The name of the custom value to erase. * @param {string} [id] - The session ID. If omitted, the currently selected session history ID will be used. * @throws {Error} If the custom value name is invalid or does not exist. * @returns {void} This method does not return a value. */ eraseCustomValue(name, id) { this.resetCustomValue(name, id); const history = this.getData(id); if (history && history.customList) { const index = history.customList.findIndex((item) => item.name === name); if (index > -1) history.customList.splice(index, 1); return; } throw new Error('Invalid custom value!'); } /** * Retrieves a custom value from the selected session history. * * @param {string} name - The name of the custom value to retrieve. * @param {string} [id] - The session ID. If omitted, the currently selected session history ID will be used. * @returns {*} The value associated with the specified name, or `null` if it does not exist. */ getCustomValue(name, id) { const history = this.getData(id); return history && typeof history[name] !== 'undefined' && history[name] !== null ? history[name] : null; } /** * Retrieves the list of custom values from the selected session history. * * @param {string} [id] - The session ID. If omitted, the currently selected session history ID will be used. * @returns {Array<*>} An array of custom values if available, or an empty array if no custom values exist. */ getCustomValueList(id) { const history = this.getData(id); return history && Array.isArray(history.customList) ? history.customList : []; } /** * Set the maximum output tokens setting for an AI session. * * @param {number} value - The maximum number of output tokens to be set. * @param {string} [id] - The session ID. If omitted, the currently selected session will be used. * @returns {void} This function does not return a value. */ setMaxOutputTokens(value, id) { if (typeof value === 'number' && !Number.isNaN(value) && Number.isFinite(value)) { const selectedId = this.getId(id); this.#_insertIntoHistory(selectedId, { maxOutputTokens: value }); this.#emit('setMaxOutputTokens', value, selectedId); return; } throw new Error('Invalid number value!'); } /** * Get the maximum output tokens setting for an AI session. * * @param {string} [id] - The session ID. If omitted, the currently selected session will be used. * @returns {number | null} The maximum output tokens value, or null if not set. */ getMaxOutputTokens(id) { const history = this.getData(id); return history && typeof history.maxOutputTokens === 'number' ? history.maxOutputTokens : null; } /** * Set the AI temperature setting for a session. * * @param {number} value - The temperature value to be set. * @param {string} [id] - The session ID. If omitted, the currently selected session will be used. * @returns {void} This function does not return a value. */ setTemperature(value, id) { if (typeof value === 'number' && !Number.isNaN(value) && Number.isFinite(value)) { const selectedId = this.getId(id); this.#_insertIntoHistory(selectedId, { temperature: value }); this.#emit('setTemperature', value, selectedId); return; } throw new Error('Invalid number value!'); } /** * Get the AI temperature setting for a session. * * @param {string} [id] - The session ID. If omitted, the currently selected session will be used. * @returns {number | null} The temperature value, or null if not set. */ getTemperature(id) { const history = this.getData(id); return history && typeof history.temperature ? history.temperature : null; } /** * Set the top-p (nucleus sampling) value in an AI session. * * @param {number} value - The top-p value to be set. * @param {string} [id] - The session ID. If omitted, the currently selected session will be used. * @returns {void} This function does not return a value. */ setTopP(value, id) { if (typeof value === 'number' && !Number.isNaN(value) && Number.isFinite(value)) { const selectedId = this.getId(id); this.#_insertIntoHistory(selectedId, { topP: value }); this.#emit('setTopP', value, selectedId); return; } throw new Error('Invalid number value!'); } /** * Get the top-p (nucleus sampling) setting for an AI session. * * @param {string} [id] - The session ID. If omitted, the currently selected session will be used. * @returns {number | null} The top-p value, or null if not set. */ getTopP(id) { const history = this.getData(id); return history && typeof history.topP === 'number' ? history.topP : null; } /** * Set the top-k setting for an AI session. * * @param {number} value - The top-k value to be set. * @param {string} [id] - The session ID. If omitted, the currently selected session will be used. * @returns {void} This function does not return a value. */ setTopK(value, id) { if (typeof value === 'number' && !Number.isNaN(value) && Number.isFinite(value)) { const selectedId = this.getId(id); this.#_insertIntoHistory(selectedId, { topK: value }); this.#emit('setTopK', value, selectedId); return; } throw new Error('Invalid number value!'); } /** * Get the top-k setting for an AI session. * * @param {string} [id] - The session ID. If omitted, the currently selected session will be used. * @returns {number | null} The top-k value, or null if not set. */ getTopK(id) { const history = this.getData(id); return history && typeof history.topK === 'number' ? history.topK : null; } /** * Set the presence penalty setting for an AI session. * * @param {number} value - The presence penalty value to be set. * @param {string} [id] - The session ID. If omitted, the currently selected session will be used. * @returns {void} This function does not return a value. */ setPresencePenalty(value, id) { if (typeof value === 'number' && !Number.isNaN(value) && Number.isFinite(value)) { const selectedId = this.getId(id); this.#_insertIntoHistory(selectedId, { presencePenalty: value }); this.#emit('setPresencePenalty', value, selectedId); return; } throw new Error('Invalid number value!'); } /** * Get the presence penalty setting for an AI session. * * @param {string} [id] - The session ID. If omitted, the currently selected session will be used. * @returns {number | null} The presence penalty value, or null if not set. */ getPresencePenalty(id) { const history = this.getData(id); return history && typeof history.presencePenalty === 'number' ? history.presencePenalty : null; } /** * Set the frequency penalty setting for an AI session. * * @param {number} value - The frequency penalty value to be set. * @param {string} [id] - The session ID. If omitted, the currently selected session will be used. * @returns {void} This function does not return a value. */ setFrequencyPenalty(value, id) { if (typeof value === 'number' && !Number.isNaN(value) && Number.isFinite(value)) { const selectedId = this.getId(id); this.#_insertIntoHistory(selectedId, { frequencyPenalty: value }); this.#emit('setFrequencyPenalty', value, selectedId); return; } throw new Error('Invalid number value!'); } /** * Get the frequency penalty setting for an AI session. * * @param {string} [id] - The session ID. If omitted, the currently selected session will be used. * @returns {number | null} The frequency penalty value, or null if not set. */ getFrequencyPenalty(id) { const history = this.getData(id); return history && typeof history.frequencyPenalty === 'number' ? history.frequencyPenalty : null; } /** * Set the setting for enabling enhanced civic answers in an AI session. * * @param {boolean} value - Whether to enable enhanced civic answers (true or false). * @param {string} [id] - The session ID. If omitted, the currently selected session will be used. * @returns {void} This function does not return a value. */ setEnabledEnchancedCivicAnswers(value, id) { if (typeof value === 'boolean') { const selectedId = this.getId(id); this.#_insertIntoHistory(selectedId, { enableEnhancedCivicAnswers: value }); this.#emit('setEnabledEnchancedCivicAnswers', value, selectedId); return; } throw new Error('Invalid boolean value!'); } /** * Get the setting for whether enhanced civic answers are enabled in an AI session. * * @param {string} [id] - The session ID. If omitted, the currently selected session will be used. * @returns {boolean | null} The value indicating whether enhanced civic answers are enabled, or null if not set. */ isEnabledEnchancedCivicAnswers(id) { const history = this.getData(id); return history && typeof history.enableEnhancedCivicAnswers === 'boolean' ? history.enableEnhancedCivicAnswers : null; } /** * Set the model for an AI session. * * @param {string} data - The model to be set (must be a string). * @param {string} [id] - The session ID. If omitted, the currently selected session will be used. * @returns {void} This function does not return a value. */ setModel(data, id) { const model = typeof data === 'string' ? data : null; const selectedId = this.getId(id); this.#_insertIntoHistory(selectedId, { model }); this.#emit('setModel', model, selectedId); } /** * Get the model for an AI session. * * @param {string} [id] - The session ID. If omitted, the currently selected session will be used. * @returns {string | null} The model, or null if not set. */ getModel(id) { const history = this.getData(id); return history && typeof history.model === 'string' ? history.model : null; } /** * Build content data for an AI session. * * @param {Array<*>} [contents] - An optional array to which the built content data will be pushed. * @param {Record<string, any>} item - The item containing content parts or a content object. * @param {string|null} [role] - The role to be associated with the content (optional). * @param {boolean} [rmFinishReason=false] - If true, removes the `finishReason` property from the content. * @returns {AIContentData|number} The constructed content data object, or array length if pushed to an array. */ buildContents(contents, item = {}, role = null, rmFinishReason = false) { // Content Data const tinyThis = this; /** @type {{ finishReason: string|number|undefined, parts: any[], role: string|undefined }} */ const contentData = { parts: [], finishReason: undefined, role: undefined }; // Role if (typeof role === 'string') contentData.role = role; /** @param {Record<string, any>} content */ const insertPart = (content) => { /** @type {Record<string, function>} */ const tinyResult = {}; for (const valName in content) { if (typeof tinyThis.#_partTypes[valName] === 'function') tinyResult[valName] = tinyThis.#_partTypes[valName](content[valName]); } contentData.parts.push(tinyResult); }; if (Array.isArray(item.parts)) { for (const index in item.parts) insertPart(item.parts[index]); } else if (item.content) insertPart(item.content); if (!rmFinishReason) if (typeof item.finishReason === 'string' || typeof item.finishReason === 'number') contentData.finishReason = item.finishReason; // Complete if (Array.isArray(contents)) return contents.push(contentData); return contentData; } /** * Set the API key for the AI session. * * @param {string} apiKey - The API key to be set. * @returns {void} This function does not return a value. */ setApiKey(apiKey) { this.#_apiKey = typeof apiKey === 'string' ? apiKey : null; } /** * Set the token for the next page of models in the AI session. * * @param {string} nextModelsPageToken - The token for the next models page. * @returns {void} This function does not return a value. */ _setNextModelsPageToken(nextModelsPageToken) { this._nextModelsPageToken = typeof nextModelsPageToken === 'string' ? nextModelsPageToken : null; } /** * Set the function to retrieve models for the AI session. * * @param {Function} getModels - The function to retrieve models. * @returns {void} This function does not return a value. */ _setGetModels(getModels) { this.#_getModels = typeof getModels === 'function' ? getModels : null; } /** * Get a list of models for the AI session. * * @param {number} [pageSize=50] - The number of models to retrieve per page. Defaults to 50. * @param {string|null} [pageToken=null] - The token for the next page of models, if available. Defaults to null. * @returns {Array<*>} The list of models retrieved. * @throws {Error} If no model list API function is defined. */ getModels(pageSize = 50, pageToken = null) { if (typeof this.#_getModels === 'function') return this.#_getModels(this.#_apiKey, pageSize, pageToken || this._nextModelsPageToken); throw new Error('No model list api script defined.'); } /** * Get the list of models for the AI session. * * @returns {(AiModel|AiCategory)[]} The list of models. */ getModelsList() { return Array.isArray(this.models) ? this.models : []; } /** * Get model data from the list of models. * * @param {string} id - The model data id to search for in the models list. * @returns {AiModel|null} The model data if found, otherwise null. */ getModelData(id) { // @ts-ignore const model = this.models.find((item) => item.id === id); // @ts-ignore if (model) return model; else { for (const index in this.models) { // @ts-ignore if (this.models[index].category) { // @ts-ignore const modelCategory = this.models[index].data.find((item) => item.id === id); if (modelCategory) return modelCategory; } } } return null; } /** * Check if a model exists in the model list. * * @param {string} id - The model id to check for in the models list. * @returns {boolean} True if the model exists, false otherwise. */ existsModel(id) { return this.getModelData(id) ? true : false; } /** * Insert a new model into the AI session's models list. * If the model already exists, it will not be inserted again. * * @param {Object} model - The model to insert. * @param {*} model._response - The raw response. * @param {number} model.index - The index position. * @param {string} model.id - The unique identifier for the model. * @param {string} [model.name] - The name of the model. * @param {string} [model.displayName] - The display name of the model. * @param {string} [model.version] - The version of the model. * @param {string} [model.description] - A description of the model. * @param {number} [model.inputTokenLimit] - The input token limit for the model. * @param {number} [model.outputTokenLimit] - The output token limit for the model. * @param {number} [model.temperature] - The temperature setting for the model. * @param {number} [model.maxTemperature] - The maximum temperature setting for the model. * @param {number} [model.topP] - The top P setting for the model. * @param {number} [model.topK] - The top K setting for the model. * @param {Array<string>} [model.supportedGenerationMethods] - The generation methods supported by the model. * @param {Object} [model.category] - The category of the model. * @param {string} model.category.id - The unique identifier for the category. * @param {string} model.category.displayName - The display name of the category. * @param {number} model.category.index - The index of the category. * @returns {Record<string, any>|null} The inserted model data, or null if the model already exists. */ _insertNewModel(model) { if (!isJsonObject(model)) throw new Error('Model data must be a valid object.'); // @ts-ignore if (this.models.findIndex((item) => item.id === model.id) < 0) { /** @type {AiModel} */ const newData = { _response: model._response, index: typeof model.index === 'number' ? model.index : 9999999, name: typeof model.name === 'string' ? model.name : null, id: typeof model.id === 'string' ? model.id : null, displayName: typeof model.displayName === 'string' ? model.displayName : null, version: typeof model.version === 'string' ? model.version : null, description: typeof model.description === 'string' ? model.description : null, inputTokenLimit: typeof model.inputTokenLimit === 'number' ? model.inputTokenLimit : null, outputTokenLimit: typeof model.outputTokenLimit === 'number' ? model.outputTokenLimit : null, temperature: typeof model.temperature === 'number' ? model.temperature : null, maxTemperature: typeof model.maxTemperature === 'number' ? model.maxTemperature : null, topP: typeof model.topP === 'number' ? model.topP : null, topK: typeof model.topK === 'number' ? model.topK : null, }; // Supported generation methods if (Array.isArray(model.supportedGenerationMethods)) { newData.supportedGenerationMethods = []; for (const index in model.supportedGenerationMethods) { if (typeof model.supportedGenerationMethods[index] === 'string') newData.supportedGenerationMethods.push(model.supportedGenerationMethods[index]); } } // Is category if (model.category && typeof model.category.id === 'string' && typeof model.category.displayName === 'string' && typeof model.category.index === 'number') { // Check category /** @type {AiCategory|null} */ // @ts-ignore let category = this.models.find((item) => item.category === model.category.id); // Insert new category if (!category) { category = { category: model.category.id, displayName: model.category.displayName, index: model.category.index, data: [], }; this.models.push(category); } // Compare function that sorts objects by their `index` property. category.data.push(newData); category.data.sort( /** @param {{ index: number, [key: string]: any }} a @param {{ index: number, [key: string]: any }} b */ (a, b) => a.index - b.index); } // Normal mode else this.models.push(newData); // Sort data this.models.sort((a, b) => a.index - b.index); return newData; } return null; } /** * Sets a function to handle the count of tokens in the AI session. * If a valid function is provided, it will be used to count tokens. * * @param {Function} countTokens - The function that will handle the token count. * @throws {Error} Throws an error if the provided value is not a function. * @returns {void} */ _setCountTokens(countTokens) { this.#_countTokens = typeof countTokens === 'function' ? countTokens : null; } /** * Counts the tokens based on the provided data and model, using a defined token counting function. * If the function to count tokens is not set, an error is thrown. * * @param {Record<string, any>} data - The data that needs to be tokenized. * @param {string} [model] - The model to use for counting tokens. If not provided, the default model is used. * @param {AbortController} [controller] - The controller that manages the process or settings for counting tokens. * @throws {Error} Throws an error if no token counting function is defined. * @returns {Record<string, any>} The count of tokens. */ countTokens(data, model, controller) { if (typeof this.#_countTokens === 'function') return this.#_countTokens(this.#_apiKey, model || this.getModel(), controller, data); throw new Error('No count token api script defined.'); } /** * @typedef {{ text: string, hide?: boolean }} ErrorCode */ /** * Sets the error codes for the current session. * * @param {Record<string|number, string|ErrorCode>} errors - The error codes to set, typically an object containing error code definitions. * @returns {void} */ _setErrorCodes(errors) { this._errorCode = errors; } /** * Get error details based on the provided error code. * * @param {string|number} code - The error code to look up. * @returns {ErrorCode|null} An object containing the error message, or null if no error is found. */ getErrorCode(code) { if (this._errorCode) { const errData = this._errorCode[code]; if (errData) { if (typeof errData === 'string') return { text: errData }; else if (isJsonObject(errData) && typeof errData.text === 'string') return errData; } } return null; } /** * Sets the content generation callback function for the AI session. * * @param {Function} callback - The callback function that handles content generation. * @returns {void} */ _setGenContent(callback) { this.#_genContentApi = typeof callback === 'function' ? callback : null; } /** * Generates content for the AI session. * * @param {Record<string, any>} data - The data for content generation. * @param {string} [model] - The model to be used for content generation. If not provided, the default model is used. * @param {AbortController} [controller] - The controller managing the content generation process. * @param {Function} [streamCallback] - The callback function for streaming content (optional). * @returns {Record<string, any>} The generated content returned by the API. * @throws {Error} If no content generator API script is defined. */ genContent(data, model, controller, streamCallback) { if (typeof this.#_genContentApi === 'function') return this.#_genContentApi(this.#_apiKey, typeof streamCallback === 'function' ? true : false, data, model || this.getModel(), streamCallback, controller); throw new Error('No content generator api script defined.'); } /** * Select a session history ID to set as the active session. * If `null` is passed, it deselects the current session ID. * * @param {string|null} id - The session history ID to select, or `null` to deselect the current session. * @returns {boolean} `true` if the session ID was successfully selected or deselected, `false` if the ID does not exist in history. */ selectDataId(id) { if (id !== null) { if (this.history[id]) { this.#_selectedHistory = id; this.#emit('selectDataId', id); return true; } return false; } this.#_selectedHistory = null; this.#emit('selectDataId', null); return true; } /** * Get the currently selected session history ID. * If no ID is provided, it returns the default selected session history ID. * * @param {string} [id] - The session history ID to retrieve. If not provided, it uses the default selected ID. * @returns {string|null} The selected session history ID, or `null` if no history ID is selected. */ getId(id) { const result = id && !this._isSingle ? id : this.#_selectedHistory; if (typeof result === 'string') return result; return null; } /** * Get the data associated with a specific session history ID. * * @param {string} [id] - The session ID. If omitted, the currently selected session history ID will be used. * @returns {SessionData|null} The data associated with the session ID, or `null` if no data exists for that ID. */ getData(id) { const selectedId = this.getId(id); if (selectedId && this.history[selectedId]) return this.history[selectedId]; return null; } /** * Calculates the total number of tokens used for messages in the session history. * * This method iterates over the `tokens` array in the session history and sums the `count` of tokens * from each message, returning the total sum. If no valid session history is found or if token data is * missing, it will return `null`. * * @param {string} [id] - The session ID. If omitted, the currently selected session history ID will be used. * @returns {number|null} The total number of tokens used in the session history, or `null` if no data is available. */ getTotalTokens(id) { const history = this.getData(id); if (history) { let result = 0; for (const msgIndex in history.tokens.data) { if (typeof history.tokens.data[msgIndex].count === 'number') result += history.tokens.data[msgIndex].count; } for (const item in history.tokens) { if (typeof history.tokens[item] === 'number') { result += history.tokens[item]; } } return result; } return null; } /** * Retrieves the token data for a specific message in the session history by its index. * * **Note**: This method does not manage the token count automatically. It assumes that token data has been added * to the history using the `addData` method. * * @param {number} msgIndex - The index of the message in the session history. * @param {string} [id] - The session ID. If omitted, the currently selected session history ID will be used. * @returns {TokenCount|null} The token data associated with the message at the specified index, or `null` if the data is not found. */ getMsgTokensByIndex(msgIndex, id) { const history = this.getData(id); if (history) { const existsIndex = this.indexExists(msgIndex, id); if (existsIndex) return history.tokens.data[msgIndex]; } return null; } /** * Retrieves the token data for a specific message in the session history by its message ID. * * **Note**: This method does not manage the token count automatically. It assumes that token data has been added * to the history using the `addData` method. * * @param {string} msgId - The unique ID of the message in the session history. * @param {string} [id] - The session ID. If omitted, the currently selected session history ID will be used. * @returns {TokenCount|null} The token data associated with the message with the given ID, or `null` if the message is not found. */ getMsgTokensById(msgId, id) { const history = this.getData(id); if (history) { const msgIndex = this.getIndexOfId(msgId); if (msgIndex > -1) return history.tokens.data[msgIndex]; } return null; } /** * Retrieves the hash of a message at a specified index in the selected session history. * * @param {number} msgIndex - The index of the message whose hash is being retrieved. * @param {string} [id] - The session ID. If omitted, the currently selected session history ID will be used. * @returns {string|null} The hash value of the message at the specified index, or null if the index is invalid or does not exist. */ getMsgHashByIndex(msgIndex, id) { const history = this.getData(id); if (history) { const existsIndex = this.indexExists(msgIndex, id); if (existsIndex) return history.hash.data[msgIndex]; } return null; } /** * Retrieves the hash of a message based on its ID from the selected session history. * * @param {string} msgId - The ID of the message whose hash is being retrieved. * @param {string} [id] - The session ID. If omitted, the currently selected session history ID will be used. * @returns {string|null} The hash value of the message with the specified ID, or null if the message ID is invalid or does not exist. */ getMsgHashById(msgId, id) { const history = this.getData(id); if (history) { const msgIndex = this.getIndexOfId(msgId); if (msgIndex > -1) return history.hash.data[msgIndex]; } return null; } /** * Checks if a specific index exists in the session history. * * **Note**: This method assumes that the history data is available and that the `getMsgByIndex` method is used * to retrieve the index. If the `getMsgByIndex` method returns a valid index, this method will return `true`. * * @param {number} index - The index to check for existence in the session history. * @param {string} [id] - The session ID. If omitted, the currently selected session history ID will be used. * @returns {boolean} `true` if the index exists, otherwise `false`. */ indexExists(index, id) { return this.getMsgByIndex(index, id) ? true : false; } /** * Retrieve a specific data entry by its index from the session history. * * @param {number} index - The index of the data entry to retrieve. * @param {string} [id] - The session ID. If omitted, the currently selected s