diff --git a/inc/js/api-functions.mjs b/inc/js/api-functions.mjs index 771a7c5..d342d44 100644 --- a/inc/js/api-functions.mjs +++ b/inc/js/api-functions.mjs @@ -144,30 +144,6 @@ async function keyValidation(ctx){ // from openAI data: memberCoreData, } } -/** - * All functionality related to a library. Note: Had to be consolidated, as openai GPT would only POST. - * @module - * @public - * @param {Koa} ctx - Koa Context object - * @returns {Koa} Koa Context object - */ -async function library(ctx){ - await mAPIKeyValidation(ctx) - const { - assistantType, - mbr_id, - library = ctx.request?.body?.library - ?? ctx.request?.body - ?? {} - } = ctx.state - const _library = await ctx.MyLife.library(mbr_id, assistantType, library) - ctx.status = 200 // OK - ctx.body = { - library: _library, - message: `library function(s) completed successfully.`, - success: true, - } -} /** * Logout function for member. * @param {Koa} ctx - Koa Context object @@ -236,21 +212,6 @@ async function story(ctx){ message: 'Story submitted successfully.', } } -/** - * Management of Member Story Libraries. Note: Key validation is performed in library(). Story library may have additional functionality inside of core/MyLife - * @param {Koa} ctx - Koa Context object - * @returns {Koa} Koa Context object. Body = { data: library, success: boolean, message: string } - */ -async function storyLibrary(ctx){ - const { id, form='biographer' } = ctx.request?.body??{} - const type = 'story' // force constant - ctx.state.library = { - id, - type, - form, - } - const _library = await library(ctx) // returns ctx.body -} /** * Validates api token * @module @@ -361,11 +322,9 @@ export { experiences, experiencesLived, keyValidation, - library, logout, register, story, - storyLibrary, tokenValidation, upload, } \ No newline at end of file diff --git a/inc/js/core.mjs b/inc/js/core.mjs index cd3f284..bedf297 100644 --- a/inc/js/core.mjs +++ b/inc/js/core.mjs @@ -279,28 +279,6 @@ class MyLife extends Organization { // form=server async hostedMembers(validations){ return await this.factory.hostedMembers(validations) } - /** - * Submits a request for a library item from MyLife via API. - * @public - * @param {string} mbr_id - Requesting Member id. - * @param {string} assistantType - String name of assistant type. - * @param {string} library - Library entry with or without `items`. - * @returns {object} - The library document from Cosmos. - */ - async library(mbr_id, assistantType='personal-avatar', library){ - const { id, type, } = library - library.assistantType = assistantType - library.id = this.globals.isValidGuid(id) - ? id - : this.globals.newGuid - library.mbr_id = mbr_id - library.type = type - ?? assistantType - const _library = this.globals.stripCosmosFields( - await this.factory.library(library) - ) - return _library - } /** * Registers a new candidate to MyLife membership * @public diff --git a/inc/js/memory-functions.mjs b/inc/js/memory-functions.mjs index acdc4d9..473a23f 100644 --- a/inc/js/memory-functions.mjs +++ b/inc/js/memory-functions.mjs @@ -8,6 +8,14 @@ import { async function collectMemory(ctx){ // @todo - implement memory collection } +async function endMemory(ctx){ + const { iid, } = ctx.params + const { Globals, MyLife, } = ctx + if(!Globals.isValidGuid(iid)) + return ctx.throw(400, 'Invalid Item ID') + const { avatar, } = ctx.state + ctx.body = await avatar.endMemory(iid) +} async function improveMemory(ctx){ const { iid, } = ctx.params const { Globals, MyLife, } = ctx @@ -48,6 +56,7 @@ async function livingMemory(ctx){ export { collectMemory, improveMemory, + endMemory, reliveMemory, livingMemory, } \ No newline at end of file diff --git a/inc/js/mylife-agent-factory.mjs b/inc/js/mylife-agent-factory.mjs index bda1b74..07a6b6c 100644 --- a/inc/js/mylife-agent-factory.mjs +++ b/inc/js/mylife-agent-factory.mjs @@ -45,8 +45,8 @@ const mMyLifeTeams = [ { active: true, allowCustom: true, - allowedTypes: ['artworks', 'editor', 'idea', 'library', 'marketing'], - defaultTypes: ['artworks', 'idea', 'library',], + allowedTypes: ['artworks', 'editor', 'idea', 'marketing'], + defaultTypes: ['artworks', 'idea',], description: 'The Creative Team is dedicated to help you experience productive creativity sessions.', id: '84aa50ca-fb64-43d8-b140-31d2373f3cd2', name: 'creative', @@ -65,8 +65,8 @@ const mMyLifeTeams = [ { active: true, allowCustom: true, - allowedTypes: ['diary', 'journaler', 'library', 'personal-biographer',], - defaultTypes: ['personal-biographer', 'library',], + allowedTypes: ['diary', 'journaler', 'personal-biographer',], + defaultTypes: ['personal-biographer',], description: 'The Memoir Team is dedicated to help you document your life stories, experiences, thoughts, and feelings.', id: 'a261651e-51b3-44ec-a081-a8283b70369d', name: 'memoir', @@ -95,8 +95,8 @@ const mMyLifeTeams = [ { active: false, allowCustom: true, - allowedTypes: ['library', 'note', 'poem', 'quote', 'religion',], - defaultTypes: ['library', 'quote', 'religion',], + allowedTypes: ['note', 'poem', 'quote', 'religion',], + defaultTypes: ['quote', 'religion',], description: 'The Spirituality Team is dedicated to help you creatively explore your spiritual side.', id: 'bea7bb4a-a339-4026-ad1c-75f604dc3349', name: 'sprituality', @@ -105,8 +105,8 @@ const mMyLifeTeams = [ { active: true, allowCustom: true, - allowedTypes: ['data-ownership', 'investment', 'library', 'ubi',], - defaultTypes: ['library', 'ubi'], + allowedTypes: ['data-ownership', 'investment', 'ubi',], + defaultTypes: ['ubi'], description: 'The Universal Basic Income (UBI) Team is dedicated to helping you tailor your MyLife revenue streams based upon consensual access to your personal MyLife data.', id: '8a4d7340-ac62-40f1-8c77-f17c68797925', name: 'ubi', @@ -398,54 +398,6 @@ class BotFactory extends EventEmitter{ async help(thread_id, bot_id, helpRequest, avatar){ return await mHelp(thread_id, bot_id, helpRequest, this, avatar) } - /** - * Gets, creates or updates Library in Cosmos. - * @todo - institute bot for library mechanics. - * @public - * @param {Object} _library - The library object, including items to be added to/updated in member's library. - * @returns {object} - The library. - */ - async library(_library){ - const updatedLibrary = await mLibrary(this, _library) - // test the type/form of Library - switch(updatedLibrary.type){ - case 'story': - if(updatedLibrary.form==='biographer'){ - // inflate and update library with stories - const stories = ( await this.stories(updatedLibrary.form) ) - .filter(story=>!this.globals.isValidGuid(story?.library_id)) - .map(story=>{ - const { mbr_name, newGuid } = this - const { author=mbr_name, id=newGuid, title=mbr_name, } = story - story = { - ...story, - author, - id, - title, - } - return mInflateLibraryItem(story, updatedLibrary.id, mbr_id) - }) - updatedLibrary.items = [ - ...updatedLibrary.items, - ...stories, - ] - /* update stories (no await) */ - stories.forEach(story=>this.dataservices.patch( - story.id, - { library_id: updatedLibrary.id }, - )) - /* update library (no await) */ - this.dataservices.patch( - updatedLibrary.id, - { items: updatedLibrary.items }, - ) - } - break - default: - break - } - return updatedLibrary - } /** * Allows member to reset passphrase. * @param {string} passphrase @@ -468,7 +420,6 @@ class BotFactory extends EventEmitter{ } /** * Gets a collection of stories of a certain format. - * @todo - currently disconnected from a library, but no decisive mechanic for incorporating library shell. * @param {string} form - The form of the stories to retrieve. * @returns {object[]} - The stories. */ @@ -770,21 +721,6 @@ class AgentFactory extends BotFactory { isSession(_session){ // when unavailable from general schemas return (_session instanceof mSchemas.session) } - /** - * Adds or updates a library with items outlined in request `library.items` array. Note: currently the override for `botFactory` function .library which returns a library item from the database. - * @todo: finalize mechanic for overrides - * @param {Object} _library - The library object, including items to be added to member's library. - * @returns {Object} - The complete library object from Cosmos. - */ - async library(_library){ - /* hydrate library micro-bot */ - const { bot_id, mbr_id } = _library - const _microBot = await this.libraryBot(bot_id, mbr_id) - return await _microBot.library(_library) - } - async libraryBot(id){ - return await this.bot(id, 'library') - } /** * Saves a completed lived experience to MyLife. * @param {Object} experience - The Lived Experience Object to save. @@ -1530,118 +1466,6 @@ async function mHelp(thread_id, bot_id, helpRequest, factory, avatar){ const response = await mLLMServices.help(thread_id, bot_id, helpRequest, factory, avatar) return response } -/** - * Inflates library item with required values and structure. Object structure expected from API, librayItemItem in JSON. - * root\inc\json-schemas\bots\library-bot.json - * @param {object} _item - Library item (API) object. { author: string, enjoymentLevel: number, format: string, insights: string, personalImpact: string, title: string, whenRead: string } - * @param {string} _library_id - Library id - * @param {string} _mbr_id - Member id - * @returns {object} - Library-item object. { assistantType: string, author_match: array, being: string, date: string, format: string, id: string, item: object, library_id: string, object_id: string, title_match: string - */ -function mInflateLibraryItem(_item, _library_id, _mbr_id){ - const _id = _item.id??mNewGuid() - return { - assistantType: _item.assistantType??'mylife-library', - author_match: (_item.author??'unknown-author') - .trim() - .toLowerCase() - .split(' '), - being: 'library-item', - date: _item.date??new Date().toISOString(), - format: _item.format??_item.form??_item.type??'book', - id: _id, - item: {..._item, id: _id }, - library_id: _library_id, - object_id: _library_id, - title_match: (_item.title??'untitled') - .trim() - .toLowerCase(), - } -} -/** - * Hydrates library and returns library object. - * @module - * @private - * @param {BotFactory} factory - BotFactory object - * @param {object} _library - Library object - * @returns {object} - Library object - */ -async function mLibrary(factory, _library){ - // @todo: micro-avatar for representation of bot(s) - // @todo: Bot class with extension for things like handling libraries - // @todo: bot-extenders so that I can get this functionality into that object context - /* constants */ - const { assistantType, form='collection', id, items: _libraryItems=[], mbr_id, type } = _library - const _avatar_id = factory.avatarId - // add/get correct library; default to core (object_id=avatar_id && type) - /* parse and cast _libraryItems */ - let _libraryCosmos = await factory.dataservices.library(id, _avatar_id, type, form) - // @dodo: currently only book/story library items are supported - if(!_libraryCosmos){ // create when undefined - // @todo: microbot should have a method for these - const _library_id = factory.newGuid - _libraryCosmos = { - being: `library`, - form: form, - id: _library_id, - items: _libraryItems.map(_item=>mInflateLibraryItem(_item, _library_id, mbr_id)), - mbr_id: mbr_id, - name: ['library',type,form,_avatar_id].join('_'), - object_id: _avatar_id, - type: type, - } - factory.dataservices.pushItem(_libraryCosmos) // push to Cosmos - } else { - // @todo: manage multiple libraries - const { id: _library_id, items: _storedLibraryItems } = _libraryCosmos - _libraryItems.forEach(_item => { - _item = mInflateLibraryItem(_item,_library_id, mbr_id) - const matchIndex = _storedLibraryItems.findIndex(storedItem => { // Find the index of the item in the stored library items that matches the criteria - if(storedItem.id===_item.id || storedItem.title_match===_item.title_match) - return true - const storedAuthorWords = storedItem.author_match - const incomingAuthorWords = _item.author_match - const authorMatches = (() => { - if (storedAuthorWords.length === 1 || incomingAuthorWords.length === 1) { // If either author array has a length of 1, check for any matching word - return storedAuthorWords.some(word => incomingAuthorWords.includes(word)) - } else { // If both have 2+ lengths, ensure at least 2 items match - const matches = storedAuthorWords.filter(word => incomingAuthorWords.includes(word)) - return matches.length >= 2 - } - })() - if (authorMatches && matchIndex !== -1) { // Mutate the author to the one with more details if needed - if (incomingAuthorWords.length > storedAuthorWords.length) { - _storedLibraryItems[matchIndex].author_match = incomingAuthorWords // Update to more detailed author - } - } - return authorMatches - }) - if (matchIndex!== -1) { // If a match is found, update the entry - _storedLibraryItems[matchIndex] = { - ..._storedLibraryItems[matchIndex], // Keep existing properties - ..._item, // Overwrite and add new properties from _item - id: _storedLibraryItems[matchIndex].id - ?? factory.newGuid, // if for some reason object hasn't id - object_id: _library_id, - type: _item.type - ?? 'book', - } - } else { - _storedLibraryItems.push({ // If no match is found, add the item to the library - ..._item, - id: _item.id??factory.newGuid, // Ensure each item has a unique ID - mbr_id: factory.mbr_id, - object_id: _library_id, - type: _item.type??'book', - }) - } - }) - // save library to Cosmos @todo: microbot should have a method for this - _libraryCosmos.items = _storedLibraryItems - factory.dataservices.patch(_library_id, {items: _libraryCosmos.items}) - } - return _libraryCosmos -} /** * Returns whether or not the factory is the MyLife server, as various functions are not available to the server and some _only_ to the server. * @param {string} _mbr_id diff --git a/inc/js/mylife-avatar.mjs b/inc/js/mylife-avatar.mjs index 31ffd3c..6921d36 100644 --- a/inc/js/mylife-avatar.mjs +++ b/inc/js/mylife-avatar.mjs @@ -216,9 +216,24 @@ class Avatar extends EventEmitter { throw new Error('MyLife avatar cannot delete items.') return await this.#factory.deleteItem(id) } + /** + * End a memory. + * @async + * @public + * @todo - save conversation fragments + * @param {Guid} id - The id of the memory to end. + * @returns {boolean} - true if memory ended successfully. + */ async endMemory(id){ - const item = this.relivingMemories.find(item=>item.id===id) - /* save conversation fragments */ + // @stub - save conversation fragments */ + const { relivingMemories, } = this + const index = relivingMemories.findIndex(item=>item.id===id) + if(index>=0){ + const removedMemory = relivingMemories.splice(index, 1) + if(!removedMemory.length) + return false + console.log('item removed', removedMemory?.[0] ?? `index: ${ index } failed`) + } return true } /** @@ -1318,7 +1333,7 @@ async function mBot(factory, avatar, bot){ /* create or update bot special properties */ const { thread_id, type, } = originBot // @stub - `bot_id` cannot be updated through this mechanic if(!thread_id?.length && !avatar.isMyLife){ // add thread_id to relevant bots - const excludeTypes = ['library', 'custom'] // @stub - custom mechanic? + const excludeTypes = ['collection', 'library', 'custom'] // @stub - custom mechanic? if(!excludeTypes.includes(type)){ const conversation = await avatar.createConversation() updatedBot.thread_id = conversation.thread_id // triggers `factory.updateBot()` @@ -2003,7 +2018,7 @@ async function mInit(factory, llmServices, avatar, bots, assetAgent){ } } /* bots */ // @stub - determine by default or activated team - requiredBotTypes.push('library', 'personal-biographer') // default memory team + requiredBotTypes.push('personal-biographer') // default memory team } bots.push(...await factory.bots(avatar.id)) await Promise.all( diff --git a/inc/js/mylife-data-service.js b/inc/js/mylife-data-service.js index 4cd2752..971a6b7 100644 --- a/inc/js/mylife-data-service.js +++ b/inc/js/mylife-data-service.js @@ -15,7 +15,7 @@ class Dataservices { * Identifies currently available selection sub-types (i.e., `being`=@var) for the data service. * @private */ - #collectionTypes = ['chat', 'conversation', 'entry', 'lived-experience', 'file', 'library', 'story'] + #collectionTypes = ['chat', 'conversation', 'entry', 'lived-experience', 'file', 'story'] /** * Represents the core functionality of the data service. This property * objectifies core data to make it more manageable and structured, @@ -233,14 +233,6 @@ class Dataservices { async collectionFiles(){ return await this.getItems('file') } - /** - * Proxy to retrieve library items. - * @param {string} form - The form of the library items (such as: personal, album,). - * @returns {array} - The library items. - */ - async collectionLibraries(form){ - return await this.getItems('library') - } /** * Proxy to retrieve biographical story items. * @returns {array} - The biographical story items. @@ -547,29 +539,6 @@ class Dataservices { async hostedMembers(validations){ return await this.datamanager.hostedMembers(validations) } - /** - * Gets library from database. - * @async - * @public - * @param {string} _library_id - The unique identifier for the library. - * @param {string} _object_id - The unique identifier for the underlying avatar. - * @param {string} _type - The type of the library. - * @param {string} _form - The form of the library. - * @returns {Array} - array of library items added to member's library. - */ - async library(_library_id, _object_id, _type, _form){ - return ( await this.getItem(_library_id) ) - ?? ( await this.getItemByFields( - 'library', - [ - { name: '@object_id', value: _object_id }, - { name: '@type', value: _type }, - { name: '@form', value: _form }, - ].filter(_=>_?.value!==undefined), - undefined, - this.mbr_id, - ) ) - } /** * Patches an item by its ID with the provided data. * @async diff --git a/inc/js/routes.mjs b/inc/js/routes.mjs index 5c06b8b..e2e7235 100644 --- a/inc/js/routes.mjs +++ b/inc/js/routes.mjs @@ -31,6 +31,7 @@ import { } from './functions.mjs' import { collectMemory, + endMemory, improveMemory, reliveMemory, livingMemory, @@ -45,11 +46,9 @@ import { experiences, experiencesLived, keyValidation, - library, logout as apiLogout, register, story, - storyLibrary, tokenValidation, } from './api-functions.mjs' // variables @@ -87,9 +86,7 @@ _apiRouter.patch('/experiences/:mid/experience/:eid/navigation', experienceNavig _apiRouter.patch('/experiences/:mid/experience/:eid', experience) // **note**: This line should be the last one alphabetically due to the wildcard. _apiRouter.post('/challenge/:mid', challenge) _apiRouter.post('/keyValidation/:mid', keyValidation) -_apiRouter.post('/library/:mid', library) _apiRouter.post('/register', register) -_apiRouter.post('/story/library/:mid', storyLibrary) /* ordered first for path rendering */ _apiRouter.post('/story/:mid', story) _apiRouter.post('/upload', upload) _apiRouter.post('/upload/:mid', upload) @@ -111,6 +108,7 @@ _memberRouter.patch('/experience/:eid', experience) _memberRouter.patch('/experience/:eid/end', experienceEnd) _memberRouter.patch('/experience/:eid/manifest', experienceManifest) _memberRouter.patch('/memory/relive/:iid', reliveMemory) +_memberRouter.patch('/memory/end/:iid', endMemory) _memberRouter.patch('/memory/living/:iid', livingMemory) _memberRouter.post('/', chat) _memberRouter.post('/bots', bots) diff --git a/inc/json-schemas/bot.json b/inc/json-schemas/bot.json index 23fb6d4..8df8f79 100644 --- a/inc/json-schemas/bot.json +++ b/inc/json-schemas/bot.json @@ -72,7 +72,7 @@ }, "type": { "description": "type of bot", - "enum": ["biographer", "companion", "identity", "library", "relationship", "self"], + "enum": ["personal-avatar", "personal-biographer", "journaler"], "type": "string", "$comment": "given together with purpose helps create tailored instructions for bot; which can be bypassed, and certainly should in localdev cases" } diff --git a/inc/json-schemas/bots/library-bot.json b/inc/json-schemas/deprecated/library-bot.json similarity index 100% rename from inc/json-schemas/bots/library-bot.json rename to inc/json-schemas/deprecated/library-bot.json diff --git a/server.js b/server.js index a43a288..424df7e 100644 --- a/server.js +++ b/server.js @@ -15,7 +15,7 @@ import chalk from 'chalk' import MyLife from './inc/js/mylife-agent-factory.mjs' // constants/variables // @todo - parse environment variables in Globals and then have them available via as values -const version = '0.0.18' +const version = '0.0.19' const app = new Koa() const port = JSON.parse(process.env.PORT ?? '3000') const __filename = fileURLToPath(import.meta.url) diff --git a/views/assets/css/bots.css b/views/assets/css/bots.css index d0fd297..cb60b96 100644 --- a/views/assets/css/bots.css +++ b/views/assets/css/bots.css @@ -54,7 +54,8 @@ .bot { /* along with widget */ align-items: stretch; } -.bot-container { +.bot-container, +.collections-container { cursor: pointer; display: flex; flex: 1 1 auto; /* Allow it to grow and shrink */ @@ -104,11 +105,8 @@ padding: 0.8rem; } /* bot */ -.bot-icon { - background-image: radial-gradient(circle at 0.1rem 0.1rem, #eaedff 0%, #1e3b4e 40%, rgb(60 162 213) 80%, #404553 100%); - border: rgba(0, 0, 0, 0.7) 0.12rem solid !important; - border-radius: 50%; - box-shadow: 0.6px 2px 3px rgba(0, 0, 0, 0.6); +.bot-icon, +.collections-spacer { display: flex; flex: 0 0 auto; height: 1.5rem; @@ -116,6 +114,12 @@ position: relative; width: 1.5rem; } +.bot-icon { + background-image: radial-gradient(circle at 0.1rem 0.1rem, #eaedff 0%, #1e3b4e 40%, rgb(60 162 213) 80%, #404553 100%); + border: rgba(0, 0, 0, 0.7) 0.12rem solid !important; + border-radius: 50%; + box-shadow: 0.6px 2px 3px rgba(0, 0, 0, 0.6); +} .bot-icon.active { animation: _statusBlink 6s ease-in-out infinite; background-image: radial-gradient(circle at 0.1rem 0.1rem, #dfe731 0%, #8b9d3f 40%, rgb(237 255 74) 80%, #000000 100%); @@ -200,7 +204,8 @@ .bot-passphrase { width: 100%; } -.bot-status { +.bot-status, +.collections-titlebar { align-items: center; background-color: #007BFF; border: rgba(0, 0, 0, 0.1) 0.12rem solid; @@ -213,7 +218,8 @@ justify-content: flex-start; margin: 0.2rem 0.2rem 0em 0.2rem; } -.bot-title { +.bot-title, +.collections-title { color: aliceblue; display: flex; flex: 1 0 auto; diff --git a/views/assets/css/chat.css b/views/assets/css/chat.css index 8c477b3..01e17e6 100644 --- a/views/assets/css/chat.css +++ b/views/assets/css/chat.css @@ -297,9 +297,14 @@ button.chat-submit:disabled { font-size: .75em; } .member-bubble a, +.relive-bubble a, .user-bubble a { color: #333333; /* Dark grey for contrast and easy reading */ } +.relive-bubble { + background-color: rgba(255, 173, 122, 0.85); /* A darker shade of blue for the agent */ + color: black; +} .spinner-green-glow { background-image: radial-gradient(circle, #4b94a1, #d2f35e); filter: blur(.2em); /* Adjust the pixel value for the level of blur */ diff --git a/views/assets/html/_bots.html b/views/assets/html/_bots.html index f230db1..88c17ca 100644 --- a/views/assets/html/_bots.html +++ b/views/assets/html/_bots.html @@ -205,19 +205,19 @@ -
-
-
-
Scrapbook
-
+
+
+
+
Scrapbook
+
-