2023-08-01 14:53:10 +02:00
import { chat _metadata , callPopup , saveSettingsDebounced , is _send _press } from "../../../script.js" ;
2023-07-20 19:32:15 +02:00
import { getContext , extension _settings , saveMetadataDebounced } from "../../extensions.js" ;
import {
substituteParams ,
eventSource ,
event _types ,
generateQuietPrompt ,
} from "../../../script.js" ;
import { registerSlashCommand } from "../../slash-commands.js" ;
2023-08-01 14:53:10 +02:00
import { waitUntilCondition } from "../../utils.js" ;
import { is _group _generating , selected _group } from "../../group-chats.js" ;
2023-07-20 19:32:15 +02:00
const MODULE _NAME = "Objective"
2023-07-29 06:21:55 +02:00
let taskTree = null
2023-07-20 19:32:15 +02:00
let globalTasks = [ ]
let currentChatId = ""
2023-07-29 06:21:55 +02:00
let currentObjective = null
2023-07-20 19:32:15 +02:00
let currentTask = null
let checkCounter = 0
2023-07-31 15:24:51 +02:00
let lastMessageWasSwipe = false
2023-07-20 19:32:15 +02:00
2023-07-25 15:36:27 +02:00
const defaultPrompts = {
2023-07-20 19:32:15 +02:00
"createTask" : ` Pause your roleplay and generate a list of tasks to complete an objective. Your next response must be formatted as a numbered list of plain text entries. Do not include anything but the numbered list. The list must be prioritized in the order that tasks must be completed.
2023-07-25 15:36:27 +02:00
The objective that you must make a numbered task list for is : [ { { objective } } ] .
The tasks created should take into account the character traits of { { char } } . These tasks may or may not involve { { user } } directly . Be sure to include the objective as the final task .
Given an example objective of 'Make me a four course dinner' , here is an example output :
1. Determine what the courses will be
2. Find recipes for each course
3. Go shopping for supplies with { { user } }
4. Cook the food
5. Get { { user } } to set the table
6. Serve the food
7. Enjoy eating the meal with { { user } }
2023-07-20 19:32:15 +02:00
` ,
"checkTaskCompleted" : ` Pause your roleplay. Determine if this task is completed: [{{task}}].
2023-07-25 15:36:27 +02:00
To do this , examine the most recent messages . Your response must only contain either true or false , nothing other words .
Example output :
true
` ,
'currentTask' : ` Your current task is [{{task}}]. Balance existing roleplay with completing this task. `
2023-07-20 19:32:15 +02:00
}
2023-07-25 15:36:27 +02:00
let objectivePrompts = defaultPrompts
2023-07-20 19:32:15 +02:00
//###############################//
//# Task Management #//
//###############################//
// Return the task and index or throw an error
function getTaskById ( taskId ) {
if ( taskId == null ) {
throw ` Null task id `
}
2023-07-29 06:21:55 +02:00
return getTaskByIdRecurse ( taskId , taskTree )
2023-07-20 19:32:15 +02:00
}
2023-07-29 06:21:55 +02:00
function getTaskByIdRecurse ( taskId , task ) {
if ( task . id == taskId ) {
return task
}
for ( const childTask of task . children ) {
const foundTask = getTaskByIdRecurse ( taskId , childTask ) ;
if ( foundTask != null ) {
return foundTask ;
}
}
return null ;
2023-07-20 19:32:15 +02:00
}
2023-08-20 20:37:17 +02:00
function substituteParamsPrompts ( content , substituteGlobal ) {
2023-07-30 16:15:12 +02:00
content = content . replace ( /{{objective}}/gi , currentObjective . description )
content = content . replace ( /{{task}}/gi , currentTask . description )
2023-07-31 14:56:49 +02:00
if ( currentTask . parent ) {
content = content . replace ( /{{parent}}/gi , currentTask . parent . description )
}
2023-08-20 20:37:17 +02:00
if ( substituteGlobal ) {
content = substituteParams ( content )
}
2023-07-30 16:15:12 +02:00
return content
}
2023-07-20 19:32:15 +02:00
// Call Quiet Generate to create task list using character context, then convert to tasks. Should not be called much.
async function generateTasks ( ) {
2023-07-29 06:21:55 +02:00
2023-08-20 20:37:17 +02:00
const prompt = substituteParamsPrompts ( objectivePrompts . createTask , false ) ;
2023-07-20 19:32:15 +02:00
console . log ( ` Generating tasks for objective with prompt ` )
toastr . info ( 'Generating tasks for objective' , 'Please wait...' ) ;
const taskResponse = await generateQuietPrompt ( prompt )
2023-08-01 14:53:10 +02:00
2023-07-29 06:21:55 +02:00
// Clear all existing objective tasks when generating
currentObjective . children = [ ]
2023-07-20 19:32:15 +02:00
const numberedListPattern = /^\d+\./
// Create tasks from generated task list
for ( const task of taskResponse . split ( '\n' ) . map ( x => x . trim ( ) ) ) {
if ( task . match ( numberedListPattern ) != null ) {
2023-07-29 06:21:55 +02:00
currentObjective . addTask ( task . replace ( numberedListPattern , "" ) . trim ( ) )
2023-07-20 19:32:15 +02:00
}
}
2023-07-29 06:21:55 +02:00
updateUiTaskList ( ) ;
setCurrentTask ( ) ;
2023-08-08 14:49:21 +02:00
console . info ( ` Response for Objective: ' ${ currentObjective . description } ' was \n ' ${ taskResponse } ', \n which created tasks \n ${ JSON . stringify ( currentObjective . children . map ( v => { return v . toSaveState ( ) } ), null, 2)} ` )
toastr . success ( ` Generated ${ currentObjective . children . length } tasks ` , 'Done!' ) ;
2023-07-20 19:32:15 +02:00
}
// Call Quiet Generate to check if a task is completed
async function checkTaskCompleted ( ) {
// Make sure there are tasks
if ( jQuery . isEmptyObject ( currentTask ) ) {
return
}
2023-08-01 14:53:10 +02:00
try {
// Wait for group to finish generating
if ( selected _group ) {
await waitUntilCondition ( ( ) => is _group _generating === false , 1000 , 10 ) ;
}
// Another extension might be doing something with the chat, so wait for it to finish
await waitUntilCondition ( ( ) => is _send _press === false , 30000 , 10 ) ;
} catch {
console . debug ( "Failed to wait for group to finish generating" )
return ;
}
2023-07-20 19:32:15 +02:00
checkCounter = $ ( '#objective-check-frequency' ) . val ( )
2023-07-31 02:15:05 +02:00
toastr . info ( "Checking for task completion." )
2023-07-20 19:32:15 +02:00
2023-08-20 20:37:17 +02:00
const prompt = substituteParamsPrompts ( objectivePrompts . checkTaskCompleted , false ) ;
2023-07-20 19:32:15 +02:00
const taskResponse = ( await generateQuietPrompt ( prompt ) ) . toLowerCase ( )
// Check response if task complete
if ( taskResponse . includes ( "true" ) ) {
2023-07-29 06:21:55 +02:00
console . info ( ` Character determined task ' ${ currentTask . description } is completed. ` )
2023-07-20 19:32:15 +02:00
currentTask . completeTask ( )
} else if ( ! ( taskResponse . includes ( "false" ) ) ) {
console . warn ( ` checkTaskCompleted response did not contain true or false. taskResponse: ${ taskResponse } ` )
} else {
console . debug ( ` Checked task completion. taskResponse: ${ taskResponse } ` )
}
}
2023-07-29 06:21:55 +02:00
function getNextIncompleteTaskRecurse ( task ) {
2023-07-31 02:15:05 +02:00
if ( task . completed === false // Return task if incomplete
&& task . children . length === 0 // Ensure task has no children, it's subtasks will determine completeness
2023-07-31 14:56:49 +02:00
&& task . parentId !== "" // Must have parent id. Only root task will be missing this and we dont want that
2023-07-31 02:15:05 +02:00
) {
2023-07-29 06:21:55 +02:00
return task
}
for ( const childTask of task . children ) {
if ( childTask . completed === true ) { // Don't recurse into completed tasks
continue
}
const foundTask = getNextIncompleteTaskRecurse ( childTask ) ;
if ( foundTask != null ) {
return foundTask ;
}
}
return null ;
}
2023-07-20 19:32:15 +02:00
// Set a task in extensionPrompt context. Defaults to first incomplete
function setCurrentTask ( taskId = null ) {
const context = getContext ( ) ;
2023-07-29 06:21:55 +02:00
// TODO: Should probably null this rather than set empty object
2023-07-20 19:32:15 +02:00
currentTask = { } ;
2023-07-29 06:21:55 +02:00
// Find the task, either next incomplete, or by provided taskId
2023-07-20 19:32:15 +02:00
if ( taskId === null ) {
2023-07-29 06:21:55 +02:00
currentTask = getNextIncompleteTaskRecurse ( taskTree ) || { } ;
2023-07-20 19:32:15 +02:00
} else {
2023-07-29 06:21:55 +02:00
currentTask = getTaskById ( taskId ) ;
2023-07-20 19:32:15 +02:00
}
2023-07-29 06:21:55 +02:00
// Don't just check for a current task, check if it has data
2023-07-20 19:32:15 +02:00
const description = currentTask . description || null ;
if ( description ) {
2023-08-20 20:37:17 +02:00
const extensionPromptText = substituteParamsPrompts ( objectivePrompts . currentTask , true ) ;
2023-08-01 14:53:10 +02:00
2023-07-29 06:21:55 +02:00
// Remove highlights
$ ( '.objective-task' ) . css ( { 'border-color' : '' , 'border-width' : '' } )
// Highlight current task
let highlightTask = currentTask
while ( highlightTask . parentId !== "" ) {
if ( highlightTask . descriptionSpan ) {
2023-08-01 14:53:10 +02:00
highlightTask . descriptionSpan . css ( { 'border-color' : 'yellow' , 'border-width' : '2px' } ) ;
2023-07-29 06:21:55 +02:00
}
const parent = getTaskById ( highlightTask . parentId )
highlightTask = parent
}
// Update the extension prompt
2023-07-20 19:32:15 +02:00
context . setExtensionPrompt ( MODULE _NAME , extensionPromptText , 1 , $ ( '#objective-chat-depth' ) . val ( ) ) ;
console . info ( ` Current task in context.extensionPrompts.Objective is ${ JSON . stringify ( context . extensionPrompts . Objective ) } ` ) ;
} else {
context . setExtensionPrompt ( MODULE _NAME , '' ) ;
console . info ( ` No current task ` ) ;
}
saveState ( ) ;
}
2023-07-29 06:21:55 +02:00
function getHighestTaskIdRecurse ( task ) {
let nextId = task . id ;
for ( const childTask of task . children ) {
const childId = getHighestTaskIdRecurse ( childTask ) ;
if ( childId > nextId ) {
nextId = childId ;
}
2023-07-20 19:32:15 +02:00
}
2023-07-29 06:21:55 +02:00
return nextId ;
2023-07-20 19:32:15 +02:00
}
2023-07-29 06:21:55 +02:00
//###############################//
//# Task Class #//
//###############################//
2023-07-20 19:32:15 +02:00
class ObjectiveTask {
id
description
completed
2023-07-29 06:21:55 +02:00
parentId
2023-07-20 19:32:15 +02:00
children
// UI Elements
taskHtml
descriptionSpan
completedCheckbox
deleteTaskButton
addTaskButton
2023-07-29 06:21:55 +02:00
constructor ( { id = undefined , description , completed = false , parentId = "" } ) {
2023-07-20 19:32:15 +02:00
this . description = description
2023-07-29 06:21:55 +02:00
this . parentId = parentId
2023-07-20 19:32:15 +02:00
this . children = [ ]
this . completed = completed
// Generate a new ID if none specified
if ( id == undefined ) {
2023-07-29 06:21:55 +02:00
this . id = getHighestTaskIdRecurse ( taskTree ) + 1
2023-07-20 19:32:15 +02:00
} else {
this . id = id
}
}
2023-07-29 06:21:55 +02:00
// Accepts optional index. Defaults to adding to end of list.
addTask ( description , index = null ) {
index = index != null ? index : index = this . children . length
this . children . splice ( index , 0 , new ObjectiveTask (
{ description : description , parentId : this . id }
) )
saveState ( )
}
2023-08-01 14:53:10 +02:00
2023-07-29 06:21:55 +02:00
getIndex ( ) {
if ( this . parentId !== null ) {
const parent = getTaskById ( this . parentId )
const index = parent . children . findIndex ( task => task . id === this . id )
if ( index === - 1 ) {
throw ` getIndex failed: Task ' ${ this . description } ' not found in parent task ' ${ parent . description } ' `
}
return index
} else {
throw ` getIndex failed: Task ' ${ this . description } ' has no parent `
}
}
// Used to set parent to complete when all child tasks are completed
checkParentComplete ( ) {
let all _completed = true ;
if ( this . parentId !== "" ) {
const parent = getTaskById ( this . parentId ) ;
for ( const child of parent . children ) {
if ( ! child . completed ) {
all _completed = false ;
break ;
}
}
if ( all _completed ) {
parent . completed = true ;
console . info ( ` Parent task ' ${ parent . description } ' completed after all child tasks complated. ` )
} else {
parent . completed = false ;
}
}
}
2023-07-20 19:32:15 +02:00
// Complete the current task, setting next task to next incomplete task
completeTask ( ) {
this . completed = true
console . info ( ` Task successfully completed: ${ JSON . stringify ( this . description ) } ` )
2023-07-29 06:21:55 +02:00
this . checkParentComplete ( )
2023-07-20 19:32:15 +02:00
setCurrentTask ( )
updateUiTaskList ( )
}
// Add a single task to the UI and attach event listeners for user edits
addUiElement ( ) {
const template = `
< div id = "objective-task-label-${this.id}" class = "flex1 checkbox_label" >
< input id = "objective-task-complete-${this.id}" type = "checkbox" >
2023-07-25 15:36:27 +02:00
< span class = "text_pole objective-task" style = "display: block" id = "objective-task-description-${this.id}" contenteditable > $ { this . description } < / s p a n >
2023-07-20 19:32:15 +02:00
< div id = "objective-task-delete-${this.id}" class = "objective-task-button fa-solid fa-xmark fa-2x" title = "Delete Task" > < / d i v >
< div id = "objective-task-add-${this.id}" class = "objective-task-button fa-solid fa-plus fa-2x" title = "Add Task" > < / d i v >
2023-07-29 06:21:55 +02:00
< div id = "objective-task-add-branch-${this.id}" class = "objective-task-button fa-solid fa-code-fork fa-2x" title = "Branch Task" > < / d i v >
2023-07-20 19:32:15 +02:00
< / d i v > < b r >
` ;
// Add the filled out template
$ ( '#objective-tasks' ) . append ( template ) ;
this . completedCheckbox = $ ( ` #objective-task-complete- ${ this . id } ` ) ;
this . descriptionSpan = $ ( ` #objective-task-description- ${ this . id } ` ) ;
this . addButton = $ ( ` #objective-task-add- ${ this . id } ` ) ;
this . deleteButton = $ ( ` #objective-task-delete- ${ this . id } ` ) ;
2023-07-29 06:21:55 +02:00
this . taskHtml = $ ( ` #objective-task-label- ${ this . id } ` ) ;
this . branchButton = $ ( ` #objective-task-add-branch- ${ this . id } ` )
2023-08-01 14:53:10 +02:00
2023-07-29 06:21:55 +02:00
// Handle sub-task forking style
if ( this . children . length > 0 ) {
this . branchButton . css ( { 'color' : '#33cc33' } )
} else {
this . branchButton . css ( { 'color' : '' } )
}
2023-07-20 19:32:15 +02:00
// Add event listeners and set properties
$ ( ` #objective-task-complete- ${ this . id } ` ) . prop ( 'checked' , this . completed ) ;
$ ( ` #objective-task-complete- ${ this . id } ` ) . on ( 'click' , ( ) => ( this . onCompleteClick ( ) ) ) ;
$ ( ` #objective-task-description- ${ this . id } ` ) . on ( 'keyup' , ( ) => ( this . onDescriptionUpdate ( ) ) ) ;
$ ( ` #objective-task-description- ${ this . id } ` ) . on ( 'focusout' , ( ) => ( this . onDescriptionFocusout ( ) ) ) ;
$ ( ` #objective-task-delete- ${ this . id } ` ) . on ( 'click' , ( ) => ( this . onDeleteClick ( ) ) ) ;
$ ( ` #objective-task-add- ${ this . id } ` ) . on ( 'click' , ( ) => ( this . onAddClick ( ) ) ) ;
2023-07-29 06:21:55 +02:00
this . branchButton . on ( 'click' , ( ) => ( this . onBranchClick ( ) ) )
}
onBranchClick ( ) {
currentObjective = this
updateUiTaskList ( ) ;
setCurrentTask ( ) ;
2023-07-20 19:32:15 +02:00
}
onCompleteClick ( ) {
this . completed = this . completedCheckbox . prop ( 'checked' )
2023-07-29 06:21:55 +02:00
this . checkParentComplete ( )
2023-07-20 19:32:15 +02:00
setCurrentTask ( ) ;
}
onDescriptionUpdate ( ) {
this . description = this . descriptionSpan . text ( ) ;
}
2023-07-29 06:21:55 +02:00
2023-07-20 19:32:15 +02:00
onDescriptionFocusout ( ) {
setCurrentTask ( ) ;
}
onDeleteClick ( ) {
2023-07-29 06:21:55 +02:00
const index = this . getIndex ( )
const parent = getTaskById ( this . parentId )
parent . children . splice ( index , 1 )
updateUiTaskList ( )
setCurrentTask ( )
2023-07-20 19:32:15 +02:00
}
onAddClick ( ) {
2023-07-29 06:21:55 +02:00
const index = this . getIndex ( )
const parent = getTaskById ( this . parentId )
parent . addTask ( "" , index + 1 ) ;
2023-07-20 19:32:15 +02:00
updateUiTaskList ( ) ;
2023-07-29 06:21:55 +02:00
setCurrentTask ( ) ;
2023-07-20 19:32:15 +02:00
}
2023-07-29 06:21:55 +02:00
toSaveStateRecurse ( ) {
let children = [ ]
if ( this . children . length > 0 ) {
for ( const child of this . children ) {
children . push ( child . toSaveStateRecurse ( ) )
}
}
2023-07-20 19:32:15 +02:00
return {
"id" : this . id ,
"description" : this . description ,
"completed" : this . completed ,
2023-07-29 06:21:55 +02:00
"parentId" : this . parentId ,
"children" : children ,
2023-07-20 19:32:15 +02:00
}
}
}
//###############################//
2023-07-29 06:21:55 +02:00
//# Custom Prompts #//
2023-07-20 19:32:15 +02:00
//###############################//
2023-07-25 15:36:27 +02:00
function onEditPromptClick ( ) {
let popupText = ''
popupText += `
< div class = "objective_prompt_modal" >
2023-07-30 16:19:17 +02:00
< small > Edit prompts used by Objective for this session . You can use { { objective } } or { { task } } plus any other standard template variables . Save template to persist changes . < / s m a l l >
2023-07-30 16:15:12 +02:00
< br >
2023-07-25 15:36:27 +02:00
< div >
< label for = "objective-prompt-generate" > Generation Prompt < / l a b e l >
< textarea id = "objective-prompt-generate" type = "text" class = "text_pole textarea_compact" rows = "8" > < / t e x t a r e a >
< label for = "objective-prompt-check" > Completion Check Prompt < / l a b e l >
< textarea id = "objective-prompt-check" type = "text" class = "text_pole textarea_compact" rows = "8" > < / t e x t a r e a >
< label for = "objective-prompt-extension-prompt" > Injected Prompt < / l a b e l >
< textarea id = "objective-prompt-extension-prompt" type = "text" class = "text_pole textarea_compact" rows = "8" > < / t e x t a r e a >
< / d i v >
2023-07-27 14:26:05 +02:00
< div class = "objective_prompt_block" >
2023-07-30 16:15:12 +02:00
< label for = "objective-custom-prompt-select" > Custom Prompt Select < / l a b e l >
< select id = "objective-custom-prompt-select" > < select >
2023-07-27 14:26:05 +02:00
< / d i v >
< div class = "objective_prompt_block" >
2023-07-30 16:15:12 +02:00
< input id = "objective-custom-prompt-new" class = "menu_button" type = "submit" value = "New Prompt" / >
< input id = "objective-custom-prompt-save" class = "menu_button" type = "submit" value = "Save Prompt" / >
2023-07-27 14:26:05 +02:00
< input id = "objective-custom-prompt-delete" class = "menu_button" type = "submit" value = "Delete Prompt" / >
2023-07-25 15:36:27 +02:00
< / d i v >
< / d i v > `
callPopup ( popupText , 'text' )
populateCustomPrompts ( )
// Set current values
$ ( '#objective-prompt-generate' ) . val ( objectivePrompts . createTask )
$ ( '#objective-prompt-check' ) . val ( objectivePrompts . checkTaskCompleted )
$ ( '#objective-prompt-extension-prompt' ) . val ( objectivePrompts . currentTask )
2023-08-01 14:53:10 +02:00
2023-07-25 15:36:27 +02:00
// Handle value updates
$ ( '#objective-prompt-generate' ) . on ( 'input' , ( ) => {
objectivePrompts . createTask = $ ( '#objective-prompt-generate' ) . val ( )
} )
$ ( '#objective-prompt-check' ) . on ( 'input' , ( ) => {
objectivePrompts . checkTaskCompleted = $ ( '#objective-prompt-check' ) . val ( )
} )
$ ( '#objective-prompt-extension-prompt' ) . on ( 'input' , ( ) => {
objectivePrompts . currentTask = $ ( '#objective-prompt-extension-prompt' ) . val ( )
} )
2023-08-01 14:53:10 +02:00
2023-07-30 16:15:12 +02:00
// Handle new
$ ( '#objective-custom-prompt-new' ) . on ( 'click' , ( ) => {
newCustomPrompt ( )
} )
2023-07-25 15:36:27 +02:00
// Handle save
$ ( '#objective-custom-prompt-save' ) . on ( 'click' , ( ) => {
2023-07-30 16:15:12 +02:00
saveCustomPrompt ( )
2023-07-25 15:36:27 +02:00
} )
// Handle delete
$ ( '#objective-custom-prompt-delete' ) . on ( 'click' , ( ) => {
2023-07-30 16:15:12 +02:00
deleteCustomPrompt ( )
2023-07-25 15:36:27 +02:00
} )
// Handle load
2023-07-30 16:15:12 +02:00
$ ( '#objective-custom-prompt-select' ) . on ( 'change' , loadCustomPrompt )
2023-07-25 15:36:27 +02:00
}
2023-07-30 16:15:12 +02:00
async function newCustomPrompt ( ) {
const customPromptName = await callPopup ( '<h3>Custom Prompt name:</h3>' , 'input' ) ;
2023-08-01 14:53:10 +02:00
2023-07-25 15:36:27 +02:00
if ( customPromptName == "" ) {
toastr . warning ( "Please set custom prompt name to save." )
return
}
2023-07-25 15:45:07 +02:00
if ( customPromptName == "default" ) {
toastr . error ( "Cannot save over default prompt" )
return
}
extension _settings . objective . customPrompts [ customPromptName ] = { }
2023-07-30 16:15:12 +02:00
Object . assign ( extension _settings . objective . customPrompts [ customPromptName ] , objectivePrompts )
saveSettingsDebounced ( )
populateCustomPrompts ( )
}
function saveCustomPrompt ( ) {
const customPromptName = $ ( "#objective-custom-prompt-select" ) . find ( ':selected' ) . val ( )
if ( customPromptName == "default" ) {
toastr . error ( "Cannot save over default prompt" )
return
}
Object . assign ( extension _settings . objective . customPrompts [ customPromptName ] , objectivePrompts )
2023-07-25 15:36:27 +02:00
saveSettingsDebounced ( )
populateCustomPrompts ( )
}
2023-07-30 16:15:12 +02:00
function deleteCustomPrompt ( ) {
const customPromptName = $ ( "#objective-custom-prompt-select" ) . find ( ':selected' ) . val ( )
2023-07-25 15:36:27 +02:00
if ( customPromptName == "default" ) {
toastr . error ( "Cannot delete default prompt" )
return
}
delete extension _settings . objective . customPrompts [ customPromptName ]
saveSettingsDebounced ( )
populateCustomPrompts ( )
loadCustomPrompt ( )
}
function loadCustomPrompt ( ) {
2023-07-30 16:15:12 +02:00
const optionSelected = $ ( "#objective-custom-prompt-select" ) . find ( ':selected' ) . val ( )
2023-07-29 06:21:55 +02:00
Object . assign ( objectivePrompts , extension _settings . objective . customPrompts [ optionSelected ] )
2023-07-25 15:36:27 +02:00
$ ( '#objective-prompt-generate' ) . val ( objectivePrompts . createTask )
$ ( '#objective-prompt-check' ) . val ( objectivePrompts . checkTaskCompleted )
$ ( '#objective-prompt-extension-prompt' ) . val ( objectivePrompts . currentTask )
}
function populateCustomPrompts ( ) {
// Populate saved prompts
2023-07-30 16:15:12 +02:00
$ ( '#objective-custom-prompt-select' ) . empty ( )
2023-07-25 15:36:27 +02:00
for ( const customPromptName in extension _settings . objective . customPrompts ) {
const option = document . createElement ( 'option' ) ;
option . innerText = customPromptName ;
option . value = customPromptName ;
option . selected = customPromptName
2023-07-30 16:15:12 +02:00
$ ( '#objective-custom-prompt-select' ) . append ( option )
2023-07-25 15:36:27 +02:00
}
}
2023-07-29 06:21:55 +02:00
//###############################//
//# UI AND Settings #//
//###############################//
const defaultSettings = {
currentObjectiveId : null ,
taskTree : null ,
chatDepth : 2 ,
checkFrequency : 3 ,
hideTasks : false ,
prompts : defaultPrompts ,
}
// Convenient single call. Not much at the moment.
function resetState ( ) {
2023-08-01 23:16:52 +02:00
lastMessageWasSwipe = false
2023-07-29 06:21:55 +02:00
loadSettings ( ) ;
}
//
function saveState ( ) {
const context = getContext ( ) ;
if ( currentChatId == "" ) {
currentChatId = context . chatId
}
chat _metadata [ 'objective' ] = {
currentObjectiveId : currentObjective . id ,
taskTree : taskTree . toSaveStateRecurse ( ) ,
checkFrequency : $ ( '#objective-check-frequency' ) . val ( ) ,
chatDepth : $ ( '#objective-chat-depth' ) . val ( ) ,
hideTasks : $ ( '#objective-hide-tasks' ) . prop ( 'checked' ) ,
prompts : objectivePrompts ,
}
saveMetadataDebounced ( ) ;
}
// Dump core state
function debugObjectiveExtension ( ) {
console . log ( JSON . stringify ( {
"currentTask" : currentTask ,
"currentObjective" : currentObjective ,
"taskTree" : taskTree . toSaveStateRecurse ( ) ,
"chat_metadata" : chat _metadata [ 'objective' ] ,
"extension_settings" : extension _settings [ 'objective' ] ,
"prompts" : objectivePrompts
} , null , 2 ) )
}
window . debugObjectiveExtension = debugObjectiveExtension
// Populate UI task list
function updateUiTaskList ( ) {
$ ( '#objective-tasks' ) . empty ( )
// Show button to navigate back to parent objective if parent exists
if ( currentObjective ) {
if ( currentObjective . parentId !== "" ) {
$ ( '#objective-parent' ) . show ( )
} else {
$ ( '#objective-parent' ) . hide ( )
}
}
$ ( '#objective-text' ) . val ( currentObjective . description )
if ( currentObjective . children . length > 0 ) {
// Show tasks if there are any to show
for ( const task of currentObjective . children ) {
task . addUiElement ( )
}
} else {
// Show button to add tasks if there are none
$ ( '#objective-tasks' ) . append ( `
< input id = "objective-task-add-first" type = "button" class = "menu_button" value = "Add Task" >
` )
$ ( "#objective-task-add-first" ) . on ( 'click' , ( ) => {
currentObjective . addTask ( "" )
setCurrentTask ( )
updateUiTaskList ( )
} )
}
}
function onParentClick ( ) {
currentObjective = getTaskById ( currentObjective . parentId )
updateUiTaskList ( )
setCurrentTask ( )
}
// Trigger creation of new tasks with given objective.
async function onGenerateObjectiveClick ( ) {
await generateTasks ( )
saveState ( )
}
// Update extension prompts
function onChatDepthInput ( ) {
saveState ( )
setCurrentTask ( ) // Ensure extension prompt is updated
}
function onObjectiveTextFocusOut ( ) {
2023-07-30 16:15:12 +02:00
if ( currentObjective ) {
currentObjective . description = $ ( '#objective-text' ) . val ( )
saveState ( )
}
2023-07-29 06:21:55 +02:00
}
// Update how often we check for task completion
function onCheckFrequencyInput ( ) {
checkCounter = $ ( "#objective-check-frequency" ) . val ( )
$ ( '#objective-counter' ) . text ( checkCounter )
saveState ( )
}
function onHideTasksInput ( ) {
$ ( '#objective-tasks' ) . prop ( 'hidden' , $ ( '#objective-hide-tasks' ) . prop ( 'checked' ) )
saveState ( )
}
function loadTaskChildrenRecurse ( savedTask ) {
let tempTaskTree = new ObjectiveTask ( {
id : savedTask . id ,
description : savedTask . description ,
completed : savedTask . completed ,
parentId : savedTask . parentId ,
} )
for ( const task of savedTask . children ) {
const childTask = loadTaskChildrenRecurse ( task )
tempTaskTree . children . push ( childTask )
}
return tempTaskTree
}
2023-07-20 19:32:15 +02:00
function loadSettings ( ) {
// Load/Init settings for chatId
currentChatId = getContext ( ) . chatId
2023-07-29 08:58:26 +02:00
// Reset Objectives and Tasks in memory
taskTree = null ;
currentObjective = null ;
2023-08-01 14:53:10 +02:00
2023-07-25 15:36:27 +02:00
// Init extension settings
if ( Object . keys ( extension _settings . objective ) . length === 0 ) {
Object . assign ( extension _settings . objective , { 'customPrompts' : { 'default' : defaultPrompts } } )
}
2023-07-20 19:32:15 +02:00
// Bail on home screen
if ( currentChatId == undefined ) {
return
}
// Migrate existing settings
if ( currentChatId in extension _settings . objective ) {
2023-07-29 06:21:55 +02:00
// TODO: Remove this soon
2023-07-20 19:32:15 +02:00
chat _metadata [ 'objective' ] = extension _settings . objective [ currentChatId ] ;
delete extension _settings . objective [ currentChatId ] ;
}
if ( ! ( 'objective' in chat _metadata ) ) {
Object . assign ( chat _metadata , { objective : defaultSettings } ) ;
}
2023-07-29 06:21:55 +02:00
// Migrate legacy flat objective to new objectiveTree and currentObjective
2023-07-29 08:58:26 +02:00
if ( 'objective' in chat _metadata . objective ) {
// Create root objective from legacy objective
taskTree = new ObjectiveTask ( { id : 0 , description : chat _metadata . objective . objective } ) ;
currentObjective = taskTree ;
// Populate root objective tree from legacy tasks
if ( 'tasks' in chat _metadata . objective ) {
let idIncrement = 0 ;
taskTree . children = chat _metadata . objective . tasks . map ( task => {
idIncrement += 1 ;
return new ObjectiveTask ( {
id : idIncrement ,
description : task . description ,
completed : task . completed ,
parentId : taskTree . id ,
} )
} ) ;
}
saveState ( ) ;
delete chat _metadata . objective . objective ;
delete chat _metadata . objective . tasks ;
} else {
// Load Objectives and Tasks (Normal path)
if ( chat _metadata . objective . taskTree ) {
taskTree = loadTaskChildrenRecurse ( chat _metadata . objective . taskTree )
}
2023-07-29 06:21:55 +02:00
}
2023-07-29 08:58:26 +02:00
2023-07-29 06:21:55 +02:00
// Make sure there's a root task
if ( ! taskTree ) {
taskTree = new ObjectiveTask ( { id : 0 , description : $ ( '#objective-text' ) . val ( ) } )
}
currentObjective = taskTree
2023-07-20 19:32:15 +02:00
checkCounter = chat _metadata [ 'objective' ] . checkFrequency
// Update UI elements
$ ( '#objective-counter' ) . text ( checkCounter )
2023-07-29 06:21:55 +02:00
$ ( "#objective-text" ) . text ( taskTree . description )
2023-07-20 19:32:15 +02:00
updateUiTaskList ( )
$ ( '#objective-chat-depth' ) . val ( chat _metadata [ 'objective' ] . chatDepth )
$ ( '#objective-check-frequency' ) . val ( chat _metadata [ 'objective' ] . checkFrequency )
$ ( '#objective-hide-tasks' ) . prop ( 'checked' , chat _metadata [ 'objective' ] . hideTasks )
2023-07-25 15:36:27 +02:00
$ ( '#objective-tasks' ) . prop ( 'hidden' , $ ( '#objective-hide-tasks' ) . prop ( 'checked' ) )
2023-07-20 19:32:15 +02:00
setCurrentTask ( )
}
function addManualTaskCheckUi ( ) {
$ ( '#extensionsMenu' ) . prepend ( `
< div id = "objective-task-manual-check-menu-item" class = "list-group-item flex-container flexGap5" >
< div id = "objective-task-manual-check" class = "extensionsMenuExtensionButton fa-regular fa-square-check" / > < / d i v >
Manual Task Check
< / d i v > ` )
$ ( '#objective-task-manual-check-menu-item' ) . attr ( 'title' , 'Trigger AI check of completed tasks' ) . on ( 'click' , checkTaskCompleted )
}
jQuery ( ( ) => {
const settingsHtml = `
< div class = "objective-settings" >
< div class = "inline-drawer" >
< div class = "inline-drawer-toggle inline-drawer-header" >
2023-07-29 06:21:55 +02:00
< b > Objective < / b >
< div class = "inline-drawer-icon fa-solid fa-circle-chevron-down down" > < / d i v >
2023-07-20 19:32:15 +02:00
< / d i v >
2023-07-29 06:21:55 +02:00
< div class = "inline-drawer-content" >
< label for = "objective-text" > < small > Enter an objective and generate tasks . The AI will attempt to complete tasks autonomously < / s m a l l > < / l a b e l >
< textarea id = "objective-text" type = "text" class = "text_pole textarea_compact" rows = "4" > < / t e x t a r e a >
< div class = "objective_block flex-container" >
< input id = "objective-generate" class = "menu_button" type = "submit" value = "Auto-Generate Tasks" / >
< label class = "checkbox_label" > < input id = "objective-hide-tasks" type = "checkbox" > Hide Tasks < / l a b e l >
< / d i v >
< div id = "objective-parent" class = "objective_block flex-container" >
< i class = "objective-task-button fa-solid fa-circle-left fa-2x" title = "Go to Parent" > < / i >
< small > Go to parent task < / s m a l l >
2023-07-20 19:32:15 +02:00
< / d i v >
2023-07-29 06:21:55 +02:00
< div id = "objective-tasks" > < / d i v >
< div class = "objective_block margin-bot-10px" >
< div class = "objective_block objective_block_control flex1 flexFlowColumn" >
< label for = "objective-chat-depth" > Position in Chat < / l a b e l >
< input id = "objective-chat-depth" class = "text_pole widthUnset" type = "number" min = "0" max = "99" / >
< / d i v >
< br >
< div class = "objective_block objective_block_control flex1" >
< label for = "objective-check-frequency" > Task Check Frequency < / l a b e l >
< input id = "objective-check-frequency" class = "text_pole widthUnset" type = "number" min = "0" max = "99" / >
< small > ( 0 = disabled ) < / s m a l l >
< / d i v >
2023-07-20 19:32:15 +02:00
< / d i v >
2023-07-29 06:21:55 +02:00
< span > Messages until next AI task completion check < span id = "objective-counter" > 0 < / s p a n > < / s p a n >
< div class = "objective_block flex-container" >
< input id = "objective_prompt_edit" class = "menu_button" type = "submit" value = "Edit Prompts" / >
< / d i v >
< hr class = "sysHR" >
2023-07-20 19:32:15 +02:00
< / d i v >
< / d i v >
2023-07-29 06:21:55 +02:00
< / d i v >
` ;
2023-07-20 19:32:15 +02:00
addManualTaskCheckUi ( )
$ ( '#extensions_settings' ) . append ( settingsHtml ) ;
$ ( '#objective-generate' ) . on ( 'click' , onGenerateObjectiveClick )
$ ( '#objective-chat-depth' ) . on ( 'input' , onChatDepthInput )
$ ( "#objective-check-frequency" ) . on ( 'input' , onCheckFrequencyInput )
$ ( '#objective-hide-tasks' ) . on ( 'click' , onHideTasksInput )
2023-07-25 15:36:27 +02:00
$ ( '#objective_prompt_edit' ) . on ( 'click' , onEditPromptClick )
2023-07-29 06:21:55 +02:00
$ ( '#objective-parent' ) . hide ( )
$ ( '#objective-parent' ) . on ( 'click' , onParentClick )
$ ( '#objective-text' ) . on ( 'focusout' , onObjectiveTextFocusOut )
2023-07-20 19:32:15 +02:00
loadSettings ( )
eventSource . on ( event _types . CHAT _CHANGED , ( ) => {
resetState ( )
} ) ;
2023-07-31 15:24:51 +02:00
eventSource . on ( event _types . MESSAGE _SWIPED , ( ) => {
lastMessageWasSwipe = true
} )
2023-07-20 19:32:15 +02:00
eventSource . on ( event _types . MESSAGE _RECEIVED , ( ) => {
2023-07-31 15:24:51 +02:00
if ( currentChatId == undefined || jQuery . isEmptyObject ( currentTask ) || lastMessageWasSwipe ) {
lastMessageWasSwipe = false
2023-07-20 19:32:15 +02:00
return
}
if ( $ ( "#objective-check-frequency" ) . val ( ) > 0 ) {
// Check only at specified interval
if ( checkCounter <= 0 ) {
checkTaskCompleted ( ) ;
}
checkCounter -= 1
}
setCurrentTask ( ) ;
$ ( '#objective-counter' ) . text ( checkCounter )
} ) ;
registerSlashCommand ( 'taskcheck' , checkTaskCompleted , [ ] , ' – checks if the current task is completed' , true , true ) ;
} ) ;