import thisHtml from './input.html'; import thisCss from './input.css'; import stylesCss from '../../../styles.css'; import rootCss from '../../../root.css'; import { delay, extractErrorMsg, pretty } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs"; import { IbGib_V1 } from "@ibgib/ts-gib/dist/V1/types.mjs"; import { IbGibAddr, TransformResult } from "@ibgib/ts-gib/dist/types.mjs"; import { ROOT } from "@ibgib/ts-gib/dist/V1/constants.mjs"; import { MetaspaceService } from "@ibgib/core-gib/dist/witness/space/metaspace/metaspace-types.mjs"; import { CommentIbGib_V1 } from "@ibgib/core-gib/dist/common/comment/comment-types.mjs"; import { createCommentIbGib } from "@ibgib/core-gib/dist/common/comment/comment-helper.mjs"; import { appendToTimeline } from "@ibgib/core-gib/dist/timeline/timeline-api.mjs"; import { getDeterministicColorInfo, getGlobalMetaspace_waitIfNeeded, } from "@ibgib/web-gib/dist/helpers.mjs"; import { IbGibDynamicComponentInstanceBase, IbGibDynamicComponentMetaBase } from "@ibgib/web-gib/dist/ui/component/ibgib-dynamic-component-bases.mjs"; import { IbGibDynamicComponentInstance, IbGibDynamicComponentInstanceInitOpts, } from "@ibgib/web-gib/dist/ui/component/component-types.mjs"; import { getAddlMetadataTextForAgentText } from '@ibgib/web-gib/dist/witness/agent/agent-helpers.mjs'; import { storageGet, } from "@ibgib/web-gib/dist/storage/storage-helpers.web.mjs"; import { promptForAPIKey, updateAPIKeyInStorage } from '@ibgib/web-gib/dist/helpers.web.mjs'; import { GLOBAL_LOG_A_LOT, ARMY_STORE, BEE_KEY, BLANK_GIB_DB_NAME, } from "../../../constants.mjs"; import { InputInfo } from "./input-types.mjs"; import { CHAT_WITH_AGENT_NEED_API_KEY } from '../../../witness/app/blank-canvas/blank-canvas-constants.mjs'; import { getComponentCtorArg } from '../../../helpers.web.mjs'; const logalot = GLOBAL_LOG_A_LOT; export const INPUT_COMPONENT_NAME: string = 'ibgib-input'; export class InputComponentMeta extends IbGibDynamicComponentMetaBase { protected override lc: string = `[${InputComponentMeta.name}]`; /** * temporary regexp path for our initial dev. this component will become * attached to actual ib^gib addrs */ routeRegExp?: RegExp = new RegExp(INPUT_COMPONENT_NAME); componentName: string = INPUT_COMPONENT_NAME; constructor() { super(getComponentCtorArg()); customElements.define(this.componentName, InputComponentInstance); } /** * for a input, we don't have any additional info in the path. */ async createInstance({ path, ibGibAddr }: { path: string; ibGibAddr: IbGibAddr; }): Promise { const lc = `${this.lc}[${this.createInstance.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: genuuid)`); } const component = document.createElement(this.componentName) as InputComponentInstance; await component.initialize({ ibGibAddr, meta: this, html: thisHtml, css: [rootCss, stylesCss, thisCss], }); return component; } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } } interface InputElements { componentEl: HTMLElement; inputTextEl: HTMLTextAreaElement; inputSendBtnEl: HTMLButtonElement; } const DEFAULT_INPUT_INFO: InputInfo = { } export class InputComponentInstance extends IbGibDynamicComponentInstanceBase implements IbGibDynamicComponentInstance { protected override lc: string = `[${InputComponentInstance.name}]`; metaspace: MetaspaceService | undefined; inputInfo: InputInfo = DEFAULT_INPUT_INFO; private _apiKey: string = ''; private _submitting = false; private _queuedSubmitTexts: string[] = []; constructor() { super(); } override async initialize(opts: IbGibDynamicComponentInstanceInitOpts): Promise { const lc = `${this.lc}[${this.initialize.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: genuuid)`); } await super.initialize(opts); this.metaspace = await getGlobalMetaspace_waitIfNeeded(); // await this.loadIbGib(); // await super.subscribeToIbGibUpdates(); } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } override async created(): Promise { const lc = `${this.lc}[${this.created.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: genuuid)`); } // const { meta, htmlPath, scriptPaths, cssPaths } = opts; await this.initUI(); await this.renderUI(); } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } private async initUI(): Promise { const lc = `${this.lc}[${this.initUI.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: genuuid)`); } const shadowRoot = this.shadowRoot; if (!shadowRoot) { throw new Error(`(UNEXPECTED) shadowRoot falsy? (E: genuuid)`); } const componentEl = shadowRoot.getElementById('input-component') as HTMLElement; if (!componentEl) { throw new Error(`(UNEXPECTED) componentEl not found in shadowRoot? (E: genuuid)`); } const inputTextEl = shadowRoot.getElementById('input-text') as HTMLTextAreaElement; if (!inputTextEl) { throw new Error(`(UNEXPECTED) inputTextEl not found in shadowRoot? (E: genuuid)`); } const inputSendBtnEl = shadowRoot.getElementById('input-send-btn') as HTMLButtonElement; if (!inputSendBtnEl) { throw new Error(`(UNEXPECTED) inputSendBtnEl not found in shadowRoot? (E: genuuid)`); } this.elements = { componentEl, inputTextEl, inputSendBtnEl, }; // inputSendBtnEl.addEventListener('click', this.handleSubmit); inputSendBtnEl.addEventListener('click', async () => { await this.handleSubmit(); }); inputTextEl.addEventListener('keydown', async (event) => { if (event.key === 'Enter' && event.ctrlKey === true) { event.preventDefault(); // Prevent default behavior (new line) await this.handleSubmit(); } }); } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } private async handleKeydown(event: any): Promise { } /** * rerender */ protected async renderUI(): Promise { const lc = `${this.lc}[${this.renderUI.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: genuuid)`); } if (!this.shadowRoot) { throw new Error(`(UNEXPECTED) this.shadowRoot falsy? (E: genuuid)`); } if (!this.elements) { console.warn(`${lc} (UNEXPECTED) tried to render but haven't initialized elements? (W: genuuid)`); return; /* <<<< returns early */ } // const color = getDeterministicColorInfo({ // ibGib: this.inputInfo.contextProxyIbGib?.ibGib, // }); // this.style.setProperty('--ibgib-border-color', color); const { punctiliarColor, punctiliarColorTranslucent, tjpColor, tjpColorTranslucent, errorMsg } = getDeterministicColorInfo({ ibGib: this.inputInfo.contextProxyIbGib?.ibGib, translucentAlpha: 10 }); if (!errorMsg) { this.style.setProperty('--ibgib-color', punctiliarColor); this.style.setProperty('--ibgib-color-translucent', punctiliarColor); this.style.setProperty('--tjp-color', tjpColor ?? punctiliarColor); this.style.setProperty('--tjp-color-translucent', tjpColorTranslucent ?? punctiliarColorTranslucent); } else { // don't set anything console.error(`${lc} ${errorMsg} (E: 65e0d330d029c1fe39f3d6280dda3725)`); } const { componentEl, inputSendBtnEl, inputTextEl, } = this.elements; const { agent, contextProxyIbGib } = this.inputInfo; if (agent || contextProxyIbGib?.ibGib) { inputTextEl.ariaPlaceholder = this.inputInfo.placeholderText ?? ''; inputTextEl.placeholder = this.inputInfo.placeholderText ?? ''; inputTextEl.removeAttribute('readonly'); // inputTextEl.readOnly = false; // inputTextEl.ariaReadOnly = false.toString(); } else { inputTextEl.ariaPlaceholder = 'loading...'; inputTextEl.placeholder = 'loading...'; inputTextEl.textContent = ''; inputTextEl.setAttribute('readonly', 'true'); // inputTextEl.readOnly = true; // inputTextEl.ariaReadOnly = true.toString(); } } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } override async disconnected(): Promise { const lc = `${this.lc}[${this.disconnected.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: genuuid)`); } // no action atow if (!this.elements) { throw new Error(`(UNEXPECTED) this.elements falsy? (E: 3768c106edf32adc8eedee1b5adc6625)`); } // unhook events...necessary? // const { inputSendBtnEl, inputTextEl } = this.elements; // inputSendBtnEl.removeEventListener('click', this.handleSubmit); // inputTextEl.removeEventListener('keydown', this.handleKeydown); } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } /** * adds a comment to the current context ibgib. * * todo: also submit pic(s) */ private async handleSubmit(): Promise { const lc = `${this.lc}[${this.handleSubmit.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: 6e5d020950ab3c161c3b023772af1a25)`); } // #region init & validation if (!this.elements) { throw new Error(`(UNEXPECTED) this.elements falsy? (E: genuuid)`); } const { inputTextEl, } = this.elements; // const inputText = inputTextEl.textContent?.trim() ?? ''; const inputText = inputTextEl.value?.trim() ?? ''; if (!inputText) { console.warn(`${lc} no text to submit. returning early. (W: genuuid)`); return; /* <<<< returns early */ } inputTextEl.value = ''; const { agent, contextProxyIbGib, spaceId, } = this.inputInfo; if (!contextProxyIbGib && !agent) { throw new Error(`(UNEXPECTED) both contextProxyIbGib AND agent falsy? (E: bb4cb430269b64e07a532e5ae2df5c25)`); } const { metaspace } = this; if (!metaspace) { throw new Error(`(UNEXPECTED) metaspace falsy? (E: 18b602faa9b1a52edef603480fa5ce25)`); } // #endregion init & validation if (!this._apiKey) { this._apiKey = await storageGet({ dbName: BLANK_GIB_DB_NAME, storeName: ARMY_STORE, key: BEE_KEY, }) ?? ''; } if (!this._apiKey) { await this.doPromptForAPIKey(); // this reloads if they enter the API key. If they don't enter // it, then we just leave it there until they submit again and // are prompted again. so return early. not the best but not the // worst workflow return; /* <<<< returns early */ } // first determine if we're already submitting. this._queuedSubmitTexts.push(inputText); if (this._submitting) { console.warn(`${lc} already submitting. queueing text. (W: genuuid)`); this._queuedSubmitTexts.push(inputText); return; /* <<<< returns early */ } const space = await metaspace.getLocalUserSpace({ localSpaceId: spaceId, lock: false }); if (contextProxyIbGib) { if (!contextProxyIbGib.ibGib) { throw new Error(`(UNEXPECTED) contextProxyIbGib truthy but contextProxyIbGib.ibGib falsy? (E: c52bdfa29268be0fb968f16940723525)`); } this._submitting = true; do { const text = this._queuedSubmitTexts.shift(); if (!text) { continue; } // create the comment ibgib /** atow (04/2025) adds a timestampInTicks and textSrc */ const addlMetadataText = getAddlMetadataTextForAgentText({ textSrc: 'human', }); const resCreateComment: TransformResult = await createCommentIbGib({ text, addlMetadataText, saveInSpace: true, space, }); const commentIbGib = resCreateComment.newIbGib; await metaspace.registerNewIbGib({ ibGib: commentIbGib, space, }); // rel8 to our current context await appendToTimeline({ timeline: contextProxyIbGib.ibGib, metaspace, rel8nInfos: [{ rel8nName: 'comment', ibGibs: [commentIbGib], }], space, }); } while (this._queuedSubmitTexts.length > 0); } else { // submitting to agent if (!agent) { throw new Error(`(UNEXPECTED) agent falsy? at this point should be logically guaranteed? (E: 94b982c0bbeb133a52c2bee537404f25)`); } await this.handleSubmit_toAgent({ texts: this._queuedSubmitTexts.concat(), }); this._queuedSubmitTexts = []; } } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } this._submitting = false; } } private async handleSubmit_toAgent({ texts, }: { texts: string[]; }): Promise { const lc = `[${this.handleSubmit_toAgent.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: genuuid)`); } const { agent, } = this.inputInfo; // first ensure our best to see if we have an api key already const fnSubmitToAgent = async () => { try { // Add to the primary agent's chat if (!agent) { throw new Error(`agent falsy. (E: genuuid)`); } if (logalot) { console.log(`Adding to agent chat: ${pretty(texts)} (I: genuuid)`); } await agent.addTexts({ infos: texts.map(text => { return { textSrc: 'human', text, isSystem: text.startsWith('system: ') }; }), // infos: [{ // textSrc: 'human', // text, // isSystem: text.startsWith('system: ') } // ], }); // for (const text of texts) { // await addToChatLogKluge({ // text, // who: 'user', // chatLog: undefined, // primary chat log for now... // scrollAfter: true, // }); // } const _ = await agent.witness(ROOT); } catch (error) { console.error(`error adding chat to agent (E: genuuid): ${extractErrorMsg(error)}`); } }; // if we have an apikey, submit the chat. else we'll have to // do some work to get the user to enter an api key (and // fund us!) if (this._apiKey) { await fnSubmitToAgent(); } else { await this.doPromptForAPIKey(); } } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } /** * ## notes * changed this to "do" function name because I'm refactoring to an external * function {@link promptForAPIKey} */ private async doPromptForAPIKey(): Promise { const lc = `${this.lc}[${this.doPromptForAPIKey.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: b475d2686686ac016f0ea48ab51ef325)`); } let resAPIKey = await promptForAPIKey({ msg: CHAT_WITH_AGENT_NEED_API_KEY, }); if (resAPIKey === undefined) { console.log(`${lc} user cancelled entering API key. (I: genuuid)`); return; /* <<<< returns early */ } this._apiKey = resAPIKey; if (this._apiKey) { await updateAPIKeyInStorage({ dbName: BLANK_GIB_DB_NAME, storeName: ARMY_STORE, key: BEE_KEY, apiKey: this._apiKey, force: false }); } else { // clear it out b/c empty string? // await updateAPIKeyInStorage({ apiKey: this._apiKey, force: true }); // clears it if empty string } } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } // #region public api public async setContextInfo({ info, }: { info: InputInfo, }): Promise { const lc = `${this.lc}[${this.setContextInfo.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: genuuid)`); } // edge case: don't want to change context in the middle of submitting. while (this._submitting) { if (logalot) { console.log(`${lc} still submitting. don't want to change context in the middle of submitting. (I: d14b2bb9704945be991db24903bf3625)`); } await delay(50); } this.inputInfo = info; // init agent to subscribe to events in the context? await this.renderUI(); } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } // #endregion public api }