diff --git a/inc/js/api-functions.mjs b/inc/js/api-functions.mjs index 6ab35d2..771a7c5 100644 --- a/inc/js/api-functions.mjs +++ b/inc/js/api-functions.mjs @@ -57,7 +57,8 @@ async function experience(ctx){ mAPIKeyValidation(ctx) const { MemberSession, } = ctx.state const { eid, } = ctx.params - ctx.body = await MemberSession.experience(eid, ctx.request.body) + const { memberInput, } = ctx.request.body + ctx.body = await MemberSession.experience(eid, memberInput) } /** * Request to end an active Living-Experience for member. diff --git a/inc/js/memory-functions.mjs b/inc/js/memory-functions.mjs index 3131c95..acdc4d9 100644 --- a/inc/js/memory-functions.mjs +++ b/inc/js/memory-functions.mjs @@ -9,12 +9,13 @@ async function collectMemory(ctx){ // @todo - implement memory collection } async function improveMemory(ctx){ - const { iid } = ctx.params + const { iid, } = ctx.params const { Globals, MyLife, } = ctx - const { avatar, } = ctx.state if(!Globals.isValidGuid(iid)) return ctx.throw(400, 'Invalid Item ID') - ctx.body = await avatar.reliveMemory(iid) + const { avatar, } = ctx.state + const { memberInput, } = ctx.request.body + ctx.body = await avatar.reliveMemory(iid, memberInput) } /** * Reliving a memory is a unique MyLife `experience` that allows a user to relive a memory from any vantage they choose. The bot by default will: @@ -24,10 +25,11 @@ async function improveMemory(ctx){ async function reliveMemory(ctx){ const { iid } = ctx.params const { Globals, MyLife, } = ctx - const { avatar, } = ctx.state if(!Globals.isValidGuid(iid)) return ctx.throw(400, 'Invalid Item ID') - ctx.body = await avatar.reliveMemory(iid) + const { avatar, } = ctx.state + const { memberInput, } = ctx.request.body + ctx.body = await avatar.reliveMemory(iid, memberInput) } /** * Living a shared memory is a unique MyLife `experience` that allows a user to relive a memory from any vantage the "author/narrator" chooses. In fact, much of the triggers and dials on how to present the experience of a shared memory is available and controlled by the member, and contained and executed by the biographer bot for the moment through this func6ion. Ultimately the default bot could be switched, in which case, information retrieval may need ways to contextualize pushbacks (floabt, meaning people asking questions about the memory that are not answerable by the summar itself, and 1) _may_ be answerable by another bot, such as biogbot, or 2) is positioned as a piece of data to "improve" or flesh out memories... Remember on this day in 2011, what did you have to eat on the boardwalk? Enquiring minds want to know!) diff --git a/inc/js/mylife-avatar.mjs b/inc/js/mylife-avatar.mjs index 374e490..99b9f8d 100644 --- a/inc/js/mylife-avatar.mjs +++ b/inc/js/mylife-avatar.mjs @@ -446,15 +446,16 @@ class Avatar extends EventEmitter { /** * Reliving a memory is a unique MyLife `experience` that allows a user to relive a memory from any vantage they choose. * @param {Guid} iid - The item id. + * @param {string} memberInput - Any member input. * @returns {Object} - livingMemory engagement object (i.e., includes frontend parameters for engagement as per instructions for included `portrayMemory` function in LLM-speak): { error, inputs, itemId, messages, processingBotId, success, } */ - async reliveMemory(iid){ + async reliveMemory(iid, memberInput){ const item = await this.#factory.item(iid) const { id, } = item if(!id) throw new Error(`item does not exist in member container: ${ iid }`) /* develop narration */ - const narration = await mReliveMemoryNarration(this, this.#factory, this.#llmServices, this.biographer, item) + const narration = await mReliveMemoryNarration(this, this.#factory, this.#llmServices, this.biographer, item, memberInput) return narration // include any required .map() pruning } /** @@ -2060,11 +2061,12 @@ function mPruneMessages(bot, messageArray, type='chat', processStartTime=Date.no * @returns {Promise} - The reliving memory object for frontend to execute. */ async function mReliveMemoryNarration(avatar, factory, llm, bot, item, memberInput='NEXT'){ + console.log('mReliveMemoryNarration::start', item.id, memberInput) const { relivingMemories, } = avatar const { bot_id, id: botId, } = bot const { id, } = item const processStartTime = Date.now() - let message = `## relive memory itemId: ${id}\n` + let message = `## relive memory itemId: ${ id }\n` let relivingMemory = relivingMemories.find(reliving=>reliving.item.id===id) if(!relivingMemory){ /* create new activated reliving memory */ const conversation = await avatar.createConversation('memory', undefined, botId, false) @@ -2078,10 +2080,11 @@ async function mReliveMemoryNarration(avatar, factory, llm, bot, item, memberInp thread_id, } relivingMemories.push(relivingMemory) - console.log('mReliveMemoryNarration::new reliving memory', `created for memory: ${ id }`, item, bot_id, thread_id) + console.log(`mReliveMemoryNarration::new reliving memory: ${ id }`) } else /* opportunity for member interrupt */ - message += `MEMBER: ${memberInput}\n` + message += `MEMBER INPUT: ${ memberInput }\n` const { conversation, thread_id, } = relivingMemory + console.log(`mReliveMemoryNarration::reliving memory: ${ id }`, message) let messages = await mCallLLM(llm, conversation, message, factory, avatar) conversation.addMessages(messages) /* frontend mutations */ diff --git a/inc/js/mylife-llm-services.mjs b/inc/js/mylife-llm-services.mjs index 92fabe2..713fff5 100644 --- a/inc/js/mylife-llm-services.mjs +++ b/inc/js/mylife-llm-services.mjs @@ -350,7 +350,6 @@ async function mRunFunctions(openai, run, factory, avatar){ // add avatar ref case 'getsummary': case 'get_summary': case 'get summary': - console.log('mRunFunctions()::getSummary::start', item) let { summary, } = item ?? {} if(!summary?.length){ action = `error getting summary for itemId: ${ itemId ?? 'missing itemId' } - halt any further processing and instead ask user to paste summary into chat and you will continue from there to incorporate their message.` @@ -360,7 +359,7 @@ async function mRunFunctions(openai, run, factory, avatar){ // add avatar ref success = true } confirmation.output = JSON.stringify({ action, itemId, success, summary, }) - console.log('mRunFunctions()::getSummary::confirmation', confirmation) + console.log('mRunFunctions()::getSummary::confirmation', itemId) return confirmation case 'hijackattempt': case 'hijack_attempt': @@ -424,14 +423,14 @@ async function mRunFunctions(openai, run, factory, avatar){ // add avatar ref case 'updatesummary': case 'update_summary': case 'update summary': - console.log('mRunFunctions()::updatesummary::start', item, itemId, toolArguments) + console.log('mRunFunctions()::updatesummary::start', itemId) const { summary: updatedSummary, } = toolArguments // remove await once confirmed updates are connected await factory.updateItem({ id: itemId, summary: updatedSummary, }) action=`confirm success and present updated summary to member` success = true confirmation.output = JSON.stringify({ action, success, }) - console.log('mRunFunctions()::getSummary::confirmation', confirmation) + console.log('mRunFunctions()::updatesummary::end', itemId, updatedSummary) return confirmation default: console.log(`ERROR::mRunFunctions()::toolFunction not found: ${ name }`, toolFunction) diff --git a/views/assets/css/main.css b/views/assets/css/main.css index 6262fa0..3892e96 100644 --- a/views/assets/css/main.css +++ b/views/assets/css/main.css @@ -211,6 +211,48 @@ body { .main-content h2 { font-size: 20px; } +/* MyLife Memories */ +.memory-input { + background-color: aliceblue; + border: 1px solid #ccc; + border-radius: 0.4rem; + color: black; + display: flex; + font-size: 0.9rem; + flex: 1 0 auto; + margin: 0.25rem; + overflow: hidden; + padding: 0.25rem; + resize: none; +} +.memory-input-container { + align-self: center; + align-content: center; + background-color: sienna; + border-radius: 0.4rem; + display: flex; + flex: 1 0 auto; + gap: 0.25rem; + justify-content: center; + margin: 0.25rem; + max-height: 5rem; + max-width: 90%; + padding: 0.25rem; + width: 100%; +} +.memory-input-button { + background-color: #741237; + border: 1px solid #ccc; + border-radius: 0.4rem; + color: #fff; + cursor: pointer; + display: flex; + flex: 0 1 5%; + justify-content: center; + margin: 0.5rem; + padding: 0.5rem; + +} /* MyLife About, Privacy Policy */ .about-container, .privacy-container { display: flex; diff --git a/views/assets/js/bots.mjs b/views/assets/js/bots.mjs index 2b4c04d..c74ba99 100644 --- a/views/assets/js/bots.mjs +++ b/views/assets/js/bots.mjs @@ -1125,24 +1125,51 @@ async function mReliveMemory(event){ const popupClose = document.getElementById(`popup-close_${ id }`) if(popupClose) popupClose.click() - const { messages, success, } = await mReliveMemoryRequest(id) + const { command, parameters, messages, success, } = await mReliveMemoryRequest(id, inputContent) if(success){ addMessages(messages) - // create input - create this function in member, as it will display it in chat and pipe it back here as below - const input = document.createElement('button') - input.addEventListener('click', mReliveMemory, { once: true }) - input.dataset.id = id - if(inputContent?.length) - input.dataset.inputContent = inputContent - input.textContent = 'next' + // @todo - create this function in member, as it will display it in chat and pipe it back here as below + const input = document.createElement('div') + // id alone is not unique!; input.id = `input_${ id }` + input.name = `input_${ id }` + input.classList.add('memory-input-container') + const inputContent = document.createElement('textarea') + inputContent.classList.add('memory-input') + inputContent.name = `memory-input_${ id }` + const inputSubmit = document.createElement('button') + inputSubmit.classList.add('memory-input-button') + inputSubmit.dataset.id = id + input.appendChild(inputContent) + input.appendChild(inputSubmit) + inputContent.addEventListener('input', event=>{ + const { value, } = event.target + inputSubmit.dataset.inputContent = value + inputSubmit.textContent = value.length > 2 + ? 'update' + : 'next' + }) + inputSubmit.addEventListener('click', mReliveMemory, { once: true }) addInput(input) } else throw new Error(`Failed to fetch memory for relive request.`) } -async function mReliveMemoryRequest(id){ +/** + * + * @param {Guid} id - The memory collection item id. + * @param {string} memberInput - The member's updates to the memory. + * @returns + */ +async function mReliveMemoryRequest(id, memberInput){ + console.log('Relive memory:', id, memberInput) try { const url = window.location.origin + '/members/memory/relive/' + id - let response = await fetch(url, { method: 'PATCH' }) + let response = await fetch(url, { + body: memberInput?.length ? JSON.stringify({ memberInput, }) : null, + method: 'PATCH', + headers: { + 'Content-Type': 'application/json' + } + }) if(!response.ok) throw new Error(`HTTP error! Status: ${response.status}`) response = await response.json() @@ -1164,11 +1191,11 @@ async function mSetBot(bot){ ? 'PUT' // update : 'POST' // create let response = await fetch(url, { - method: method, + body: JSON.stringify(bot), headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(bot) + method: method, }) if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`) diff --git a/views/assets/js/experience.mjs b/views/assets/js/experience.mjs index 659c541..a686106 100644 --- a/views/assets/js/experience.mjs +++ b/views/assets/js/experience.mjs @@ -718,12 +718,12 @@ function mEventInput(){ * @returns {void} */ async function mEvents(memberInput){ - const response = await fetch(`/members/experience/${mExperience.id}`, { + const response = await fetch(`/members/experience/${ mExperience.id }`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', }, - body: memberInput ? JSON.stringify(memberInput) : null, + body: memberInput ? JSON.stringify({ memberInput, }) : null, }) if(!response.ok) throw new Error(`HTTP error! Status: ${response.status}`)