@iyio/convo-lang
Version:
A conversational language.
400 lines • 13.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConversationUiCtrl = void 0;
const common_1 = require("@iyio/common");
const rxjs_1 = require("rxjs");
const Conversation_1 = require("./Conversation");
const LocalStorageConvoDataStore_1 = require("./LocalStorageConvoDataStore");
const convo_lang_ui_lib_1 = require("./convo-lang-ui-lib");
const convo_lib_1 = require("./convo-lib");
class ConversationUiCtrl {
get lastCompletionSubject() { return this._lastCompletion; }
get lastCompletion() { return this._lastCompletion.value; }
get currentTaskSubject() { return this._currentTask; }
get currentTask() { return this._currentTask.value; }
get templateSubject() { return this._template; }
get template() { return this._template.value; }
set template(value) {
if (value == this._template.value) {
return;
}
this._template.next(value);
}
;
get removeDanglingUserMessagesSubject() { return this._removeDanglingUserMessages; }
get removeDanglingUserMessages() { return this._removeDanglingUserMessages.value; }
set removeDanglingUserMessages(value) {
if (value == this._removeDanglingUserMessages.value) {
return;
}
this._removeDanglingUserMessages.next(value);
}
get convoSubject() { return this._convo; }
get convo() { return this._convo.value; }
get showSourceSubject() { return this._showSource; }
/**
* If true the source convo-lang syntax should be displayed to the user
*/
get showSource() { return this._showSource.value; }
set showSource(value) {
if (value == this._showSource.value) {
return;
}
this._showSource.next(value);
}
get editorModeSubject() { return this._editorMode; }
get editorMode() { return this._editorMode.value; }
set editorMode(value) {
if (value == this._editorMode.value) {
return;
}
this._editorMode.next(value);
}
get showSystemMessagesSubject() { return this._showSystemMessages; }
/**
* If true the system messages should be displayed to the user
*/
get showSystemMessages() { return this._showSystemMessages.value; }
set showSystemMessages(value) {
if (value == this._showSystemMessages.value) {
return;
}
this._showSystemMessages.next(value);
}
get showFunctionsSubject() { return this._showFunctions; }
/**
* If true function calls should be displayed to the user
*/
get showFunctions() { return this._showFunctions.value; }
set showFunctions(value) {
if (value == this._showFunctions.value) {
return;
}
this._showFunctions.next(value);
}
get enabledSlashCommandsSubject() { return this._enabledSlashCommands; }
/**
* If messages appended to the conversation using the appendUiMessage will be checked for messages
* starting with a forward slash and be interpreted as a command.
*/
get enabledSlashCommands() { return this._enabledSlashCommands.value; }
set enabledSlashCommands(value) {
if (value == this._enabledSlashCommands.value) {
return;
}
this._enabledSlashCommands.next(value);
}
get themeSubject() { return this._theme; }
get theme() { return this._theme.value; }
set theme(value) {
if (value == this._theme.value) {
return;
}
this._theme.next(value);
}
get mediaQueueSubject() { return this._mediaQueue; }
get mediaQueue() { return this._mediaQueue.value; }
get collapsedSubject() { return this._collapsed; }
/**
* Often used to indicate if the conversation is display in a collapsed state
*/
get collapsed() { return this._collapsed.value; }
set collapsed(value) {
if (value == this._collapsed.value) {
return;
}
this._collapsed.next(value);
}
get onClear() { return this._onClear; }
constructor({ id, autoLoad, convo, initConvo, convoOptions, template, autoSave = false, removeDanglingUserMessages = false, store = 'localStorage', enableSlashCommand = false, } = {}) {
this._lastCompletion = new rxjs_1.BehaviorSubject(null);
this.tasks = [];
this._currentTask = new rxjs_1.BehaviorSubject(null);
this._convo = new rxjs_1.BehaviorSubject(null);
this._showSource = new rxjs_1.BehaviorSubject(false);
this._editorMode = new rxjs_1.BehaviorSubject('code');
this._showSystemMessages = new rxjs_1.BehaviorSubject(false);
this._showFunctions = new rxjs_1.BehaviorSubject(false);
this._theme = new rxjs_1.BehaviorSubject({});
this._mediaQueue = new rxjs_1.BehaviorSubject([]);
this._collapsed = new rxjs_1.BehaviorSubject(true);
this._onClear = new rxjs_1.Subject();
this.componentRenderers = {};
this._isDisposed = false;
this.convoTaskCount = 0;
this.convoTaskSub = null;
this.isAutoSaving = false;
this.autoSaveRequested = false;
this.messageRenderers = [];
this.id = id ?? (0, common_1.shortUuid)();
this._removeDanglingUserMessages = new rxjs_1.BehaviorSubject(removeDanglingUserMessages);
this._enabledSlashCommands = new rxjs_1.BehaviorSubject(enableSlashCommand);
this.convoOptions = convoOptions;
this.initConvoCallback = initConvo;
this._template = new rxjs_1.BehaviorSubject(template);
this.autoSave = autoSave;
this.store = store === 'localStorage' ?
new LocalStorageConvoDataStore_1.LocalStorageConvoDataStore() : store;
if (id !== undefined && autoLoad) {
if (convo) {
this.setConvo(convo);
}
this.loadAsync();
}
else {
this.setConvo(convo ?? this.createConvo(true));
}
}
get isDisposed() { return this._isDisposed; }
dispose() {
if (this._isDisposed) {
return;
}
this._isDisposed = true;
this._currentTask.next('disposed');
if (this.convoTaskSub) {
this.convoTaskSub.unsubscribe();
this.convoTaskSub = null;
}
}
initConvo(convo, options) {
if (this.initConvoCallback) {
this.initConvoCallback(convo);
}
if (options.appendTemplate && this.template) {
convo.append(this.template);
}
}
createConvo(appendTemplate) {
const convo = new Conversation_1.Conversation(this.convoOptions);
this.initConvo(convo, { appendTemplate });
return convo;
}
async loadAsync() {
if (this.isDisposed || this.currentTask || !this.store?.loadConvo) {
return false;
}
this.pushTask('loading');
try {
const str = await this.store?.loadConvo?.(this.id);
if (this.isDisposed) {
return false;
}
const convo = this.createConvo(str ? false : true);
if (str) {
convo.append(str);
}
this.setConvo(convo);
}
finally {
this.popTask('loading');
}
return true;
}
clear() {
if (this.isDisposed || this.currentTask) {
return false;
}
this.setConvo(this.createConvo(true));
if (this.autoSave) {
this.queueAutoSave();
}
this._onClear.next();
return true;
}
pushTask(task) {
this.tasks.push(task);
if (this._currentTask.value !== task) {
this._currentTask.next(task);
}
}
popTask(task) {
if (this.isDisposed) {
return;
}
const i = this.tasks.lastIndexOf(task);
if (i === -1) {
console.error(`ConversationUiCtrl.popTask out of sync. (${task}) not in current list`);
return;
}
this.tasks.splice(i, 1);
const next = this.tasks[this.tasks.length - 1] ?? null;
if (this._currentTask.value !== next) {
this._currentTask.next(next);
}
}
setConvo(convo) {
if (this.convoTaskSub) {
this.convoTaskSub.unsubscribe();
this.convoTaskSub = null;
}
while (this.tasks.includes('completing')) {
this.popTask('completing');
}
this.convoTaskSub = convo.activeTaskCountSubject.subscribe(n => {
if (n === 1) {
if (!this.tasks.includes('completing')) {
this.pushTask('completing');
}
}
else if (n === 0) {
if (this.tasks.includes('completing')) {
this.popTask('completing');
}
}
});
this._convo.next(convo);
}
replace(convo) {
if (this.isDisposed || this.currentTask) {
return false;
}
if (this.removeDanglingUserMessages) {
convo = (0, convo_lib_1.removeDanglingConvoUserMessage)(convo);
}
const c = this.createConvo(false);
try {
c.append(convo);
}
catch {
return false;
}
this.setConvo(c);
if (this.autoSave) {
this.queueAutoSave();
}
return true;
}
async replaceAndCompleteAsync(convo) {
if (!this.replace(convo)) {
return false;
}
if (this.currentTask) {
return false;
}
await this.convo?.completeAsync();
this._lastCompletion.next(this.convo?.convo ?? null);
if (this.autoSave) {
this.queueAutoSave();
}
return true;
}
isSlashCommand(message) {
return cmdReg.test(message);
}
async appendUiMessageAsync(message) {
if (this.isDisposed) {
return false;
}
if (cmdReg.test(message)) {
if (!this._enabledSlashCommands.value) {
return false;
}
message = message.trim();
switch (message) {
case '/source':
this.showSource = this.editorMode === 'code' ? !this.showSource : true;
this.editorMode = 'code';
break;
case '/vars':
this.showSource = this.editorMode === 'vars' ? !this.showSource : true;
this.editorMode = 'vars';
break;
case '/flat':
this.showSource = this.editorMode === 'flat' ? !this.showSource : true;
this.editorMode = 'flat';
break;
case '/tree':
this.showSource = this.editorMode === 'tree' ? !this.showSource : true;
this.editorMode = 'tree';
break;
case '/system':
this.showSystemMessages = !this.showSystemMessages;
break;
case '/function':
this.showFunctions = !this.showFunctions;
break;
case '/clear':
this.clear();
break;
}
return 'command';
}
const convo = this.convo;
if (!convo || convo.isCompleting || this.currentTask) {
return false;
}
if (this.mediaQueue.length) {
message += '\n\n' + this.mediaQueue.map(i => {
const url = (0, convo_lang_ui_lib_1.getConvoPromptMediaUrl)(i, 'prompt');
if (!url) {
return '';
}
return `})`;
}).join('\n');
this._mediaQueue.next([]);
}
if (message.trim()) {
convo.appendUserMessage(message);
}
await convo.completeAsync();
this._lastCompletion.next(convo.convo ?? null);
if (this.autoSave) {
this.queueAutoSave();
}
return true;
}
queueMedia(media) {
this._mediaQueue.next([...this._mediaQueue.value, (typeof media === 'string') ? { url: media } : media]);
}
dequeueMedia(media) {
if (typeof media === 'string') {
const match = this._mediaQueue.value.find(m => m.url === media);
if (!match) {
return false;
}
media = match;
}
if (!this._mediaQueue.value.includes(media)) {
return false;
}
this._mediaQueue.next((0, common_1.aryDuplicateRemoveItem)(this._mediaQueue.value, media));
return true;
}
queueAutoSave() {
if (this.isDisposed || !this.store) {
return;
}
if (this.isAutoSaving) {
this.autoSaveRequested = true;
return;
}
this._autoSaveAsync();
}
async _autoSaveAsync() {
if (this.isAutoSaving || !this.store) {
return;
}
this.isAutoSaving = true;
do {
try {
await this.store?.saveConvo?.(this.id, this.convo?.convo ?? '');
}
catch (ex) {
console.error('ConversationUiCtrl auto save failed', ex);
}
} while (this.autoSaveRequested && !this.isDisposed);
}
renderMessage(message, index) {
for (const r of this.messageRenderers) {
const v = r(message, index, this);
if (v !== undefined && v !== null) {
return v;
}
}
return undefined;
}
}
exports.ConversationUiCtrl = ConversationUiCtrl;
const cmdReg = /^\s*\//;
//# sourceMappingURL=ConversationUiCtrl.js.map