From 10df866514e0c4e0537ba0546ea6e2b5a2360dcf Mon Sep 17 00:00:00 2001 From: Matic Jurglic Date: Fri, 27 Dec 2024 09:08:20 +0100 Subject: [PATCH 01/16] Open AI panel when app generator starts generating --- .../create-boxel-app-command.ts | 4 +++ .../create-product-requirements-command.ts | 7 +++++ .../experiments-realm/ai-app-generator.gts | 5 ++++ .../app/components/ai-assistant/panel.gts | 24 ++++++++-------- .../operator-mode/interact-submode.gts | 5 ++++ .../operator-mode/submode-layout.gts | 28 ++++++------------- packages/host/app/config/environment.d.ts | 2 +- packages/host/app/services/matrix-service.ts | 9 ++++++ .../services/operator-mode-state-service.ts | 13 +++++++++ packages/host/config/environment.js | 4 --- packages/runtime-common/index.ts | 1 + 11 files changed, 67 insertions(+), 35 deletions(-) diff --git a/packages/experiments-realm/AiAppGenerator/create-boxel-app-command.ts b/packages/experiments-realm/AiAppGenerator/create-boxel-app-command.ts index 58f088301e..56f1be60a1 100644 --- a/packages/experiments-realm/AiAppGenerator/create-boxel-app-command.ts +++ b/packages/experiments-realm/AiAppGenerator/create-boxel-app-command.ts @@ -16,12 +16,16 @@ export default class CreateBoxelApp extends Command< CardDef > { inputType = CreateProductRequirementsInput; + onRoomCreation: ((roomId: string) => void) | null = null; protected async run(input: CreateProductRequirementsInput): Promise { let createPRDCommand = new CreateProductRequirementsInstance( this.commandContext, undefined, ); + + createPRDCommand.onRoomCreation = this.onRoomCreation; + let { productRequirements: prdCard, roomId } = await createPRDCommand.execute(input); diff --git a/packages/experiments-realm/AiAppGenerator/create-product-requirements-command.ts b/packages/experiments-realm/AiAppGenerator/create-product-requirements-command.ts index 1e6642e520..f2c4d17103 100644 --- a/packages/experiments-realm/AiAppGenerator/create-product-requirements-command.ts +++ b/packages/experiments-realm/AiAppGenerator/create-product-requirements-command.ts @@ -33,6 +33,8 @@ export default class CreateProductRequirementsInstance extends Command< > { inputType = CreateProductRequirementsInput; + onRoomCreation: ((roomId: string) => void) | null = null; + get skillCard() { return new SkillCard({ id: 'prd-helper-skill', @@ -77,6 +79,11 @@ export default class CreateProductRequirementsInstance extends Command< let { roomId } = await createRoomCommand.execute({ name: 'Product Requirements Doc Creation', }); + + if (this.onRoomCreation) { + this.onRoomCreation(roomId); + } + let addSkillsToRoomCommand = new AddSkillsToRoomCommand( this.commandContext, ); diff --git a/packages/experiments-realm/ai-app-generator.gts b/packages/experiments-realm/ai-app-generator.gts index f85aaf8af6..4fd4d8a746 100644 --- a/packages/experiments-realm/ai-app-generator.gts +++ b/packages/experiments-realm/ai-app-generator.gts @@ -362,6 +362,10 @@ class DashboardTab extends GlimmerComponent<{ throw new Error('Missing commandContext'); } let command = new CreateBoxelApp(commandContext, undefined); + command.onRoomCreation = (roomId) => { + debugger; + this.args.context?.actions?.openRoom(roomId); + }; this.isGenerating = true; try { await command.execute( @@ -471,6 +475,7 @@ class RequirementsTab extends GlimmerComponent<{ if (!this.cardRef) { throw new Error('Can not create a card without a card ref.'); } + await this.args.context?.actions?.createCard?.( this.cardRef, this.args.currentRealm, diff --git a/packages/host/app/components/ai-assistant/panel.gts b/packages/host/app/components/ai-assistant/panel.gts index d20bbd7058..6c71bd590e 100644 --- a/packages/host/app/components/ai-assistant/panel.gts +++ b/packages/host/app/components/ai-assistant/panel.gts @@ -179,9 +179,9 @@ export default class AiAssistantPanel extends Component { {{else if this.isReady}} {{! below if statement is covered in 'isReady' check above but added due to glint not realizing it }} - {{#if this.currentRoomId}} + {{#if this.matrixService.currentRoomId}} {{/if}} @@ -362,7 +362,7 @@ export default class AiAssistantPanel extends Component { @service private declare router: RouterService; @service private declare commandService: CommandService; - @tracked private currentRoomId: string | undefined; + // @tracked private currentRoomId: string | undefined; @tracked private isShowingPastSessions = false; @tracked private roomToRename: SessionRoomData | undefined = undefined; @tracked private roomToDelete: SessionRoomData | undefined = undefined; @@ -403,14 +403,14 @@ export default class AiAssistantPanel extends Component { ]); } if (roomToEnter) { - this.currentRoomId = roomToEnter.roomId; + this.matrixService.currentRoomId = roomToEnter.roomId; return; } } let latestRoom = this.latestRoom; if (latestRoom) { - this.currentRoomId = latestRoom.roomId; + this.matrixService.currentRoomId = latestRoom.roomId; return; } @@ -423,8 +423,8 @@ export default class AiAssistantPanel extends Component { } private get roomResource() { - return this.currentRoomId - ? this.roomResources.get(this.currentRoomId) + return this.matrixService.currentRoomId + ? this.roomResources.get(this.matrixService.currentRoomId) : undefined; } @@ -464,7 +464,7 @@ export default class AiAssistantPanel extends Component { } catch (e) { console.log(e); this.displayRoomError = true; - this.currentRoomId = undefined; + this.matrixService.currentRoomId = undefined; } }); @@ -538,7 +538,7 @@ export default class AiAssistantPanel extends Component { @action private enterRoom(roomId: string, hidePastSessionsList = true) { - this.currentRoomId = roomId; + this.matrixService.currentRoomId = roomId; if (hidePastSessionsList) { this.hidePastSessions(); } @@ -591,7 +591,7 @@ export default class AiAssistantPanel extends Component { window.localStorage.removeItem(NewSessionIdPersistenceKey); } - if (this.currentRoomId === roomId) { + if (this.matrixService.currentRoomId === roomId) { window.localStorage.removeItem(CurrentRoomIdPersistenceKey); if (this.latestRoom) { this.enterRoom(this.latestRoom.roomId, false); @@ -624,7 +624,9 @@ export default class AiAssistantPanel extends Component { private get isReady() { return Boolean( - this.currentRoomId && this.maybeMonacoSDK && this.doCreateRoom.isIdle, + this.matrixService.currentRoomId && + this.maybeMonacoSDK && + this.doCreateRoom.isIdle, ); } } diff --git a/packages/host/app/components/operator-mode/interact-submode.gts b/packages/host/app/components/operator-mode/interact-submode.gts index d727984bf6..1de275aefe 100644 --- a/packages/host/app/components/operator-mode/interact-submode.gts +++ b/packages/host/app/components/operator-mode/interact-submode.gts @@ -329,6 +329,11 @@ export default class InteractSubmode extends Component { here.operatorModeStateService.updateCodePath(url); here.operatorModeStateService.updateSubmode(submode); }, + openRoom: async (roomId: string): Promise => { + here.operatorModeStateService.aiAssistantOpen = true; + // wait until room is loaded + this.matrixService.currentRoomId = roomId; + }, }; } stackBackgroundsState = stackBackgroundsResource(this); diff --git a/packages/host/app/components/operator-mode/submode-layout.gts b/packages/host/app/components/operator-mode/submode-layout.gts index 5315f0abc9..fef69b42d3 100644 --- a/packages/host/app/components/operator-mode/submode-layout.gts +++ b/packages/host/app/components/operator-mode/submode-layout.gts @@ -26,7 +26,7 @@ import AiAssistantPanel from '@cardstack/host/components/ai-assistant/panel'; import AiAssistantToast from '@cardstack/host/components/ai-assistant/toast'; import ProfileSettingsModal from '@cardstack/host/components/operator-mode/profile/profile-settings-modal'; import ProfileInfoPopover from '@cardstack/host/components/operator-mode/profile-info-popover'; -import ENV from '@cardstack/host/config/environment'; + import type IndexController from '@cardstack/host/controllers'; import { assertNever } from '@cardstack/host/utils/assert-never'; @@ -42,8 +42,6 @@ import WorkspaceChooser from './workspace-chooser'; import type MatrixService from '../../services/matrix-service'; import type OperatorModeStateService from '../../services/operator-mode-state-service'; -const { APP } = ENV; - interface Signature { Element: HTMLDivElement; Args: { @@ -83,7 +81,6 @@ type PanelWidths = { }; export default class SubmodeLayout extends Component { - @tracked private isAiAssistantVisible = false; @tracked private searchSheetMode: SearchSheetMode = SearchSheetModes.Closed; @tracked private profileSettingsOpened = false; @tracked private profileSummaryOpened = false; @@ -115,7 +112,7 @@ export default class SubmodeLayout extends Component { } private get aiAssistantVisibilityClass() { - return this.isAiAssistantVisible + return this.operatorModeStateService.aiAssistantOpen ? 'ai-assistant-open' : 'ai-assistant-closed'; } @@ -159,11 +156,6 @@ export default class SubmodeLayout extends Component { this.operatorModeStateService.updateSubmode(submode); } - @action - private toggleChat() { - this.isAiAssistantVisible = !this.isAiAssistantVisible; - } - @action private closeSearchSheet() { if (this.suppressSearchClose) { return; @@ -313,23 +305,21 @@ export default class SubmodeLayout extends Component { @onCardSelect={{this.handleCardSelectFromSearch}} @onInputInsertion={{this.storeSearchElement}} /> - {{#if (and APP.experimentalAIEnabled (not @hideAiAssistant))}} + {{#if (not @hideAiAssistant)}} {{/if}} {{#if (and - APP.experimentalAIEnabled - (not @hideAiAssistant) - this.isAiAssistantVisible + (not @hideAiAssistant) this.operatorModeStateService.aiAssistantOpen ) }} { @collapsible={{false}} > ; matrixURL: string; matrixServerName: string; - experimentalAIEnabled: boolean; + resolvedBaseRealmURL: string; hostsOwnAssets: boolean; realmsServed?: string[]; diff --git a/packages/host/app/services/matrix-service.ts b/packages/host/app/services/matrix-service.ts index 5578a153da..e8956d2f52 100644 --- a/packages/host/app/services/matrix-service.ts +++ b/packages/host/app/services/matrix-service.ts @@ -114,6 +114,7 @@ export default class MatrixService extends Service { @tracked private _isInitializingNewUser = false; @tracked private _isNewUser = false; @tracked private postLoginCompleted = false; + @tracked private _currentRoomId: string | undefined; profile = getMatrixProfile(this, () => this.userId); @@ -148,6 +149,14 @@ export default class MatrixService extends Service { this.currentUserEventReadReceipts.set(eventId, receipt); } + get currentRoomId(): string | undefined { + return this._currentRoomId; + } + + set currentRoomId(value: string | undefined) { + this._currentRoomId = value; + } + get ready() { return this.#ready; } diff --git a/packages/host/app/services/operator-mode-state-service.ts b/packages/host/app/services/operator-mode-state-service.ts index 352916789d..9423710fd6 100644 --- a/packages/host/app/services/operator-mode-state-service.ts +++ b/packages/host/app/services/operator-mode-state-service.ts @@ -82,6 +82,7 @@ export default class OperatorModeStateService extends Service { codePath: null, openDirs: new TrackedMap(), }); + @tracked private _aiAssistantOpen = false; private cachedRealmURL: URL | null = null; private openFileSubscribers: OpenFileSubscriber[] = []; @@ -99,6 +100,18 @@ export default class OperatorModeStateService extends Service { this.reset.register(this); } + get aiAssistantOpen() { + return this._aiAssistantOpen; + } + + set aiAssistantOpen(value: boolean) { + this._aiAssistantOpen = value; + } + + toggleAiAssistant = () => { + this.aiAssistantOpen = !this.aiAssistantOpen; + }; + resetState() { this.state = new TrackedObject({ stacks: new TrackedArray([]), diff --git a/packages/host/config/environment.js b/packages/host/config/environment.js index 0cb318f7ce..c9a175d8ad 100644 --- a/packages/host/config/environment.js +++ b/packages/host/config/environment.js @@ -22,8 +22,6 @@ module.exports = function (environment) { APP: { // Here you can pass flags/options to your application instance // when it is created - experimentalAIEnabled: - process.env.EXPERIMENTAL_AI_ENABLED === 'true' ? true : false, }, 'ember-cli-mirage': { enabled: false, @@ -52,7 +50,6 @@ module.exports = function (environment) { // ENV.APP.LOG_TRANSITIONS = true; // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; // ENV.APP.LOG_VIEW_LOOKUPS = true; - ENV.APP.experimentalAIEnabled = true; } if (environment === 'test') { @@ -65,7 +62,6 @@ module.exports = function (environment) { ENV.APP.rootElement = '#ember-testing'; ENV.APP.autoboot = false; - ENV.APP.experimentalAIEnabled = true; ENV.autoSaveDelayMs = 0; ENV.monacoDebounceMs = 0; ENV.monacoCursorDebounceMs = 0; diff --git a/packages/runtime-common/index.ts b/packages/runtime-common/index.ts index 34838554bf..6ae2c5ecff 100644 --- a/packages/runtime-common/index.ts +++ b/packages/runtime-common/index.ts @@ -355,6 +355,7 @@ export interface Actions { changeSizeCallback: () => Promise, ) => Promise; changeSubmode: (url: URL, submode: 'code' | 'interact') => void; + openRoom: (roomId: string) => void; } export function hasExecutableExtension(path: string): boolean { From 8cb89b0ac14fcca8930b6041e1ae73737473915e Mon Sep 17 00:00:00 2001 From: Matic Jurglic Date: Fri, 27 Dec 2024 09:52:50 +0100 Subject: [PATCH 02/16] Cleanup --- .../host/app/components/operator-mode/interact-submode.gts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/host/app/components/operator-mode/interact-submode.gts b/packages/host/app/components/operator-mode/interact-submode.gts index 1de275aefe..2e50f1cbaf 100644 --- a/packages/host/app/components/operator-mode/interact-submode.gts +++ b/packages/host/app/components/operator-mode/interact-submode.gts @@ -331,8 +331,7 @@ export default class InteractSubmode extends Component { }, openRoom: async (roomId: string): Promise => { here.operatorModeStateService.aiAssistantOpen = true; - // wait until room is loaded - this.matrixService.currentRoomId = roomId; + here.matrixService.currentRoomId = roomId; }, }; } From 75b03b30d28bbb13d3e14321587db76803ba947d Mon Sep 17 00:00:00 2001 From: Matic Jurglic Date: Fri, 27 Dec 2024 09:54:35 +0100 Subject: [PATCH 03/16] Allow selecting a room before ai panel gets opened --- packages/host/app/components/ai-assistant/panel.gts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/host/app/components/ai-assistant/panel.gts b/packages/host/app/components/ai-assistant/panel.gts index 6c71bd590e..0047181d72 100644 --- a/packages/host/app/components/ai-assistant/panel.gts +++ b/packages/host/app/components/ai-assistant/panel.gts @@ -377,6 +377,9 @@ export default class AiAssistantPanel extends Component { } private async enterRoomInitially() { + if (this.matrixService.currentRoomId) { + return; + } let persistedRoomId = window.localStorage.getItem( CurrentRoomIdPersistenceKey, ); From 0ea5b1a0b4a4f7d67d6676c3e483ae58e58e909d Mon Sep 17 00:00:00 2001 From: Matic Jurglic Date: Fri, 27 Dec 2024 10:02:45 +0100 Subject: [PATCH 04/16] Cleanup --- packages/experiments-realm/ai-app-generator.gts | 1 - packages/host/app/components/ai-assistant/panel.gts | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/experiments-realm/ai-app-generator.gts b/packages/experiments-realm/ai-app-generator.gts index 4fd4d8a746..e2ecb62de0 100644 --- a/packages/experiments-realm/ai-app-generator.gts +++ b/packages/experiments-realm/ai-app-generator.gts @@ -363,7 +363,6 @@ class DashboardTab extends GlimmerComponent<{ } let command = new CreateBoxelApp(commandContext, undefined); command.onRoomCreation = (roomId) => { - debugger; this.args.context?.actions?.openRoom(roomId); }; this.isGenerating = true; diff --git a/packages/host/app/components/ai-assistant/panel.gts b/packages/host/app/components/ai-assistant/panel.gts index 0047181d72..7e6c3c3fb6 100644 --- a/packages/host/app/components/ai-assistant/panel.gts +++ b/packages/host/app/components/ai-assistant/panel.gts @@ -362,7 +362,6 @@ export default class AiAssistantPanel extends Component { @service private declare router: RouterService; @service private declare commandService: CommandService; - // @tracked private currentRoomId: string | undefined; @tracked private isShowingPastSessions = false; @tracked private roomToRename: SessionRoomData | undefined = undefined; @tracked private roomToDelete: SessionRoomData | undefined = undefined; From 80feab742f387d77dc26dec29d58a30147e6699a Mon Sep 17 00:00:00 2001 From: Matic Jurglic Date: Fri, 27 Dec 2024 12:29:23 +0100 Subject: [PATCH 05/16] Persist to local storage when setting current room --- packages/host/app/components/ai-assistant/panel.gts | 4 ---- packages/host/app/services/matrix-service.ts | 5 +++++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/host/app/components/ai-assistant/panel.gts b/packages/host/app/components/ai-assistant/panel.gts index 7e6c3c3fb6..6e6e93ab91 100644 --- a/packages/host/app/components/ai-assistant/panel.gts +++ b/packages/host/app/components/ai-assistant/panel.gts @@ -376,9 +376,6 @@ export default class AiAssistantPanel extends Component { } private async enterRoomInitially() { - if (this.matrixService.currentRoomId) { - return; - } let persistedRoomId = window.localStorage.getItem( CurrentRoomIdPersistenceKey, ); @@ -544,7 +541,6 @@ export default class AiAssistantPanel extends Component { if (hidePastSessionsList) { this.hidePastSessions(); } - window.localStorage.setItem(CurrentRoomIdPersistenceKey, roomId); } @action private setRoomToRename(room: SessionRoomData) { diff --git a/packages/host/app/services/matrix-service.ts b/packages/host/app/services/matrix-service.ts index e8956d2f52..64a969b08e 100644 --- a/packages/host/app/services/matrix-service.ts +++ b/packages/host/app/services/matrix-service.ts @@ -155,6 +155,11 @@ export default class MatrixService extends Service { set currentRoomId(value: string | undefined) { this._currentRoomId = value; + if (value) { + window.localStorage.setItem(CurrentRoomIdPersistenceKey, value); + } else { + window.localStorage.removeItem(CurrentRoomIdPersistenceKey); + } } get ready() { From 6379c160138d639a135ab4f08099fd556dc114fe Mon Sep 17 00:00:00 2001 From: Matic Jurglic Date: Fri, 27 Dec 2024 13:16:24 +0100 Subject: [PATCH 06/16] Fix test --- packages/host/tests/acceptance/commands-test.gts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/host/tests/acceptance/commands-test.gts b/packages/host/tests/acceptance/commands-test.gts index 0c946a85fa..0c6daaf06f 100644 --- a/packages/host/tests/acceptance/commands-test.gts +++ b/packages/host/tests/acceptance/commands-test.gts @@ -602,9 +602,10 @@ module('Acceptance | Commands tests', function (hooks) { event_id: '__EVENT_ID__', }, }); - await delay(500); + assert.dom('[data-test-submode-switcher=interact]').exists(); await click('[data-test-open-ai-assistant]'); + await waitFor(`[data-room-settled]`); assert .dom( '[data-test-message-idx="0"][data-test-boxel-message-from="testuser"]', @@ -622,7 +623,7 @@ module('Acceptance | Commands tests', function (hooks) { assert.dom('[data-test-card-url-bar-input]').hasValue(`${testCard}.json`); await click('[data-test-submode-switcher] button'); await click('[data-test-boxel-menu-item-text="Interact"]'); - await click('[data-test-open-ai-assistant]'); + assert .dom( '[data-test-message-idx="0"][data-test-boxel-message-from="testuser"]', From f97eaf54a7c65bf0f5ca8c27f1509b97d8c7c479 Mon Sep 17 00:00:00 2001 From: Matic Jurglic Date: Mon, 30 Dec 2024 11:33:53 +0100 Subject: [PATCH 07/16] Add a command for opening an ai assistant room --- packages/base/command.gts | 4 +++ .../create-boxel-app-command.ts | 3 -- .../create-product-requirements-command.ts | 12 ++++--- .../experiments-realm/ai-app-generator.gts | 4 +-- packages/host/app/commands/index.ts | 5 +++ .../app/commands/open-ai-assistant-room.ts | 29 ++++++++++++++++ .../operator-mode/interact-submode.gts | 4 --- .../host/tests/acceptance/commands-test.gts | 34 +++++++++++++++++++ .../realm-indexing-and-querying-test.gts | 1 + packages/runtime-common/index.ts | 1 - 10 files changed, 81 insertions(+), 16 deletions(-) create mode 100644 packages/host/app/commands/open-ai-assistant-room.ts diff --git a/packages/base/command.gts b/packages/base/command.gts index cf821942fe..4972816c9e 100644 --- a/packages/base/command.gts +++ b/packages/base/command.gts @@ -113,3 +113,7 @@ export class SendAiAssistantMessageInput extends CardDef { export class SendAiAssistantMessageResult extends CardDef { @field eventId = contains(StringField); } + +export class OpenAiAssistantRoomInput extends CardDef { + @field roomId = contains(StringField); +} diff --git a/packages/experiments-realm/AiAppGenerator/create-boxel-app-command.ts b/packages/experiments-realm/AiAppGenerator/create-boxel-app-command.ts index 56f1be60a1..dfeb127e75 100644 --- a/packages/experiments-realm/AiAppGenerator/create-boxel-app-command.ts +++ b/packages/experiments-realm/AiAppGenerator/create-boxel-app-command.ts @@ -16,7 +16,6 @@ export default class CreateBoxelApp extends Command< CardDef > { inputType = CreateProductRequirementsInput; - onRoomCreation: ((roomId: string) => void) | null = null; protected async run(input: CreateProductRequirementsInput): Promise { let createPRDCommand = new CreateProductRequirementsInstance( @@ -24,8 +23,6 @@ export default class CreateBoxelApp extends Command< undefined, ); - createPRDCommand.onRoomCreation = this.onRoomCreation; - let { productRequirements: prdCard, roomId } = await createPRDCommand.execute(input); diff --git a/packages/experiments-realm/AiAppGenerator/create-product-requirements-command.ts b/packages/experiments-realm/AiAppGenerator/create-product-requirements-command.ts index f2c4d17103..47f98cb042 100644 --- a/packages/experiments-realm/AiAppGenerator/create-product-requirements-command.ts +++ b/packages/experiments-realm/AiAppGenerator/create-product-requirements-command.ts @@ -14,6 +14,7 @@ import ReloadCardCommand from '@cardstack/boxel-host/commands/reload-card'; import CreateAIAssistantRoomCommand from '@cardstack/boxel-host/commands/create-ai-assistant-room'; import AddSkillsToRoomCommand from '@cardstack/boxel-host/commands/add-skills-to-room'; import SendAiAssistantMessageCommand from '@cardstack/boxel-host/commands/send-ai-assistant-message'; +import OpenAiAssistantRoomCommand from '@cardstack/boxel-host/commands/open-ai-assistant-room'; export class CreateProductRequirementsInput extends CardDef { @field targetAudience = contains(StringField); @@ -33,8 +34,6 @@ export default class CreateProductRequirementsInstance extends Command< > { inputType = CreateProductRequirementsInput; - onRoomCreation: ((roomId: string) => void) | null = null; - get skillCard() { return new SkillCard({ id: 'prd-helper-skill', @@ -80,9 +79,12 @@ export default class CreateProductRequirementsInstance extends Command< name: 'Product Requirements Doc Creation', }); - if (this.onRoomCreation) { - this.onRoomCreation(roomId); - } + let openAiPanelRoomCommand = new OpenAiAssistantRoomCommand( + this.commandContext, + ); + await openAiPanelRoomCommand.execute({ + roomId, + }); let addSkillsToRoomCommand = new AddSkillsToRoomCommand( this.commandContext, diff --git a/packages/experiments-realm/ai-app-generator.gts b/packages/experiments-realm/ai-app-generator.gts index e2ecb62de0..a436844bc1 100644 --- a/packages/experiments-realm/ai-app-generator.gts +++ b/packages/experiments-realm/ai-app-generator.gts @@ -362,9 +362,7 @@ class DashboardTab extends GlimmerComponent<{ throw new Error('Missing commandContext'); } let command = new CreateBoxelApp(commandContext, undefined); - command.onRoomCreation = (roomId) => { - this.args.context?.actions?.openRoom(roomId); - }; + this.isGenerating = true; try { await command.execute( diff --git a/packages/host/app/commands/index.ts b/packages/host/app/commands/index.ts index 4f19fea0ab..b06b3f8b69 100644 --- a/packages/host/app/commands/index.ts +++ b/packages/host/app/commands/index.ts @@ -2,6 +2,7 @@ import { VirtualNetwork } from '@cardstack/runtime-common'; import * as AddSkillsToRoomCommandModule from './add-skills-to-room'; import * as CreateAIAssistantRoomCommandModule from './create-ai-assistant-room'; +import * as OpenAiAssistantRoomCommandModule from './open-ai-assistant-room'; import * as PatchCardCommandModule from './patch-card'; import * as ReloadCardCommandModule from './reload-card'; import * as SaveCardCommandModule from './save-card'; @@ -32,6 +33,10 @@ export function shimHostCommands(virtualNetwork: VirtualNetwork) { '@cardstack/boxel-host/commands/save-card', SaveCardCommandModule, ); + virtualNetwork.shimModule( + '@cardstack/boxel-host/commands/open-ai-assistant-room', + OpenAiAssistantRoomCommandModule, + ); virtualNetwork.shimModule( '@cardstack/boxel-host/commands/send-ai-assistant-message', SendAiAssistantMessageModule, diff --git a/packages/host/app/commands/open-ai-assistant-room.ts b/packages/host/app/commands/open-ai-assistant-room.ts new file mode 100644 index 0000000000..bfcbfbfb2f --- /dev/null +++ b/packages/host/app/commands/open-ai-assistant-room.ts @@ -0,0 +1,29 @@ +import { service } from '@ember/service'; + +import type * as BaseCommandModule from 'https://cardstack.com/base/command'; + +import HostBaseCommand from '../lib/host-base-command'; + +import MatrixService from '../services/matrix-service'; +import OperatorModeStateService from '../services/operator-mode-state-service'; + +export default class OpenAAssistantRoomCommand extends HostBaseCommand< + BaseCommandModule.OpenAiAssistantRoomInput, + undefined +> { + @service private declare operatorModeStateService: OperatorModeStateService; + @service private declare matrixService: MatrixService; + + async getInputType() { + let commandModule = await this.loadCommandModule(); + const { OpenAiAssistantRoomInput } = commandModule; + return OpenAiAssistantRoomInput; + } + + protected async run( + input: BaseCommandModule.OpenAiAssistantRoomInput, + ): Promise { + this.operatorModeStateService.aiAssistantOpen = true; + this.matrixService.currentRoomId = input.roomId; + } +} diff --git a/packages/host/app/components/operator-mode/interact-submode.gts b/packages/host/app/components/operator-mode/interact-submode.gts index 2e50f1cbaf..d727984bf6 100644 --- a/packages/host/app/components/operator-mode/interact-submode.gts +++ b/packages/host/app/components/operator-mode/interact-submode.gts @@ -329,10 +329,6 @@ export default class InteractSubmode extends Component { here.operatorModeStateService.updateCodePath(url); here.operatorModeStateService.updateSubmode(submode); }, - openRoom: async (roomId: string): Promise => { - here.operatorModeStateService.aiAssistantOpen = true; - here.matrixService.currentRoomId = roomId; - }, }; } stackBackgroundsState = stackBackgroundsResource(this); diff --git a/packages/host/tests/acceptance/commands-test.gts b/packages/host/tests/acceptance/commands-test.gts index 0c6daaf06f..7592ff9420 100644 --- a/packages/host/tests/acceptance/commands-test.gts +++ b/packages/host/tests/acceptance/commands-test.gts @@ -22,6 +22,7 @@ import { } from '@cardstack/runtime-common/matrix-constants'; import CreateAIAssistantRoomCommand from '@cardstack/host/commands/create-ai-assistant-room'; +import OpenAAssistantRoomCommand from '@cardstack/host/commands/open-ai-assistant-room'; import PatchCardCommand from '@cardstack/host/commands/patch-card'; import SaveCardCommand from '@cardstack/host/commands/save-card'; import SendAiAssistantMessageCommand from '@cardstack/host/commands/send-ai-assistant-message'; @@ -268,6 +269,19 @@ module('Acceptance | Commands tests', function (hooks) { }); await sleepCommand.execute(new ScheduleMeetingInput()); }; + runOpenAiAssistantRoomCommand = async () => { + let commandContext = this.args.context?.commandContext; + if (!commandContext) { + console.error('No command context found'); + return; + } + let openAiAssistantRoomCommand = new OpenAAssistantRoomCommand( + commandContext, + ); + await openAiAssistantRoomCommand.execute({ + roomId: 'mock_room_1', + }); + }; }; } @@ -335,6 +353,22 @@ module('Acceptance | Commands tests', function (hooks) { }); }); + test('OpenAAssistantRoomCommand opens the AI assistant room', async function (assert) { + await visitOperatorMode({ + stacks: [[{ id: `${testRealmURL}Person/hassan`, format: 'isolated' }]], + }); + + await click('[data-test-schedule-meeting-button]'); + await click('[data-test-open-ai-assistant-room-button]'); + + await waitFor('[data-room-settled]'); + await waitFor('[data-test-room-name="AI Assistant Room"]'); + + assert + .dom('[data-test-ai-message-content]') + .includesText('Change the topic of the meeting to "Meeting with Hassan"'); + }); + test('a command sent via SendAiAssistantMessageCommand with autoExecute flag is automatically executed by the bot, panel closed', async function (assert) { await visitOperatorMode({ stacks: [ diff --git a/packages/host/tests/integration/realm-indexing-and-querying-test.gts b/packages/host/tests/integration/realm-indexing-and-querying-test.gts index 7cba0ce45f..46211e9634 100644 --- a/packages/host/tests/integration/realm-indexing-and-querying-test.gts +++ b/packages/host/tests/integration/realm-indexing-and-querying-test.gts @@ -3603,6 +3603,7 @@ module(`Integration | realm indexing and querying`, function (hooks) { 'https://cardstack.com/base/text-input-validator', 'https://cardstack.com/base/watched-array', 'https://packages/@cardstack/boxel-host/commands/create-ai-assistant-room', + 'https://packages/@cardstack/boxel-host/commands/open-ai-assistant-room', 'https://packages/@cardstack/boxel-host/commands/send-ai-assistant-message', 'https://packages/@cardstack/boxel-host/commands/switch-submode', 'https://packages/@cardstack/boxel-ui/components', diff --git a/packages/runtime-common/index.ts b/packages/runtime-common/index.ts index 6ae2c5ecff..34838554bf 100644 --- a/packages/runtime-common/index.ts +++ b/packages/runtime-common/index.ts @@ -355,7 +355,6 @@ export interface Actions { changeSizeCallback: () => Promise, ) => Promise; changeSubmode: (url: URL, submode: 'code' | 'interact') => void; - openRoom: (roomId: string) => void; } export function hasExecutableExtension(path: string): boolean { From 834e2408e46ec9a270f48df91b0bf8555c55f4ba Mon Sep 17 00:00:00 2001 From: Matic Jurglic Date: Mon, 30 Dec 2024 11:54:50 +0100 Subject: [PATCH 08/16] Fix test --- .../host/tests/integration/realm-indexing-and-querying-test.gts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/host/tests/integration/realm-indexing-and-querying-test.gts b/packages/host/tests/integration/realm-indexing-and-querying-test.gts index 46211e9634..7cba0ce45f 100644 --- a/packages/host/tests/integration/realm-indexing-and-querying-test.gts +++ b/packages/host/tests/integration/realm-indexing-and-querying-test.gts @@ -3603,7 +3603,6 @@ module(`Integration | realm indexing and querying`, function (hooks) { 'https://cardstack.com/base/text-input-validator', 'https://cardstack.com/base/watched-array', 'https://packages/@cardstack/boxel-host/commands/create-ai-assistant-room', - 'https://packages/@cardstack/boxel-host/commands/open-ai-assistant-room', 'https://packages/@cardstack/boxel-host/commands/send-ai-assistant-message', 'https://packages/@cardstack/boxel-host/commands/switch-submode', 'https://packages/@cardstack/boxel-ui/components', From cd308a360798b0c0ab1740d28a6adc29ecef398a Mon Sep 17 00:00:00 2001 From: Matic Jurglic Date: Mon, 30 Dec 2024 12:12:48 +0100 Subject: [PATCH 09/16] Rename --- .../AiAppGenerator/create-product-requirements-command.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/experiments-realm/AiAppGenerator/create-product-requirements-command.ts b/packages/experiments-realm/AiAppGenerator/create-product-requirements-command.ts index 47f98cb042..5d4471ae7e 100644 --- a/packages/experiments-realm/AiAppGenerator/create-product-requirements-command.ts +++ b/packages/experiments-realm/AiAppGenerator/create-product-requirements-command.ts @@ -79,10 +79,10 @@ export default class CreateProductRequirementsInstance extends Command< name: 'Product Requirements Doc Creation', }); - let openAiPanelRoomCommand = new OpenAiAssistantRoomCommand( + let openAiAssistantRoomCommand = new OpenAiAssistantRoomCommand( this.commandContext, ); - await openAiPanelRoomCommand.execute({ + await openAiAssistantRoomCommand.execute({ roomId, }); From 4c7ba43830cc446c0db364c33ffe45b169f2f68c Mon Sep 17 00:00:00 2001 From: Hassan Abdel-Rahman Date: Mon, 30 Dec 2024 12:17:30 -0500 Subject: [PATCH 10/16] Use CardResource to create card instances on the host --- packages/host/app/commands/show-card.ts | 2 +- .../card-preview-panel/index.gts | 4 +- .../operator-mode/interact-submode.gts | 31 +++----- .../app/components/operator-mode/overlays.gts | 17 +---- packages/host/app/lib/stack-item.ts | 58 +++++---------- packages/host/app/resources/card-resource.ts | 73 ++++++++++++++----- packages/host/app/resources/search.ts | 9 +++ .../services/operator-mode-state-service.ts | 13 ++-- .../acceptance/interact-submode-test.gts | 62 ++++++++++++++++ 9 files changed, 164 insertions(+), 105 deletions(-) diff --git a/packages/host/app/commands/show-card.ts b/packages/host/app/commands/show-card.ts index 1143472148..375ffe8d3a 100644 --- a/packages/host/app/commands/show-card.ts +++ b/packages/host/app/commands/show-card.ts @@ -32,7 +32,7 @@ export default class ShowCardCommand extends HostBaseCommand< 1, ); let newStackItem = await this.operatorModeStateService.createStackItem( - input.cardToShow, + new URL(input.cardToShow.id), newStackIndex, ); this.operatorModeStateService.addItemToStack(newStackItem); diff --git a/packages/host/app/components/operator-mode/card-preview-panel/index.gts b/packages/host/app/components/operator-mode/card-preview-panel/index.gts index 748f586bb7..6ce4257ea7 100644 --- a/packages/host/app/components/operator-mode/card-preview-panel/index.gts +++ b/packages/host/app/components/operator-mode/card-preview-panel/index.gts @@ -93,7 +93,9 @@ export default class CardPreviewPanel extends Component { } openInInteractMode = task(async () => { - await this.operatorModeStateService.openCardInInteractMode(this.args.card); + await this.operatorModeStateService.openCardInInteractMode( + new URL(this.args.card.id), + ); });