import thisHtml from './projects.html'; import thisCss from './projects.css'; import stylesCss from '../../styles.css'; import rootCss from '../../root.css'; import { clone, delay, extractErrorMsg, getSaferSubstring, pickRandom_Letters, } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs"; import { IbGibAddr } from "@ibgib/ts-gib/dist/types.mjs"; import { IbGib_V1 } from "@ibgib/ts-gib/dist/V1/types.mjs"; import { getIbAndGib, getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs"; import { getGibInfo, isPrimitive } from "@ibgib/ts-gib/dist/V1/transforms/transform-helper.mjs"; import { MetaspaceService } from "@ibgib/core-gib/dist/witness/space/metaspace/metaspace-types.mjs"; import { fnObs } from "@ibgib/core-gib/dist/common/pubsub/observer/observer-helper.mjs"; import { IbGibTimelineUpdateInfo } from "@ibgib/core-gib/dist/common/other/other-types.mjs"; import { getTjpAddr } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs"; import { SpaceId } from "@ibgib/core-gib/dist/witness/space/space-types.mjs"; import { CommentIbGib_V1 } from "@ibgib/core-gib/dist/common/comment/comment-types.mjs"; import { isComment } from "@ibgib/core-gib/dist/common/comment/comment-helper.mjs"; import { tellUserFunctionInfo } from "@ibgib/web-gib/dist/api/commands/chat/tell-user.mjs"; import { getDeterministicColorInfo, getGlobalMetaspace_waitIfNeeded, } from "@ibgib/web-gib/dist/helpers.mjs"; import { IbGibDynamicComponentMetaBase, IbGibDynamicComponentInstanceBase_ParentOfTabs, } from "@ibgib/web-gib/dist/ui/component/ibgib-dynamic-component-bases.mjs"; import { ElementsBase, ChildInfoBase, IbGibDynamicComponentInstance, IbGibDynamicComponentInstanceInitOpts, } from "@ibgib/web-gib/dist/ui/component/component-types.mjs"; import { storageGet, } from "@ibgib/web-gib/dist/storage/storage-helpers.web.mjs"; import { getComponentSvc } from "@ibgib/web-gib/dist/ui/component/ibgib-component-service.mjs"; import { getColorStrings, } from "@ibgib/web-gib/dist/helpers.mjs"; import { alertUser, copyToClipboard, highlightElement, promptForText, shadowRoot_getElementById, } from "@ibgib/web-gib/dist/helpers.web.mjs"; import { getAgentForDomainIbGib, getAgents, registerDomainIbGibWithAgentIndex } from "@ibgib/web-gib/dist/witness/agent/agent-helpers.mjs"; import { AgentWitnessAny, } from "@ibgib/web-gib/dist/witness/agent/agent-one-file.mjs"; import { GEMINI_DEFAULT_MODEL_STR, } from "@ibgib/web-gib/dist/witness/agent/gemini/gemini-constants.mjs"; import { getAgentsSvc } from "@ibgib/web-gib/dist/witness/agent/agents-service-v1.mjs"; import { createProjectIbGib, isProjectIbGib_V1 } from "@ibgib/web-gib/dist/common/project/project-helper.mjs"; import { DEFAULT_PROJECT_DESCRIPTION, ProjectIbGib_V1 } from "@ibgib/web-gib/dist/common/project/project-types.mjs"; import { IbGibSettings, SettingsWithTabs } from "@ibgib/web-gib/dist/common/settings/settings-types.mjs"; import { SettingsType } from "@ibgib/web-gib/dist/common/settings/settings-constants.mjs"; import { AGENT_SPECIAL_IBGIB_TYPE_PROJECTAGENT, PROJECT_NAME_REGEXP, } from "@ibgib/web-gib/dist/common/project/project-constants.mjs"; import { AGENT_AVAILABLE_FUNCTIONS_PROJECTAGENT, } from "@ibgib/web-gib/dist/common/project/project-agent-functions.mjs"; import { GLOBAL_LOG_A_LOT, ARMY_STORE, BEE_KEY, BLANK_GIB_DB_NAME, } from "../../constants.mjs"; import { getComponentCtorArg, getIbGibGlobalThis_BlankGib, } from "../../helpers.web.mjs"; import { AGENT_INITIAL_CHAT_TEXT_PROJECTAGENT, AGENT_INITIAL_SYSTEM_TEXT_PROJECTAGENT, CHAT_WITH_AGENT_PLACEHOLDER_PROJECTAGENT, } from "../../agent-texts/project-agent-texts.mjs"; import { PROJECT_COMPONENT_NAME, ProjectComponentInstance, } from "./project/project-component-one-file.mjs"; import { getAppShellSvc } from "../../ui/shell/app-shell-service.mjs"; import { simpleIbGibRouterSingleton } from "../../ui/router/router-one-file.mjs"; import { AGENT_INITIAL_CHAT_TEXT_PROJECTSAGENT, AGENT_INITIAL_SYSTEM_TEXT_PROJECTSAGENT } from "../../agent-texts/projects-agent-texts.mjs"; const logalot = GLOBAL_LOG_A_LOT; export const AGENT_SPECIAL_IBGIB_TYPE_PROJECTSAGENT = 'projectsagent'; export const AGENT_AVAILABLE_FUNCTIONS_PROJECTSAGENT = [ tellUserFunctionInfo, // ...RenderAgentFunctionInfos, ]; export const PROJECTS_COMPONENT_NAME: string = 'ibgib-projects'; export class ProjectsComponentMeta extends IbGibDynamicComponentMetaBase { protected override lc: string = `[${ProjectsComponentMeta.name}]`; /** * temporary regexp path for our initial dev. this component will become * attached to actual ib^gib addrs * * * either the path is * * /apps/projects/gib/projects * * /apps/projects/ABC123.456DEF/project%20my-project%201744553032000 * * The point is that it's either a bland projects^gib to indicate create a * new project or it's a valid gib and a valid project ib. lazy regexp here */ routeRegExp?: RegExp = /apps\/projects\/(gib|.*)\/(projects|project \/.*\/.*)?/; componentName: string = PROJECTS_COMPONENT_NAME; constructor() { super(getComponentCtorArg()); customElements.define(this.componentName, ProjectsComponentInstance); } async createInstance({ path, ibGibAddr }: { /** * todo: store this in the instance (i think) but will change this when needed */ path: string; ibGibAddr: IbGibAddr; }): Promise { const lc = `${this.lc}[${this.createInstance.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: 65da57b67d533197f95d26aca2f03c25)`); } const component = document.createElement(this.componentName) as ProjectsComponentInstance; 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.`); } } } } /** * helper interface for managing tabs and their associated ibgibs */ interface ProjectTabInfo extends ChildInfoBase { // tabBtnEl: HTMLElement; // addr: IbGibAddr; /** * access the ibGib via this component. * * This component wraps an ibgib proxy that automatically stays up-to-date * when new ibgib frames are added to the ibgib's timeline and published to * the metaspace. (via metaspace.registerNewIbGib) */ // projectComponent?: ProjectComponentInstance; // agent?: AgentWitnessAny; // active: boolean; } interface ProjectsElements extends ElementsBase { headerEl: HTMLElement; headerTabsEl: HTMLElement; footerEl: HTMLElement; addBtnEl: HTMLButtonElement; ellipsisBtnEl: HTMLElement; ellipsisPopoverEl: HTMLElement; } export class ProjectsComponentInstance extends IbGibDynamicComponentInstanceBase_ParentOfTabs implements IbGibDynamicComponentInstance { protected override lc: string = `[${ProjectsComponentInstance.name}]`; // projectTabInfos: ProjectTabInfo[] = []; get activeProjectTabInfo(): ProjectTabInfo | undefined { return this.childInfos.find(x => x.active); } metaspace: MetaspaceService | undefined; protected get settingsType(): SettingsType { return SettingsType.projects; } constructor() { super(); } override async initialize(opts: IbGibDynamicComponentInstanceInitOpts): Promise { const lc = `${this.lc}[${this.initialize.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: aec41edb278ffac62f77c757f8595725)`); } // before any initialization, we want to ensure we are bootstrapped // await getIbGibGlobalThis_BlankGib().bootstrapPromise; // this is in the super call now if (!this.metaspace) { // wait for the metaspace to be initialized this.metaspace = await getGlobalMetaspace_waitIfNeeded({ delayIntervalMs: 50 }); } opts.ibGibAddr = await this.metaspace.getLatestAddr({ addr: opts.ibGibAddr }) ?? opts.ibGibAddr; await super.initialize(opts); await this.setBreadcrumbs(); this.agentsInitialized = this.initAgents(); // spins off } 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: 4e3e670ab00fc2dad9c2912e6344d425)`); } // const { meta, htmlPath, scriptPaths, cssPaths } = opts; // does nothing atow await this.initElements(); if (!this.elements) { throw new Error(`(UNEXPECTED) just initElements but this.elements falsy? (E: 931b47d386e41c3b8d58d67ad0ec4825)`); } const { } = this.elements; await this.agentsInitialized; const { gib } = getIbAndGib({ ibGibAddr: this.ibGibAddr }); if (isPrimitive({ gib })) { if (logalot) { console.log(`${lc} ibGibAddr has primitive gib, so returning early. (I: c68201caad350aa385bf6b6bf58ee525)`); } return; /* <<<< returns early */ } // await this.activateProject({ projectAddr: this.ibGibAddr, }); this.activateIbGib({ addr: this.ibGibAddr, }); // spin off, because super.activateIbGib awaits this created promise const globalBlankGib = getIbGibGlobalThis_BlankGib(); globalBlankGib.projectsComponent = this; } 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: d85a47973e435e5476265dbcfeba9c25)`); } // no action atow } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } public async newProject({ projectSpaceId, initialCommentText, srcIbGibAddr, }: { /** * @property projectSpaceId - id of the local space in which the project's * ibgib will be accessed. */ projectSpaceId?: SpaceId; initialCommentText?: string; /** * @property srcIbGibAddr - Optional address of an existing ibGib (likely a * comment) to use as the initial source or context for the new project. */ srcIbGibAddr?: IbGibAddr; }): Promise { const lc = `${this.lc}[${this.newProject.name}]`; try { await this.disableNewProjectUI(); // #region init/validate if (!this.metaspace) { throw new Error(`(UNEXPECTED) this.metaspace falsy? (E: d0c5d3df962949420e641d9160b85625)`); } const space = await this.metaspace.getLocalUserSpace({ localSpaceId: projectSpaceId, lock: false }); if (!space) { throw new Error(`couldn't get local user space (${projectSpaceId ?? '[default local user space]'}) from metaspace. (E: 1bc833e38cb231eedac30facf6d40325)`); } let srcCommentIbGib: CommentIbGib_V1 | undefined = undefined; if (srcIbGibAddr) { const resGetSrc = await this.metaspace.get({ addrs: [srcIbGibAddr], space, }); if (resGetSrc.errorMsg || resGetSrc.ibGibs?.length !== 1) { throw new Error(`couldn't get srcIbGibAddr (${srcIbGibAddr}) from space (${space.ib}) (E: 0314d4c477e3aed3c75a4358625ee825)`); } srcCommentIbGib = resGetSrc.ibGibs![0] as CommentIbGib_V1; if (!isComment({ ibGib: srcCommentIbGib })) { throw new Error(`srcIbGibAddr (${srcIbGibAddr}) is not a comment ibgib. (E: 2e68b8efcd11dd8428ee3d68bb6d7825)`); } } // #endregion init/validate let name: string = ''; do { name = await promptForText({ title: 'project name?', msg: [ `Name of the new project?`, `(only alphanumerics, spaces, hyphens, underscores)`, `If you leave this blank, a random "temporary" name will be used.`, ].join('\n'), cancelable: false, // should be cancelable, but we're going to interpret this as use a default name. confirm: false, }); if (name) { // they've entered something truthy, so regexp test it if (!PROJECT_NAME_REGEXP.test(name)) { await alertUser({ title: 'invalid project name', msg: `Doh. The project name can only contain alphanumerics (letters/digits), spaces, hyphens, and underscores. Here is the nerdy regex: ${PROJECT_NAME_REGEXP}`, }); name = ''; } } else { // cancelled/empty, so default it name = `untitled-${pickRandom_Letters({ count: 8 })}`; } } while (!name); const resProjectIbGib = await createProjectIbGib({ name, description: DEFAULT_PROJECT_DESCRIPTION, space, saveInSpace: true, srcCommentIbGib, }); const projectIbGib = resProjectIbGib.newIbGib; const projectAddr = getIbGibAddr({ ibGib: projectIbGib }); await this.metaspace.registerNewIbGib({ ibGib: projectIbGib }); if (logalot) { console.log(`${lc} starting... (I: 87460f0ca94356520a6255981f6df725)`); } const agentsSvc = getAgentsSvc(); // Assuming getAgentsSvc is available const newAgentIbGib = await agentsSvc.createNewAgent({ metaspace: this.metaspace, superSpace: undefined, // uses default local user space as the super space name: `ProjectAgent-${this.instanceId}`, api: 'gemini', model: GEMINI_DEFAULT_MODEL_STR, availableFunctions: clone(AGENT_AVAILABLE_FUNCTIONS_PROJECTAGENT), initialSystemText: [ AGENT_INITIAL_SYSTEM_TEXT_PROJECTAGENT, ].join('\n'), initialChatText: [ AGENT_INITIAL_CHAT_TEXT_PROJECTAGENT, ].join('\n'), fnGetAPIKey: this.getFnGetAPIKey(), type: AGENT_SPECIAL_IBGIB_TYPE_PROJECTAGENT, addToAgentsTag: true, }); // 2. Register the domain ibGib (project) with the new agent in the index await registerDomainIbGibWithAgentIndex({ domainIbGib: projectIbGib, agentIbGib: newAgentIbGib, metaspace: this.metaspace, space, }); await newAgentIbGib.setActiveContext({ contextIbGib: projectIbGib, }); // await this.activateProject({ // projectIbGib: projectIbGib, // projectAddr: projectAddr, // }); await this.activateIbGib({ ibGib: projectIbGib, addr: projectAddr, }); return projectIbGib; } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { await this.enableNewProjectUI(); if (logalot) { console.log(`${lc} complete.`); } } } /** * I'm sketching this thinking that it will be useful for commands (via * agents) to get the relevant project component. * * The Projects component atow (05/2025) is basically a singleton, so this * provides a way of getting at the component from a command. I'm not sure * if this is the right way or if it will be used though. * * @returns the corresponding project tab info if exists, else undefined. */ public async getProjectTab({ ibGibAddr, ibGib, }: { ibGibAddr?: IbGibAddr, ibGib?: ProjectIbGib_V1, }): Promise { const lc = `${this.lc}[${this.getProjectTab.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: 69c1d8b32ba9dd403a911bc5f0095425)`); } if (!ibGibAddr && !ibGib) { throw new Error(`either ibGibAddr or ibGib is required. (E: 42811cc61d78cc83a21b6528c6d58825)`); } ibGibAddr ??= getIbGibAddr({ ibGib }); const gibInfo = getGibInfo({ ibGibAddr }); const tjpGib = gibInfo.tjpGib ?? getIbAndGib({ ibGibAddr }); const filtered = this.childInfos.filter(x => { const xGibInfo = getGibInfo({ ibGibAddr: x.addr }); const xTjpGib = xGibInfo.tjpGib ?? getIbAndGib({ ibGibAddr: x.addr }); return tjpGib === xTjpGib; }); if (filtered.length === 0) { return undefined; } else if (filtered.length === 1) { return filtered[0]; } else { console.warn(`${lc} more than one tab opened for the same tjpGib (${tjpGib})? returning the first one but this doesn't seem right. (W: 0c67f8e2f8eda7cb886343b8f1736b25)`); return filtered[0]; } } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } private async disableNewProjectUI(): Promise { const lc = `${this.lc}[${this.disableNewProjectUI.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: ea9c02985442ca330759217f29b90c25)`); } if (!this.elements) { throw new Error(`(UNEXPECTED) this.elements falsy? (E: f0a77baed7c2e5acccd078baf8b0aa25)`); } const { addBtnEl } = this.elements; addBtnEl.disabled = true; addBtnEl.textContent = '…'; } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } private async enableNewProjectUI(): Promise { const lc = `${this.lc}[${this.enableNewProjectUI.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: 91e16b23d2271a6afe8852b8bc77f525)`); } if (!this.elements) { throw new Error(`(UNEXPECTED) this.elements falsy? (E: 3a1ac8e82351a128fff208ccac468f25)`); } const { addBtnEl } = this.elements; addBtnEl.disabled = false; addBtnEl.textContent = '+'; } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } public override async activateIbGib({ addr, ibGib, }: { addr?: IbGibAddr; ibGib?: IbGib_V1; }): Promise { const lc = `${this.lc}[${this.activateIbGib.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: a178721ccbe8d7f753a06bc100b16825)`); } if (!this.metaspace) { debugger; throw new Error(`(UNEXPECTED) this.metaspace falsy? (E: 30b7489383281225fa3b768885c3dd25)`); } await super.activateIbGib({ addr, ibGib }); if (!this.activeChildInfo) { throw new Error(`(UNEXPECTED) this.activeChildInfo falsy after calling activateIbGib? (E: 7a5845a43e9adf178d822b68336b3825)`); } ibGib ??= this.activeChildInfo.component?.ibGib if (ibGib && isProjectIbGib_V1(ibGib)) { // init the agent to listen for context events const space = await this.metaspace.getLocalUserSpace({ lock: false }); if (!space) { throw new Error(`(UNEXPECTED) couldn't get default local user space? (E: a524ecb58d65b524618a58dad7b07f25)`); } const projectAgent = await getAgentForDomainIbGib({ ibGib, metaspace: this.metaspace, space, }); if (!projectAgent) { throw new Error(`projectAgent not found for project (${addr}) (E: 8e88748121d24c82552387011863e225)`); } await projectAgent.setActiveContext({ contextIbGib: ibGib, }); // update the location await simpleIbGibRouterSingleton.updateCurrentURLPathIbGibAddr({ ibGibAddr: this.activeChildInfo.addr, replace: true, }); // update breadcrumb await this.setBreadcrumbs(); // update left panel to ensure we're on the Projects Tab const appShellSvc = getAppShellSvc(); appShellSvc.activateLeftPanelTab({ tabName: 'projects' }); // update right panel - project chronology await appShellSvc.activateRightPanelTab({ tabName: "chronologys", ibGibAddr: this.activeChildInfo.addr, }); // update footer panel's input while (!appShellSvc.inputComponent) { await delay(100); console.log(`${lc} appShellSvc.inputComponent still falsy. probably just page load. will retry... (I: 2628de248d662dc8184ed73c98bb9225)`) } if (!this.activeProjectTabInfo) { throw new Error(`(UNEXPECTED) this.activeProjectTabInfo falsy? (E: 1c50943dcd8f59297f8bbe5635bc8a25)`); } if (!this.activeProjectTabInfo.component?.ibGibProxy) { throw new Error(`(UNEXPECTED) this.activeProjectTabInfo.component?.ibGibProxy falsy? (E: d54f26a14cbb046aa900dea638257f25)`); } await appShellSvc.inputComponent.setContextInfo({ info: { agent: projectAgent, placeholderText: CHAT_WITH_AGENT_PLACEHOLDER_PROJECTAGENT, contextProxyIbGib: this.activeProjectTabInfo.component.ibGibProxy, // default to default local user space for now spaceId: undefined, }, }); } else { console.error(`${lc} ibGib is falsy or not a project ibGib? this is not necessarily an error, I just don't know about this state of things at this point. this "error" is just logged to the console, i.e., wasn't thrown. (E: d5c997bdbf28274ed80b057242768825)`); } } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } // /** // * Loads the ibGib if only addr is provided. // * // * Then manages the UI: // * // * - If a tab is already created for the project and active, then does nothing. // * - If it's already created but inactive, then activates tab and loads content // * - if no tab, then creates it, activates it, loads content. // * // * To do this, it creates/hydrates a {@link ProjectTabInfo} and activates // * it. // * // * @see {@link ProjectTabInfo.active} // * @see {@link activeProjectTabInfo} // */ // private async activateProject({ // projectAddr, // projectIbGib, // }: { // projectAddr?: IbGibAddr, // projectIbGib?: IbGib_V1, // }): Promise { // const lc = `${this.lc}[${this.activateProject.name}]`; // try { // if (logalot) { console.log(`${lc} starting... (I: 2d596a9c36f7e5fac4b8bbc6f0e84d25)`); } // if (!this.elements) { throw new Error(`(UNEXPECTED) this.elements falsy? (E: a574f48ef54d75d0997933fbed55eb25)`); } // const { contentEl } = this.elements; // // #region init/validate // if (!this.metaspace) { throw new Error(`(UNEXPECTED) this.metaspace falsy? (E: d647670e3e2a436862719c5b1f324125)`); } // if (projectIbGib && projectAddr && getIbGibAddr({ ibGib: projectIbGib }) !== projectAddr) { throw new Error(`(UNEXPECTED) addr !== getIbGibAddr({ibGib})? (E: 6d12ed6a578cf3c466a6c7acd645b625)`); } // projectAddr ??= getIbGibAddr({ ibGib: projectIbGib }); // // #endregion init/validate // // super.activateIbGib({}) // /** // * the point of this function is to populate this and make it active // */ // const projectTabInfo = await this.getLoadedChildInfo({ // addr: projectAddr, // ibGib: projectIbGib, // }); // if (projectTabInfo.active) { // // already active? // // debugger; // warning/error...activating an already active tab? // console.warn(`${lc} tab already active (W: 5fc45570f99f8fde3c5a887ca42e0a25)`); // return; /* <<<< returns early */ // } // projectIbGib ??= projectTabInfo.component?.ibGib; // if (!projectIbGib) { throw new Error(`(UNEXPECTED) projectIbGib falsy? the projectTabInfo should be fully loaded at this point, which should include a projectComponent instance. That component should have its ibGib value populated. (E: 73323f5cc6ed3bede337e646322c8e25)`); } // // at this point, we are guaranteed to have a non-active // // projectTabInfo, so deactivate the old, and activate the new // const currentlyActive = this.activeProjectTabInfo; // if (currentlyActive) { // currentlyActive.childBtnEl.classList.remove('active'); // currentlyActive.active = false; // contentEl.innerHTML = ''; // } // // init the agent to listen for context events // const space = await this.metaspace.getLocalUserSpace({ lock: false }); // if (!space) { throw new Error(`(UNEXPECTED) couldn't get default local user space? (E: a524ecb58d65b524618a58dad7b07f25)`); } // const projectAgent = await getAgentForDomainIbGib({ // ibGib: projectIbGib, // metaspace: this.metaspace, // space, // }); // if (!projectAgent) { // throw new Error(`projectAgent not found for project (${projectAddr}) (E: 8e88748121d24c82552387011863e225)`); // } // await projectAgent.setActiveContext({ // contextIbGib: projectIbGib, // }); // // activate the new tab // projectTabInfo.childBtnEl.classList.add('active'); // projectTabInfo.childBtnEl.scrollIntoView({ behavior: 'smooth' }); // projectTabInfo.active = true; // if (!this.childInfos.some(x => x.addr === projectTabInfo.addr)) { // this.childInfos.push(projectTabInfo); // } // if (!projectTabInfo.component) { throw new Error(`(UNEXPECTED) projectTabInfo.component falsy? should be populated by this point in code (E: 5d766308dcb6a30e6c262f4633505125)`); } // const componentSvc = await getComponentSvc(); // await componentSvc.inject({ // parentEl: contentEl, // componentToInject: projectTabInfo.component, // }); // // update tab button colors // // projectTabInfo.component.style.getPropertyValue() // // update the location // await simpleIbGibRouterSingleton.updateCurrentURLPathIbGibAddr({ // ibGibAddr: projectTabInfo.addr, // replace: true, // }); // // update breadcrumb // await this.setBreadcrumbs(); // // update left panel to ensure we're on the Projects Tab // const appShellSvc = getAppShellSvc(); // appShellSvc.activateLeftPanelTab({ tabName: 'projects' }); // // update right panel - project chronology // await appShellSvc.activateRightPanelTab({ // tabName: "chronologys", // ibGibAddr: projectTabInfo.addr, // }); // // update footer panel's input // while (!appShellSvc.inputComponent) { // await delay(100); // console.log(`${lc} appShellSvc.inputComponent still falsy. probably just page load. will retry... (I: 2628de248d662dc8184ed73c98bb9225)`) // } // if (!this.activeProjectTabInfo) { throw new Error(`(UNEXPECTED) this.activeProjectTabInfo falsy? (E: 1c50943dcd8f59297f8bbe5635bc8a25)`); } // if (!this.activeProjectTabInfo.component?.ibGibProxy) { throw new Error(`(UNEXPECTED) this.activeProjectTabInfo.component?.ibGibProxy falsy? (E: d54f26a14cbb046aa900dea638257f25)`); } // await appShellSvc.inputComponent.setContextInfo({ // info: { // agent: projectAgent, // placeholderText: CHAT_WITH_AGENT_PLACEHOLDER_PROJECTAGENT, // contextProxyIbGib: this.activeProjectTabInfo.component.ibGibProxy, // // default to default local user space for now // spaceId: undefined, // }, // }); // } catch (error) { // console.error(`${lc} ${extractErrorMsg(error)}`); // throw error; // } finally { // if (logalot) { console.log(`${lc} complete.`); } // } // } // protected getLoadedTabInfo({ addr, ibGib, }: { addr: IbGibAddr; ibGib?: IbGib_V1; }): Promise { // throw new Error("Method not implemented."); // } /** * @internal * gets the project tab info for the given addr. * * creates and loads the project ibgib component, which itself loads the * ibgib internally (or throws) if not already loaded. * * @returns ProjectTabInfo with fully loaded component and ibgib. * * @see {@link ProjectTabInfo} */ protected async getLoadedChildInfo({ addr, ibGib, }: { addr: IbGibAddr, ibGib?: IbGib_V1, }): Promise { const lc = `${this.lc}[${this.getLoadedChildInfo.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: 8bc42ecb105f4ca9dff6590c1a9c2f25)`); } if (!addr) { throw new Error(`(UNEXPECTED) addr falsy? (E: 25e89572d09ae8dd581ba60bde1c9825)`); } const tjpGib = getGibInfo({ ibGibAddr: addr }).tjpGib ?? getIbAndGib({ ibGibAddr: addr }).gib; const fnCreateAndLoadProjectComponent = async () => { const componentSvc = await getComponentSvc(); const projectComponent = await componentSvc.getComponentInstance({ path: PROJECT_COMPONENT_NAME, ibGibAddr: addr, useRegExpPrefilter: true, }) as ProjectComponentInstance | undefined; if (!projectComponent) { debugger; // error couldn't create component instance for project? throw new Error(`(UNEXPECTED) projectComponent falsy? couldn't create component instance for project? (E: efeee6b003f5b0af9b0fda679593c725)`); } // await projectComponent.loadIbGib(); // await projectComponent.initialized; if (!projectComponent.ibGib) { debugger; // error couldn't load ibGib for project? throw new Error(`(UNEXPECTED) ibGib falsy? couldn't load ibGib for project? (E: 352f152063a97e67f769babfadfe1e25)`); } // guaranteed loaded ibGib return projectComponent; } /** the point of this function is to populate this */ let projectTabInfo: ProjectTabInfo; /** * filtered for same timeline of addr via tjpGib */ const filtered = this.childInfos.filter(x => { const tabGibInfo = getGibInfo({ ibGibAddr: x.addr }); const tabTjpGib = tabGibInfo.tjpGib ?? getIbAndGib({ ibGibAddr: x.addr }).gib; return tabTjpGib === tjpGib; }); if (filtered.length > 0) { // already have an existing tab // const filtered = this.childInfos.filter(x => x.addr === addr); if (filtered.length !== 1) { throw new Error(`(UNEXPECTED) filtered.length !== 1? (E: ef126a918a93f1c13d0c4b41d5194b25)`); } projectTabInfo = filtered[0]; if (projectTabInfo.component) { console.log(`${lc} projectTabInfo.component already truthy. (I: f752651269b2ed7151c78668418a7825)`); } else { projectTabInfo.component = await fnCreateAndLoadProjectComponent(); // ibGib = projectTabInfo.component!.ibGib!; } } else { // no existing tab, so create new project tab info const projectComponent = await fnCreateAndLoadProjectComponent(); ibGib = projectComponent.ibGib!; // guaranteed in above fn // both addr and ibGib guaranteed now const tabBtnEl = await this.addChild({ addr, ibGib }) projectTabInfo = { addr, childBtnEl: tabBtnEl, component: projectComponent, active: false, }; } return projectTabInfo; } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } /** * creates a new tab element (span atow 03/2025), adds it to the * headerTabsEl and returns the new span element. * * @returns the newly created tab span element */ protected override async addChild({ addr, ibGib, }: { ibGib: IbGib_V1, addr?: IbGibAddr, }): Promise { const lc = `${this.lc}[${this.addChild.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: e6657888d2b42cfdef6df03d89f3e625)`); } // #region init/validate if (!this.elements) { throw new Error(`(UNEXPECTED) this.elements falsy? (E: 9f5ad8d5f35ded4496a68ac6b73ac725)`); } const { headerTabsEl } = this.elements; addr ??= getIbGibAddr({ ibGib }); if (addr !== getIbGibAddr({ ibGib })) { throw new Error(`(UNEXPECTED) addr !== getIbGibAddr({ibGib})? (E: 1981efb1b691c154670376934336d125)`); } const tjpAddr = getTjpAddr({ ibGib, defaultIfNone: 'incomingAddr' }); const { gib: tjpGib } = getIbAndGib({ ibGibAddr: tjpAddr }); // #endregion init/validate // create the tab button element const span = document.createElement('span'); span.id = `projects-tab-button-${addr}`; span.classList.add('panel-tab-button'); // if (activate) { span.classList.add('active'); } this.updateTabTitleAndText({ span, ibGib }); let { punctiliarColor, punctiliarColorTranslucent, tjpColor, tjpColorContrast, tjpColorTranslucent, errorMsg } = getDeterministicColorInfo({ ibGib, translucentAlpha: 70 }); if (!errorMsg) { span.style.borderColor = tjpColor ?? punctiliarColor; span.style.backgroundColor = tjpColorTranslucent ?? punctiliarColorTranslucent; span.style.color = tjpColorContrast ?? getColorStrings(90, tjpGib).at(2) ?? 'red' // 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: f837c92aa2876b444707f0b229fb8e25)`); } headerTabsEl.appendChild(span); span.addEventListener('click', async (event) => { // if (logalot) { console.log(`${lc} activating...`); } // await this.activateProject({ projectAddr: addr }); await this.activateIbGib({ addr }); }); if (!this.metaspace) { throw new Error(`(UNEXPECTED) this.metaspace falsy? (E: 56ed15004c98e40a95db20144e726825)`); } if (!this.metaspace.latestObs) { throw new Error(`(UNEXPECTED) this.metaspace.latestObs falsy? (E: bab9882340fcdc29242045e8c2d2fe25)`); } await this.metaspace.latestObs?.subscribe(fnObs({ next: async (updateInfo: IbGibTimelineUpdateInfo) => { if (updateInfo.tjpAddr !== tjpAddr) { return; /* <<<< returns early */ } if (updateInfo.latestIbGib) { this.updateTabTitleAndText({ span, ibGib: updateInfo.latestIbGib }) } else { console.error(`{lc}[next] updateInfo.latestIbGib falsy? (E: a8558bdc53889ee07d8d78178328ae25)`); } }, complete: async () => { console.warn(`${lc}[complete] completed executed? (W: a2df080a4f8ef87df5266888109dfe25)`); }, error: async (error) => { debugger; // error in metaspace.latestObs dispatch? console.error(`${lc}[error] ${extractErrorMsg(error)}`); }, })); return span; } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } protected removeTabBtn({ tabInfo, }: { tabInfo: ProjectTabInfo; }): Promise { throw new Error("Method not implemented."); } private updateTabTitleAndText({ span, ibGib, }: { span: HTMLElement, ibGib: IbGib_V1, }): void { const lc = `[${this.updateTabTitleAndText.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: d1c34e159aecc0bf06f57a759b06a625)`); } if (!ibGib.data) { throw new Error(`(UNEXPECTED) ibGib.data falsy? (E: a22dd936f38a779c0d61489c7d7c9125)`); } if (!ibGib.data.name) { console.warn(`${lc} ibGib.data.name falsy? (W: genuuid)`); } const title = ibGib.data.name ?? 'untitled' const desc = ibGib.data.description ?? ''; span.title = desc ? `${title}\n${desc}` : title; const MAX_TAB_TEXT_LENGTH = 12; span.textContent = getSaferSubstring({ text: title, length: MAX_TAB_TEXT_LENGTH, }); if (title.length > MAX_TAB_TEXT_LENGTH) { span.textContent += '…'; } } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } // private async activateTab({ // addr, // tabSpan, // }: { // addr: IbGibAddr, // tabSpan: HTMLSpanElement, // }): Promise { // const lc = `${this.lc}[${this.activateTab.name}]`; // try { // if (logalot) { console.log(`${lc} starting... (I: 3703365ccbfb68df570991962aaf7925)`); } // // if (!this.elementsheaderEl) { throw new Error(`(UNEXPECTED) this.headerEl falsy? (E: b61066b20aa36f902dbe474577abea25)`); } // } catch (error) { // console.error(`${lc} ${extractErrorMsg(error)}`); // throw error; // } finally { // if (logalot) { console.log(`${lc} complete.`); } // } // } async initElements(): Promise { const lc = `${this.lc}[${this.initElements.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: 0001223d063c984cd870e9e440725425)`); } if (!this.shadowRoot) { throw new Error(`(UNEXPECTED) this.shadowRoot falsy? (E: e2d3077643f8c314fd050706ae64ca25)`); } const headerEl = shadowRoot_getElementById(this.shadowRoot, 'projects-header'); // headerTabsEl const headerTabsEl = shadowRoot_getElementById(this.shadowRoot, 'projects-header-tabs'); const contentEl = shadowRoot_getElementById(this.shadowRoot, 'projects-content'); const pContent = document.createElement('p'); pContent.textContent = '[no project loaded...try creating one]'; pContent.style.lineHeight = `${contentEl.clientHeight}px`; pContent.style.textAlign = 'center'; pContent.style.fontStyle = 'italic'; contentEl.appendChild(pContent); const footerEl = shadowRoot_getElementById(this.shadowRoot, 'projects-footer'); footerEl.style.display = 'none'; // addBtnEl // projects-header-add-btn const addBtnEl = shadowRoot_getElementById(this.shadowRoot, 'projects-header-add-btn'); addBtnEl.addEventListener('click', async (event) => { await this.newProject({}); }); // ellipsisBtnEl const ellipsisBtnEl = shadowRoot_getElementById(this.shadowRoot, 'projects-header-ellipsis-btn'); // ellipsisPopoverEl - when user clicks ellipsis, this popover has the options // of what exactly to add const ellipsisPopoverEl = shadowRoot_getElementById(this.shadowRoot, 'ellipsis-popover'); const ellipsisPopoverOptions = ellipsisPopoverEl.querySelectorAll('.ellipsis-popover-option'); ellipsisBtnEl.addEventListener('click', async (event) => { ellipsisPopoverEl.style.position = 'absolute'; ellipsisPopoverEl.style.top = `${ellipsisBtnEl.offsetTop + ellipsisBtnEl.clientHeight}px`; ellipsisPopoverEl.style.left = `${ellipsisBtnEl.offsetLeft}px`; }); // Event listeners for popover options ellipsisPopoverOptions.forEach(option => { option.addEventListener('click', async (event: Event) => { try { const target = event.target as HTMLElement; const optionType = target.getAttribute('data-option'); if (optionType) { await this.handleEllipsisPopoverSelected(optionType); } } catch (error) { const emsg = `${lc}[ellipsisPopoverOption] ${extractErrorMsg(error)} (E: 8468587582a85f00f8a71e12282a7825)`; console.error(emsg); alertUser({ msg: emsg, title: 'export failed...' }); // spins off } finally { // Hide popover after selection ellipsisPopoverEl.hidePopover(); } }); }); this.elements = { headerEl, headerTabsEl, addBtnEl, ellipsisBtnEl, ellipsisPopoverEl, contentEl, footerEl, } } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } /** * exports the current project. If more than one is open, exports the currently active project. * * Should this also include exporting the project's agent? As a flag */ private async exportProject({ compress }: { compress: boolean }): Promise { const lc = `${this.lc}[${this.exportProject.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: 4912c1a75be42f7cb8cefc88472eea25)`); } // if (!this.ibGib) { throw new Error(`(UNEXPECTED) this.ibGib falsy? (E: b50b68bbde884e5928602f77d7579a25)`); } if (!this.activeProjectTabInfo) { throw new Error(`(UNEXPECTED) this.activeProjectTabInfo falsy? (E: 1e99c827600de57928dd3568adebf825)`); } if (!this.activeProjectTabInfo.component) { throw new Error(`(UNEXPECTED) this.activeProjectTabInfo.component falsy? (E: d5c0b80d178860cb484741c808126225)`); } if (!this.activeProjectTabInfo.component.ibGib) { throw new Error(`(UNEXPECTED) this.activeProjectTabInfo.component.ibGib falsy? (E: 64e1c88b22044e63d8d5dea8c3932825)`); } await this.exportIbGib({ ibGib: this.activeProjectTabInfo.component.ibGib, compress, }); } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } private async importProject(): Promise { const lc = `[${this.importProject.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: 73e3ca46756816843659c8ae7f438e25)`); } throw new Error(`not implemented (yet!!) ...delayed because of thinking about import as basically being the same thing as a merge, which is non-trivial. work is mostly located in ibgib-dynamic-component-bases.mts importibGib, but need to pull out into a helper. that should actually just call the helper, passing in the components this.ibGib (can be overridden in descending concrete component classes, in case this.ibGib isn't what we want) (E: 6741dd74686156ac8878af58253dd825)`); } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } /** * Handler for when an option is selected from the add popover. */ private async handleEllipsisPopoverSelected(optionType: string): Promise { const lc = `${this.lc}[${this.handleEllipsisPopoverSelected.name}]`; try { if (logalot) { console.log(`${lc} starting... optionType: ${optionType} (I: genuuid)`); } // const getActiveTabIbGib: () => IbGib_V1 = () => { // if (!this.activeChildInfo) { throw new Error(`this.activeChildInfo falsy (E: genuuid)`); } // if (!this.activeChildInfo.component) { throw new Error(`this.activeChildInfo.component falsy. (E: genuuid)`); } // const ibGib = this.activeChildInfo.component.ibGib; // if (!ibGib) { throw new Error(`(UNEXPECTED) active tab's component's ibgib falsy? (E: genuuid)`); } // return ibGib; // } switch (optionType) { case 'copy-active-project-address': if (this.activeProjectTabInfo) { copyToClipboard({ data: { text: this.activeProjectTabInfo.addr }, }); highlightElement({ el: this.activeProjectTabInfo.childBtnEl, magicHighlightTimingMs: 1_000 }); // spin off so options disappear } else { await alertUser({ title: 'No Project?', msg: `There was no project tab found? This seems like a bug so you should report it please. (E: 249298d98278b3e07fa7261a8d009f25)` }); } break; case 'export-project': await this.exportProject({ compress: false }); break; case 'export-project-gzip': await this.exportProject({ compress: true }); break; case 'import-project': await this.importProject(); break; default: throw new Error(`(UNEXPECTED) invalid optionType (${optionType})? (E: genuuid)`); } } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } protected override async initAgents(): Promise { const lc = `${this.lc}[${this.initAgents.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: 5629caed7bb86623facfde3caf202c25)`); } // if (!this.elements) { throw new Error(`(UNEXPECTED) this.elements falsy? (E: e141c6a515a92f2e68c58b5dcf2eac25)`); } // const {} = this.elements; this.metaspace = await getGlobalMetaspace_waitIfNeeded(); let agent: AgentWitnessAny | undefined = undefined; let agents = await getAgents({ metaspace: this.metaspace, type: AGENT_SPECIAL_IBGIB_TYPE_PROJECTSAGENT, spaceId: undefined, // explicitly use default local space just to show this option bc it's early in life }); if (agents.length > 0) { agent = agents.at(0)!; } else { const agentsSvc = getAgentsSvc(); agent = await agentsSvc.createNewAgent({ metaspace: this.metaspace, superSpace: undefined, // uses default local user space as the super space name: `ProjectsAgent-${this.instanceId}`, api: 'gemini', model: GEMINI_DEFAULT_MODEL_STR, availableFunctions: [ ...AGENT_AVAILABLE_FUNCTIONS_PROJECTSAGENT, ], initialSystemText: AGENT_INITIAL_SYSTEM_TEXT_PROJECTSAGENT, initialChatText: AGENT_INITIAL_CHAT_TEXT_PROJECTSAGENT, fnGetAPIKey: this.getFnGetAPIKey(), type: AGENT_SPECIAL_IBGIB_TYPE_PROJECTSAGENT, addToAgentsTag: true, }); } this.agents = [agent]; if (!this.agent) { throw new Error(`(UNEXPECTED) agent falsy after createNewAgent? (E: 6ea8e8b7aa551bcf467042dbabab9425)`); } } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } private getFnGetAPIKey(): () => Promise { const lc = `${this.lc}[${this.getFnGetAPIKey.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: 18dc13812e3657d0e2874b72123c6125)`); } const fn = async () => { let apiKey = await storageGet({ dbName: BLANK_GIB_DB_NAME, storeName: ARMY_STORE, key: BEE_KEY, }); return apiKey ?? ''; }; return fn; } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } private async setBreadcrumbs(): Promise { const lc = `${this.lc}[${this.setBreadcrumbs.name}]`; try { if (logalot) { console.log(`${lc} starting... (I: b18e0977f9213dca453689fa75326b25)`); } let appShellSvc = getAppShellSvc(); appShellSvc.initialized.then(async () => { try { const { breadcrumbComponent } = appShellSvc; if (!breadcrumbComponent) { throw new Error(`(UNEXPECTED) breadcrumbComponent is falsy? (E: 1d5b0148882b0a7f68aab9198ddf7925)`); } if (!this.meta) { throw new Error(`(UNEXPECTED) this.meta falsy? (E: f85649a6574eb111078faeca6dea5325)`); } await breadcrumbComponent.addBreadcrumb({ info: { text: 'projects', type: 'projects', // fnClickAction: () => Promise.resolve(), // do nothing }, clear: true, }); if (this.activeProjectTabInfo) { const { component: projectComponent } = this.activeProjectTabInfo; if (!projectComponent) { console.error(`${lc} (UNEXPECTED) activeProjectTabInfo truthy but its projectComponent falsy? Not throwing here, but returning early. (E: f21843e57044ec11680c078c3d0bdd25)`); return; /* <<<< returns early */ } if (!projectComponent.ibGib) { console.error(`${lc} (UNEXPECTED) activeProjectTabInfo truthy but its projectComponent.ibGib falsy? Not throwing here, but returning early. (E: de3167af997ca496599724198d12ed25)`); return; /* <<<< returns early */ } const projectIbGib = projectComponent.ibGib; if (isProjectIbGib_V1(projectIbGib)) { await breadcrumbComponent.addBreadcrumb({ info: { text: projectIbGib.data!.name, type: 'project', // in the future this will probably be highlight the project main tab // fnClickAction: () => Promise.resolve(), // do nothing }, clear: false, }); } else { // ? debugger; // want to see how we have an active project component ibgib but it's not a project ibgib } } } catch (error) { console.error(`${lc} couldn't set breadcrumbs for web1 component. (E: 5d255d9c91fdc126a46884cc74fada25)`); } }); } catch (error) { console.error(`${lc} ${extractErrorMsg(error)}`); throw error; } finally { if (logalot) { console.log(`${lc} complete.`); } } } }