Skip to content

Commit

Permalink
WIP Updating command results to a) always be a CardDef or undefined, …
Browse files Browse the repository at this point in the history
…b) added as a card to the room, and c) references by eventID in the reaction event created once a command runs

aibot will need to be updated to read from this
Message model and room-message will need to updated to display the result card
  • Loading branch information
lukemelia committed Dec 17, 2024
1 parent b7cfbfa commit 7e85f7c
Show file tree
Hide file tree
Showing 11 changed files with 318 additions and 219 deletions.
16 changes: 16 additions & 0 deletions packages/base/command.gts
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,19 @@ export class SendAiAssistantMessageInput extends CardDef {
export class SendAiAssistantMessageResult extends CardDef {
@field eventId = contains(StringField);
}

export class GetBoxelUIStateResult extends CardDef {
@field submode = contains(StringField);
//TODO expand this to include more of the UI state:
// - open cards
// - current room ID
}

export class SearchCardsResult extends CardDef {
@field cardDocs = containsMany(JsonField);
}

export class LegacyGenerateAppModuleResult extends CardDef {
@field moduleId = contains(StringField);
@field source = contains(StringField);
}
34 changes: 7 additions & 27 deletions packages/base/matrix-event.gts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,13 @@ export interface ReactionEventContent {
};
}

export type CommandReactionEventContent = ReactionEventContent & {
msgtype: 'org.boxel.command_result';
data: {
card_event_id: string | null;
};
};

export interface CardMessageEvent extends BaseMatrixEvent {
type: 'm.room.message';
content: CardMessageContent | CardFragmentContent;
Expand Down Expand Up @@ -227,39 +234,12 @@ export interface SkillsConfigEvent extends RoomStateEvent {
};
}

export interface CommandResultEvent extends BaseMatrixEvent {
type: 'm.room.message';
content: CommandResultContent;
unsigned: {
age: number;
transaction_id: string;
prev_content?: any;
prev_sender?: string;
};
}

export interface CommandResultContent {
'm.relates_to'?: {
rel_type: 'm.annotation';
key: string;
event_id: string;
'm.in_reply_to'?: {
event_id: string;
};
};
formatted_body: string;
body: string;
msgtype: 'org.boxel.commandResult';
result: any;
}

export type MatrixEvent =
| RoomCreateEvent
| RoomJoinRules
| RoomPowerLevels
| MessageEvent
| CommandEvent
| CommandResultEvent
| ReactionEvent
| CardMessageEvent
| RoomNameEvent
Expand Down
27 changes: 27 additions & 0 deletions packages/host/app/commands/get-boxel-ui-state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { inject as service } from '@ember/service';

import { GetBoxelUIStateResult } from 'https://cardstack.com/base/command';

import HostBaseCommand from '../lib/host-base-command';

import type OperatorModeStateService from '../services/operator-mode-state-service';

export default class GetBoxelUIStateCommand extends HostBaseCommand<
undefined,
GetBoxelUIStateResult
> {
@service declare operatorModeStateService: OperatorModeStateService;
static displayName = 'GetBoxelUIStateCommand';
description =
'Get information about the current state of the Boxel UI, including the current submode, what cards are open, and what room, if any, the AI assistant is showing.';
async getInputType() {
return undefined;
}
protected async run() {
let commandModule = await this.loadCommandModule();
const { GetBoxelUIStateResult } = commandModule;
return new GetBoxelUIStateResult({
submode: this.operatorModeStateService.state.submode,
});
}
}
40 changes: 10 additions & 30 deletions packages/host/app/lib/matrix-classes/message-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import type {
CardMessageContent,
CardMessageEvent,
CommandEvent,
CommandResultEvent,
CommandReactionEventContent,
MatrixEvent as DiscreteMatrixEvent,
MessageEvent,
ReactionEvent,
Expand All @@ -30,11 +30,7 @@ const ErrorMessage: Record<string, string> = {

export default class MessageBuilder {
constructor(
private event:
| MessageEvent
| CommandEvent
| CardMessageEvent
| CommandResultEvent,
private event: MessageEvent | CommandEvent | CardMessageEvent,
owner: Owner,
private builderContext: {
effectiveEventId: string;
Expand All @@ -60,7 +56,6 @@ export default class MessageBuilder {
transactionId: this.event.unsigned?.transaction_id || null,
attachedCardIds: null,
command: null,
commandResult: null,
status: this.event.status,
eventId: this.builderContext.effectiveEventId,
index: this.builderContext.index,
Expand Down Expand Up @@ -126,7 +121,6 @@ export default class MessageBuilder {
) {
messageArgs.formattedMessage = this.formattedMessageForCommand;
messageArgs.command = await this.buildMessageCommand();
messageArgs.commandResult = await this.buildCommandResultCard();
messageArgs.isStreamingFinished = true;
}
return messageArgs;
Expand All @@ -139,45 +133,31 @@ export default class MessageBuilder {
let r = e.content['m.relates_to'];
return (
e.type === 'm.reaction' &&
e.content.msgtype === 'org.boxel.command_result' &&
r?.rel_type === 'm.annotation' &&
(r?.event_id === event.content.data.eventId ||
r?.event_id === event.event_id ||
r?.event_id === this.builderContext.effectiveEventId)
);
}) as ReactionEvent | undefined;
let status: CommandStatus = 'ready';
if (annotation?.content['m.relates_to'].key === 'applied') {
let reactionContent = annotation?.content as
| CommandReactionEventContent
| undefined;
if (reactionContent && reactionContent['m.relates_to'].key === 'applied') {
status = 'applied';
}
let commandResultCardId: string | undefined =
reactionContent?.data.card_event_id ?? undefined;
let messageCommand = new MessageCommand(
command.id,
command.name,
command.arguments,
this.builderContext.effectiveEventId,
status,
commandResultCardId,
getOwner(this)!,
);
return messageCommand;
}

private async buildCommandResultCard() {
let event = this.event as CommandEvent;
let commandResultEvent = this.builderContext.events.find(
(e) =>
e.type === 'm.room.message' &&
e.content.msgtype === 'org.boxel.commandResult' &&
e.content['m.relates_to']?.rel_type === 'm.annotation' &&
e.content['m.relates_to'].event_id === event.content.data.eventId,
) as CommandResultEvent;
let r = commandResultEvent?.content?.result
? await this.commandService.createCommandResultArgs(
event,
commandResultEvent,
)
: undefined;
let commandResult = r
? await this.commandService.createCommandResult(r)
: undefined;
return commandResult;
}
}
1 change: 1 addition & 0 deletions packages/host/app/lib/matrix-classes/message-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export default class MessageCommand {
public payload: any, //arguments of toolCall. Its not called arguments due to lint
public eventId: string,
private commandStatus: CommandStatus,
public commandResultCardId: string | undefined,
owner: Owner,
) {
setOwner(this, owner);
Expand Down
2 changes: 2 additions & 0 deletions packages/host/app/lib/matrix-classes/room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export default class Room {
@tracked private _events: DiscreteMatrixEvent[] = [];
@tracked private _roomState: MatrixSDK.RoomState | undefined;

constructor(public readonly roomId: string) {}

readonly mutex = new Mutex();

get events() {
Expand Down
6 changes: 3 additions & 3 deletions packages/host/app/resources/room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,8 +252,8 @@ export class RoomResource extends Resource<Args> {
let effectiveEventId = event.event_id;
let update = false;
if (event.content['m.relates_to']?.rel_type == 'm.annotation') {
// we have to trigger a message field update if there is a reaction event so apply button state reliably updates
// otherwise, the message field (may) still but it occurs only accidentally because of a ..thinking event
// ensure that we update a message when we see a reaction event for it, since we merge data from the reaction event
// into the message state (i.e. apply button, command result)
update = true;
} else if (event.content['m.relates_to']?.rel_type === 'm.replace') {
effectiveEventId = event.content['m.relates_to'].event_id;
Expand Down Expand Up @@ -293,7 +293,7 @@ export class RoomResource extends Resource<Args> {
return;
}
if (event.content.msgtype === 'org.boxel.commandResult') {
//don't display command result in the room as a message
// Legacy data
return;
}

Expand Down
Loading

0 comments on commit 7e85f7c

Please sign in to comment.