diff --git a/.gitignore b/.gitignore index 2309cc8..d055fbb 100644 --- a/.gitignore +++ b/.gitignore @@ -136,3 +136,4 @@ dist .yarn/install-state.gz .pnp.* +.history \ No newline at end of file diff --git a/README.md b/README.md index ffe83db..9a62f13 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,188 @@ # Amily-Databin -Databin组件,优化数据存储持久化功能,不直接提供用户界面,默认仅供开发者进行调用。 \ No newline at end of file +Databin组件,优化数据存储持久化功能,不直接提供用户界面,默认仅供开发者进行调用。 + +该组件的意义为降低开发者对数据存储的复杂度,提供一种简单的数据存储方式,并支持数据持久化。该组件维护且仅维护存储和读取数据,不提供用户界面。(当然,并不意味着该组件不可用于用户界面,我们鼓励用户自行设计用户界面) + +## 安装 + +### 通过链接安装 (推荐) + +1. 在 SillyTavern 的扩展页面,点击 "Install from URL"。 +2. 输入以下链接: `https://amily-gitlab.amily49.cc/slvccans/Amily-Databin` +3. 点击 "Install"。 +4. 重启 SillyTavern. + +### 手动安装 + +1. 下载本仓库的 ZIP 压缩包。 +2. 解压 ZIP 文件。 +3. 将 `Amily-Databin` 文件夹复制到 `SillyTavern/public/extensions` 目录下。 +4. 重启 SillyTavern. + +## API 用法 + +本插件会在客户端环境中注入一个全局的 `window.AmilyDatabin` 对象,用于进行精细化的数据交互。 + +**架构说明:** `SourceType` 常量表现在通过根目录的 `sourcetype.json` 文件进行动态管理。客户端会在加载时自动从服务器获取此配置,这使得系统具有很强的扩展性,未来增加新的数据类型也无需修改客户端代码。 + +### `AmilyDatabin.SourceType` + +一个用于定义数据来源的常量表。 + +```javascript +{ + CHAT_LOG: 1, // 用于聊天文件存储 + CHARACTER_CARD: 2, // 用于角色卡存储 + WORLD_BOOK: 3 // 用于世界书存储 +} +``` + +### `AmilyDatabin.getData(source, fileName, objName = null)` + +从服务器获取特定数据。`objName` 是可选的,如果省略,插件会从当前上下文中自动检测。 + +```javascript +/** + * 从服务器获取特定数据。 + * @param {number} source - 数据来源标识 (使用 AmilyDatabin.SourceType)。 + * @param {string} fileName - 文件名或唯一键。 + * @param {string} [objName=null] - 可选。指定对象名(如角色ID)。若为null,则自动检测。**注意:对于 `WORLD_BOOK` 类型,`objName` 无法自动检测,必须明确提供。** + * @returns {Promise} 一个Promise,解析为请求到的数据对象;如果未找到则为 null。 + */ +async function retrieveCurrentCharacterMemory() { + try { + // 此处省略了 objName,插件将自动使用当前角色的ID + const memory = await window.AmilyDatabin.getData( + window.AmilyDatabin.SourceType.CHARACTER_CARD, + "character_memory.json" + ); + + if (memory) { + console.log("成功获取当前角色记忆:", memory); + } else { + console.log("没有找到当前角色的记忆。"); + } + return memory; + } catch (error) { + console.error("获取数据失败:", error); + return null; + } +} + +// 自动上下文调用示例: +retrieveCurrentCharacterMemory(); +``` + +### `AmilyDatabin.saveData(source, fileName, fileContent, objName = null)` + +将一个JavaScript对象保存到服务器的指定路径。`objName` 是可选的。 + +```javascript +/** + * 将一个数据对象保存到服务器的指定路径。 + * @param {number} source - 数据来源标识 (使用 AmilyDatabin.SourceType)。 + * @param {string} fileName - 文件名或唯一键。 + * @param {string} [objName=null] - 可选。指定对象名。若为null,则自动检测。**注意:对于 `WORLD_BOOK` 类型,`objName` 无法自动检测,必须明确提供。** + * @returns {Promise} 一个Promise,如果保存成功则解析为 true,否则为 false。 + */ +async function saveCurrentChatLog(chatHistory) { + try { + // 此处省略了 objName,它将自动解析为当前聊天的ID + const success = await window.AmilyDatabin.saveData( + window.AmilyDatabin.SourceType.CHAT_LOG, + `log_${new Date().getTime()}.json`, // 唯一键示例 + { history: chatHistory, savedAt: new Date().toISOString() } + ); + + if (success) { + console.log("当前聊天的记录已成功保存。"); + } else { + console.error("聊天记录保存失败。"); + } + return success; + } catch (error) { + console.error("保存数据时出错:", error); + return false; + } +} + +// 调用示例: +const currentChat = ["用户: 你好!", "角色: 你好!"]; +saveCurrentChatLog(currentChat); +``` + +### `AmilyDatabin.deleteData(source, fileName, objName = null)` + +从服务器删除一个指定的数据文件。`objName` 是可选的。 + +```javascript +/** + * 从服务器删除一个特定的数据文件。 + * @param {number} source - 数据来源标识 (使用 AmilyDatabin.SourceType)。 + * @param {string} fileName - 要删除的数据的文件名或唯一键。 + * @param {string} [objName=null] - 可选。指定对象名。若为null,则自动检测。**注意:对于 `WORLD_BOOK` 类型,`objName` 无法自动检测,必须明确提供。** + * @returns {Promise} 一个Promise,如果删除成功则解析为 true,否则为 false。 + */ +async function deleteCurrentCharacterMemory() { + try { + const success = await window.AmilyDatabin.deleteData( + window.AmilyDatabin.SourceType.CHARACTER_CARD, + "character_memory.json" + ); + + if (success) { + console.log("成功删除当前角色记忆。"); + } else { + console.log("删除角色记忆失败。"); + } + return success; + } catch (error) { + console.error("删除数据失败:", error); + return false; + } +} + +// 调用示例: +deleteCurrentCharacterMemory(); +``` + +## 调试方法 + +这些方法设计用于在浏览器控制台中进行快速调试。它们不与后端API交互。 + +### `AmilyDatabin.getDebugInstance()` + +将 `AmilyDatabin` 对象本身输出到控制台。可用于检查其当前状态、`SourceType` 映射和可用方法。 + +```javascript +// 在浏览器控制台调用: +AmilyDatabin.getDebugInstance(); +// 预期输出: { SourceType: { CHAT_LOG: 1, ... }, initialize: f, ... } +``` + +### `AmilyDatabin.getDebugChatId()` + +尝试使用与主API方法相同的逻辑解析当前聊天ID,并将其记录到控制台。 + +```javascript +// 在浏览器控制台调用: +AmilyDatabin.getDebugChatId(); +// 预期输出: [Amily-Databin] Current Chat ID (Debug): "你的聊天ID" +``` + +### `AmilyDatabin.getDebugCardId()` + +尝试使用与主API方法相同的逻辑解析当前角色卡ID,并将其记录到控制台。 + +```javascript +// 在浏览器控制台调用: +AmilyDatabin.getDebugCardId(); +// 预期输出: [Amily-Databin] Current Character Card ID (Debug): "你的角色卡ID" +``` + +## 开发者 + +主要逻辑分别位于 `index.js` (服务器端) 和 `public/client.js` (客户端)。 + +你可以直接通过全局的 `window.AmilyDatabin` 对象与此插件交互。 diff --git a/README_en.md b/README_en.md new file mode 100644 index 0000000..969de2c --- /dev/null +++ b/README_en.md @@ -0,0 +1,192 @@ +# Amily-Databin + +A Databin component designed to optimize data storage persistence. It does not directly provide a user interface but is intended for developers to call for data operations. + +The purpose of this component is to simplify data storage for developers, offering a straightforward method for data storage with persistence support. This component solely manages data storage and retrieval; it does not offer a user interface. (However, this does not mean the component cannot be used for user interfaces; users are encouraged to design their own user interfaces.) + +## Installation + +### Install via Link (Recommended) + +1. In the SillyTavern extensions page, click "Install from URL". +2. Enter the following link: `https://amily-gitlab.amily49.cc/slvccans/Amily-Databin` +3. Click "Install". +4. Restart SillyTavern. + +### Manual Installation + +1. Download the ZIP archive of this repository. +2. Extract the ZIP file. +3. Copy the `Amily-Databin` folder to the `SillyTavern/public/extensions` directory. +4. Restart SillyTavern. + +## Usage + +This plugin acts as a data bucket component and does not provide a user interface. It is primarily intended for developers to call. + +## API Usage + +This plugin injects a global `window.AmilyDatabin` object into the client-side environment for simplified, granular data interaction. + +**Architectural Note:** The `SourceType` constants table is dynamically managed via the `sourcetype.json` file. The client automatically fetches this configuration from the server upon loading, making the system extensible without requiring client-side code changes. + +### `AmilyDatabin.SourceType` + +A constants table used to define the source of the data. + +```javascript +{ + CHAT_LOG: 1, // For chat log storage + CHARACTER_CARD: 2, // For character card storage + WORLD_BOOK: 3 // For world book storage +} +``` + +### `AmilyDatabin.getData(source, fileName, objName = null)` + +Retrieves specific data from the server. The `objName` is optional and will be auto-detected from the current context if omitted. + +```javascript +/** + * Fetches specific data from the server. + * @param {number} source - The data source identifier (use AmilyDatabin.SourceType). + * @param {string} fileName - The file name or unique key for the data. + * @param {string} [objName=null] - Optional. The specific object name (e.g., character ID). If null, it's auto-detected. **Note: For `WORLD_BOOK` type, `objName` cannot be automatically detected and must be provided explicitly.** + * @returns {Promise} A Promise that resolves to the requested data object, or null if not found. + */ +async function retrieveCurrentCharacterMemory() { + try { + // We omit objName, so the plugin will automatically use the current character's ID. + const memory = await window.AmilyDatabin.getData( + window.AmilyDatabin.SourceType.CHARACTER_CARD, + "character_memory.json" + ); + + if (memory) { + console.log("Retrieved current character memory:", memory); + } else { + console.log("No memory found for the current character."); + } + return memory; + } catch (error) { + console.error("Failed to retrieve data:", error); + return null; + } +} + +// Example call for automatic context: +retrieveCurrentCharacterMemory(); +``` + +### `AmilyDatabin.saveData(source, fileName, fileContent, objName = null)` + +Saves a JavaScript object to a specific path on the server. The `objName` is optional. + +```javascript +/** + * Saves a data object to a specific path on the server. + * @param {number} source - The data source identifier (use AmilyDatabin.SourceType). + * @param {string} fileName - The file name or unique key for the data. + * @param {object} fileContent - The JavaScript object to save. + * @param {string} [objName=null] - Optional. The specific object name. If null, it's auto-detected. **Note: For `WORLD_BOOK` type, `objName` cannot be automatically detected and must be provided explicitly.** + * @returns {Promise} A Promise that resolves to true if successful, false otherwise. + */ +async function saveCurrentChatLog(chatHistory) { + try { + // objName is omitted here, it will be resolved to the current chat's ID automatically. + const success = await window.AmilyDatabin.saveData( + window.AmilyDatabin.SourceType.CHAT_LOG, + `log_${new Date().getTime()}.json`, // Example of a unique key + { history: chatHistory, savedAt: new Date().toISOString() } + ); + + if (success) { + console.log("Chat log for current chat saved successfully."); + } else { + console.error("Failed to save chat log."); + } + return success; + } catch (error) { + console.error("Error during data saving:", error); + return false; + } +} + +// Example call: +const currentChat = ["User: Hi!", "Character: Hello!"]; +saveCurrentChatLog(currentChat); +``` + +### `AmilyDatabin.deleteData(source, fileName, objName = null)` + +Deletes a specific data file from the server. The `objName` is optional. + +```javascript +/** + * Deletes a specific data file from the server. + * @param {number} source - The data source identifier (use AmilyDatabin.SourceType). + * @param {string} [objName=null] - Optional. The specific object name. If null, it's auto-detected. **Note: For `WORLD_BOOK` type, `objName` cannot be automatically detected and must be provided explicitly.** + * @returns {Promise} A Promise that resolves to true if successful, false otherwise. + */ +async function deleteCurrentCharacterMemory() { + try { + const success = await window.AmilyDatabin.deleteData( + window.AmilyDatabin.SourceType.CHARACTER_CARD, + "character_memory.json" + ); + + if (success) { + console.log("Successfully deleted current character memory."); + } else { + console.log("Failed to delete character memory."); + } + return success; + } catch (error) { + console.error("Failed to delete data:", error); + return false; + } +} + +// Example call: +deleteCurrentCharacterMemory(); +``` + +## Debug Methods + +These methods are designed for quick debugging in the browser console. They do not interact with the backend API. + +### `AmilyDatabin.getDebugInstance()` + +Outputs the `AmilyDatabin` object itself to the console. Useful for inspecting its current state, `SourceType` map, and available methods. + +```javascript +// Call in browser console: +AmilyDatabin.getDebugInstance(); +// Expected output: { SourceType: { CHAT_LOG: 1, ... }, initialize: f, ... } +``` + +### `AmilyDatabin.getDebugChatId()` + +Attempts to resolve the current chat ID using the same logic as the main API methods and logs it to the console. + +```javascript +// Call in browser console: +AmilyDatabin.getDebugChatId(); +// Expected output: [Amily-Databin] Current Chat ID (Debug): "your_chat_id_here" +``` + +### `AmilyDatabin.getDebugCardId()` + +Attempts to resolve the current character card ID using the same logic as the main API methods and logs it to the console. + +```javascript +// Call in browser console: +AmilyDatabin.getDebugCardId(); +// Expected output: [Amily-Databin] Current Character Card ID (Debug): "your_character_id_here" +``` + +## Developer + +The main logic is located in `index.js` (server-side) and `public/client.js` (client-side). The plugin registers itself with SillyTavern's extension API. + +You can interact with this plugin via the global `window.AmilyDatabin` object directly. diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..1cff360 --- /dev/null +++ b/config.yaml @@ -0,0 +1,5 @@ +name: "Amily-Databin" +author: "Silence_Lurker" +description: "A data bin extension for SillyTavern extension." +version: "1.0.0" +st_version: "1.11.0" diff --git a/index.js b/index.js new file mode 100644 index 0000000..bec7be2 --- /dev/null +++ b/index.js @@ -0,0 +1,134 @@ +const express = require('express'); +const fs = require('fs').promises; +const path = require('path'); + +const { + plugins, + plugin_name_to_folder_name, + plugin_data_path, +} = require('../../../public/script.js'); + +const extensionName = "Amily-Databin"; +const extensionVersion = "0.0.1-a"; + +const storageBasePath = path.join(plugin_data_path, 'cc.amily49.amily-databin'); +let sourceTypes = {}; // Will be loaded from sourcetype.json + +/** + * Builds a safe file path and prevents path traversal attacks. + */ +function getSafeFilePath(source, objName, file) { + if (typeof source !== 'string' && typeof source !== 'number' || typeof objName !== 'string' || typeof file !== 'string') { + return null; + } + const sanitize = (part) => String(part).replace(/\.\./g, '').replace(/[/\]/g, ''); + const safeSource = sanitize(source); + const safeObjName = sanitize(objName); + const safeFile = sanitize(file); + + if (!safeSource || !safeObjName || !safeFile) return null; + + return path.join(storageBasePath, safeSource, safeObjName, safeFile); +} + +// #region Server-side Business Logic per SourceType +// These functions implement the "strategy" for each source type. + +/** Generic save handler */ +async function defaultSaveStrategy(filePath, data) { + await fs.mkdir(path.dirname(filePath), { recursive: true }); + await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf8'); +} + +// #endregion + +(async () => { + try { + // Load source types from the file on startup. + const sourceTypesRaw = await fs.readFile(path.join(__dirname, 'sourcetype.json'), 'utf8'); + sourceTypes = JSON.parse(sourceTypesRaw); + + // Ensure the base storage directory exists. + await fs.mkdir(storageBasePath, { recursive: true }); + } catch (error) { + console.error(`[${extensionName}] Critical startup error:`, error); + return; // Stop loading the plugin + } + + const router = express.Router(); + + // Endpoint to serve the source type map to the client + router.get('/sourcetype', (req, res) => { + res.json(sourceTypes); + }); + + router.get('/data', async (req, res) => { + const filePath = getSafeFilePath(req.query.source, req.query.objName, req.query.file); + if (!filePath) return res.status(400).send("Invalid parameters."); + + try { + const fileContent = await fs.readFile(filePath, 'utf8'); + res.json(JSON.parse(fileContent)); + } catch (error) { + if (error.code === 'ENOENT') return res.status(404).send('Not found.'); + console.error(`[${extensionName}] Error reading file ${filePath}:`, error); + res.status(500).send('Internal Server Error'); + } + }); + + router.post('/data', async (req, res) => { + const { source, objName, file, data } = req.body; + const filePath = getSafeFilePath(source, objName, file); + + if (!filePath) return res.status(400).send("Invalid parameters."); + if (data === undefined) return res.status(400).send("'data' is required."); + + const sourceType = parseInt(source, 10); + + try { + // Strategy pattern based on source type + switch (sourceType) { + case sourceTypes.CHAT_LOG: + case sourceTypes.CHARACTER_CARD: + case sourceTypes.WORLD_BOOK: + // For now, all implemented types use the default save strategy. + await defaultSaveStrategy(filePath, data); + res.status(200).send('Data saved successfully.'); + break; + + default: + // If the source type is known but not implemented, or unknown. + res.status(501).send(`Save handler for source type '${sourceType}' is not implemented.`); + break; + } + } catch (error) { + console.error(`[${extensionName}] Error in POST /data for source ${sourceType}:`, error); + res.status(500).send('Internal Server Error'); + } + }); + + router.delete('/data', async (req, res) => { + const filePath = getSafeFilePath(req.query.source, req.query.objName, req.query.file); + if (!filePath) return res.status(400).send("Invalid parameters."); + + try { + await fs.unlink(filePath); + res.status(200).send('Data deleted successfully.'); + } catch (error) { + if (error.code === 'ENOENT') return res.status(200).send('Not found.'); + console.error(`[${extensionName}] Error deleting file ${filePath}:`, error); + res.status(500).send('Internal Server Error'); + } + }); + + plugins[extensionName] = { + name: extensionName, + version: extensionVersion, + is_enabled: true, + folder_name: plugin_name_to_folder_name[extensionName], + router: router, + assets: ['client.js'], + }; + + console.log(`[${extensionName}] version ${extensionVersion} loaded. Storage path: ${storageBasePath}`); +})(); \ No newline at end of file diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..ea46352 --- /dev/null +++ b/manifest.json @@ -0,0 +1,7 @@ +{ + "name": "Amily-Databin", + "author": "Amily", + "version": "0.0.1-a", + "description": "A SillyTavern plugin for data management.", + "entry": "index.js" +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..ee1ad1f --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "amily-databin", + "version": "0.0.1-a", + "description": "A SillyTavern plugin for data management.", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "SillyTavern" + ], + "author": "Amily", + "license": "ISC" +} \ No newline at end of file diff --git a/public/client.js b/public/client.js new file mode 100644 index 0000000..92c1309 --- /dev/null +++ b/public/client.js @@ -0,0 +1,173 @@ +(function() { + const extensionName = "Amily-Databin"; + const apiBaseUrl = `/api/plugins/${extensionName}`; + + // ---------------------------------------------------------------- + // Private Helper Functions + // ---------------------------------------------------------------- + + /** + * Retrieves the current context from SillyTavern and maps it to the expected object name. + * This function attempts to get context dynamically from SillyTavern's global objects. + */ + function _getDynamicObjectName(source) { + const { SillyTavern } = window; + let context = null; + if (SillyTavern && typeof SillyTavern.getContext === 'function') { + context = SillyTavern.getContext(); + } else { + console.error(`[${extensionName}] window.SillyTavern.getContext() not found. Dynamic context resolution is not possible.`); + return null; + } + + if (!context) { + console.error(`[${extensionName}] SillyTavern context is null or undefined.`); + return null; + } + + let resolvedObjectName = null; + + switch (source) { + case AmilyDatabin.SourceType.CHAT_LOG: + resolvedObjectName = context.chatId; + break; + case AmilyDatabin.SourceType.CHARACTER_CARD: + resolvedObjectName = context.characterId; + break; + case AmilyDatabin.SourceType.WORLD_BOOK: + // From the provided getContext() structure, there is no direct 'current world book name' key. + // The user must provide objName for WORLD_BOOK type, or implement custom logic to detect it. + console.warn(`[${extensionName}] 'WORLD_BOOK' type's object name cannot be automatically resolved from SillyTavern.getContext(). Please provide 'objName' explicitly.`); + break; + default: + console.error(`[${extensionName}] Unknown source type for dynamic resolution: ${source}`); + return null; + } + + if (!resolvedObjectName) { + console.warn(`[${extensionName}] Could not dynamically resolve object name for source type ${source}. Context key might be missing or not active.`); + } + return resolvedObjectName; + } + + // ---------------------------------------------------------------- + // Public API Method Implementations + // ---------------------------------------------------------------- + + async function initialize() { + try { + const response = await fetch(`${apiBaseUrl}/sourcetype`); + if (!response.ok) throw new Error(`Failed to fetch source types: ${response.status}`); + this.SourceType = await response.json(); // 'this' refers to AmilyDatabin object + console.log(`[${extensionName}] Client initialized with source types:`, this.SourceType); + } catch (error) { + console.error(`[${extensionName}] Initialization failed:`, error); + } + } + + async function getData(source, fileName, objName = null) { + const objectName = objName ?? _getDynamicObjectName(source); + if (!objectName) { + console.error(`[${extensionName}] 'objName' could not be resolved for getData (Source: ${source}, File: ${fileName}).`); + return null; + } + try { + const queryParams = new URLSearchParams({ source, objName: objectName, file: fileName }); + const response = await fetch(`${apiBaseUrl}/data?${queryParams.toString()}`); + if (response.status === 404) return null; + if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); + return await response.json(); + } catch (error) { + console.error(`[${extensionName}] getData failed (Source: ${source}, ObjName: ${objectName}, File: ${fileName}):`, error); + return null; + } + } + + async function saveData(source, fileName, fileContent, objName = null) { + const objectName = objName ?? _getDynamicObjectName(source); + if (!objectName) { + console.error(`[${extensionName}] 'objName' could not be resolved for saveData (Source: ${source}, File: ${fileName}).`); + return false; + } + try { + const response = await fetch(`${apiBaseUrl}/data`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ source, objName: objectName, file: fileName, data: fileContent }), + }); + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`HTTP error! status: ${response.status}, message: ${errorText}`); + } + console.log(`[${extensionName}] Data saved to: ${source}/${objectName}/${fileName}`); + return true; + } catch (error) { + console.error(`[${extensionName}] saveData failed (Source: ${source}, ObjName: ${objectName}, File: ${fileName}):`, error); + return false; + } + } + + async function deleteData(source, fileName, objName = null) { + const objectName = objName ?? _getDynamicObjectName(source); + if (!objectName) { + console.error(`[${extensionName}] 'objName' could not be resolved for deleteData (Source: ${source}, File: ${fileName}).`); + return false; + } + try { + const queryParams = new URLSearchParams({ source, objName: objectName, file: fileName }); + const response = await fetch(`${apiBaseUrl}/data?${queryParams.toString()}`, { method: 'DELETE' }); + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`HTTP error! status: ${response.status}, message: ${errorText}`); + } + console.log(`[${extensionName}] Data deleted from: ${source}/${objectName}/${fileName}`); + return true; + } catch (error) { + console.error(`[${extensionName}] deleteData failed (Source: ${source}, ObjName: ${objectName}, File: ${fileName}):`, error); + return false; + } + } + + // ---------------------------------------------------------------- + // Debug Methods + // ---------------------------------------------------------------- + + function getDebugInstance() { + console.log(`[${extensionName}] AmilyDatabin Instance:`, AmilyDatabin); + return AmilyDatabin; + } + + function getDebugChatId() { + const chatId = _getDynamicObjectName(AmilyDatabin.SourceType.CHAT_LOG); + console.log(`[${extensionName}] Current Chat ID (Debug):`, chatId); + return chatId; + } + + function getDebugCardId() { + const cardId = _getDynamicObjectName(AmilyDatabin.SourceType.CHARACTER_CARD); + console.log(`[${extensionName}] Current Character Card ID (Debug):`, cardId); + return cardId; + } + + // ---------------------------------------------------------------- + // Global Object Definition + // ---------------------------------------------------------------- + + const AmilyDatabin = { + SourceType: {}, + initialize, + getData, + saveData, + deleteData, + // Debug methods + getDebugInstance, + getDebugChatId, + getDebugCardId + }; + + window.AmilyDatabin = AmilyDatabin; + + // Automatically initialize the module. + window.AmilyDatabin.initialize.bind(window.AmilyDatabin)(); + +})(); \ No newline at end of file diff --git a/showdisplay.tmp b/showdisplay.tmp new file mode 100644 index 0000000..382d6a3 --- /dev/null +++ b/showdisplay.tmp @@ -0,0 +1,161 @@ +export function getContext() { + return { + accountStorage, + chat, + characters, + groups, + name1, + name2, + characterId: this_chid, + groupId: selected_group, + chatId: selected_group + ? groups.find(x => x.id == selected_group)?.chat_id + : (characters[this_chid]?.chat), + getCurrentChatId, + getRequestHeaders, + reloadCurrentChat, + renameChat, + saveSettingsDebounced, + onlineStatus: online_status, + maxContext: Number(max_context), + chatMetadata: chat_metadata, + saveMetadataDebounced, + streamingProcessor, + eventSource, + eventTypes: event_types, + addOneMessage, + deleteLastMessage, + deleteMessage, + generate: Generate, + sendStreamingRequest, + sendGenerationRequest, + stopGeneration, + tokenizers, + getTextTokens, + /** @deprecated Use getTokenCountAsync instead */ + getTokenCount, + getTokenCountAsync, + extensionPrompts: extension_prompts, + setExtensionPrompt, + updateChatMetadata, + saveChat: saveChatConditional, + openCharacterChat, + openGroupChat, + saveMetadata, + sendSystemMessage, + activateSendButtons, + deactivateSendButtons, + saveReply, + substituteParams, + substituteParamsExtended, + SlashCommandParser, + SlashCommand, + SlashCommandArgument, + SlashCommandNamedArgument, + ARGUMENT_TYPE, + executeSlashCommandsWithOptions, + /** @deprecated Use SlashCommandParser.addCommandObject() instead */ + registerSlashCommand, + /** @deprecated Use executeSlashCommandWithOptions instead */ + executeSlashCommands, + timestampToMoment, + /** @deprecated Handlebars for extensions are no longer supported. */ + registerHelper: () => { }, + registerMacro: MacrosParser.registerMacro.bind(MacrosParser), + unregisterMacro: MacrosParser.unregisterMacro.bind(MacrosParser), + registerFunctionTool: ToolManager.registerFunctionTool.bind(ToolManager), + unregisterFunctionTool: ToolManager.unregisterFunctionTool.bind(ToolManager), + isToolCallingSupported: ToolManager.isToolCallingSupported.bind(ToolManager), + canPerformToolCalls: ToolManager.canPerformToolCalls.bind(ToolManager), + ToolManager, + registerDebugFunction, + /** @deprecated Use renderExtensionTemplateAsync instead. */ + renderExtensionTemplate, + renderExtensionTemplateAsync, + registerDataBankScraper: ScraperManager.registerDataBankScraper.bind(ScraperManager), + /** @deprecated Use callGenericPopup or Popup instead. */ + callPopup, + callGenericPopup, + showLoader, + hideLoader, + mainApi: main_api, + extensionSettings: extension_settings, + ModuleWorkerWrapper, + getTokenizerModel, + generateQuietPrompt, + generateRaw, + writeExtensionField, + getThumbnailUrl, + selectCharacterById, + messageFormatting, + shouldSendOnEnter, + isMobile, + t, + translate, + getCurrentLocale, + addLocaleData, + tags, + tagMap: tag_map, + menuType: menu_type, + createCharacterData: create_save, + /** @deprecated Legacy snake-case naming, compatibility with old extensions */ + event_types: event_types, + Popup, + POPUP_TYPE, + POPUP_RESULT, + chatCompletionSettings: oai_settings, + textCompletionSettings: textgenerationwebui_settings, + powerUserSettings: power_user, + getCharacters, + getCharacterCardFields, + uuidv4, + humanizedDateTime, + updateMessageBlock, + appendMediaToMessage, + ensureMessageMediaIsArray, + getMediaDisplay, + getMediaIndex, + swipe: { + left: swipe_left, + right: swipe_right, + show: showSwipeButtons, + hide: hideSwipeButtons, + refresh: refreshSwipeButtons, + isAllowed: () => isSwipingAllowed, + }, + variables: { + local: { + get: getLocalVariable, + set: setLocalVariable, + }, + global: { + get: getGlobalVariable, + set: setGlobalVariable, + }, + }, + loadWorldInfo, + saveWorldInfo, + reloadWorldInfoEditor: reloadEditor, + updateWorldInfoList, + convertCharacterBook, + getWorldInfoPrompt, + CONNECT_API_MAP, + getTextGenServer, + extractMessageFromData, + getPresetManager, + getChatCompletionModel, + printMessages, + clearChat, + ChatCompletionService, + TextCompletionService, + ConnectionManagerRequestService, + updateReasoningUI, + parseReasoningFromString, + unshallowCharacter, + unshallowGroupMembers, + openThirdPartyExtensionMenu, + symbols: { + ignore: IGNORE_SYMBOL, + }, + }; +} \ No newline at end of file diff --git a/sourcetype.json b/sourcetype.json new file mode 100644 index 0000000..77cb36d --- /dev/null +++ b/sourcetype.json @@ -0,0 +1,5 @@ +{ + "CHAT_LOG": 1, + "CHARACTER_CARD": 2, + "WORLD_BOOK": 3 +}