From 4b9bcfa2f3172dcab223a0c61aaee6a1cbcce1f9 Mon Sep 17 00:00:00 2001 From: Marcel Menk Date: Sun, 11 Oct 2026 18:51:40 +0200 Subject: [PATCH] fix(agenstra): output chat message timestamps with date --- .../src/lib/chat/chat.component.html | 73 +++++++---- .../src/lib/chat/chat.component.scss | 19 +-- .../src/lib/chat/chat.component.ts | 116 ++++++++++-------- 3 files changed, 114 insertions(+), 94 deletions(-) diff --git a/libs/domains/agenstra/frontend/feature-agent-console/src/lib/chat/chat.component.html b/libs/domains/agenstra/frontend/feature-agent-console/src/lib/chat/chat.component.html index a806099d7..fdac15445 100644 --- a/libs/domains/agenstra/frontend/feature-agent-console/src/lib/chat/chat.component.html +++ b/libs/domains/agenstra/frontend/feature-agent-console/src/lib/chat/chat.component.html @@ -1049,7 +1049,7 @@
} } @else if (item.kind === 'ticketAutomationRun') {
-
+
{{ item.payload.ticket.title }} @@ -1089,7 +1089,7 @@
} @else if (item.kind === 'chatPlan') {
-
+
}
-
- @if (selectedCommand(); as markedCommand) { -
- - {{ markedCommand }} - - -
- } - @if (slashCommandMenuOpen()) { +
+ @if (isSlashCommandMenuOpenFor('chat')) { } + @if (isSlashCommandMenuOpenFor('planRefine')) { + + }
diff --git a/libs/domains/agenstra/frontend/feature-agent-console/src/lib/chat/chat.component.scss b/libs/domains/agenstra/frontend/feature-agent-console/src/lib/chat/chat.component.scss index fc1a51509..5be372b36 100644 --- a/libs/domains/agenstra/frontend/feature-agent-console/src/lib/chat/chat.component.scss +++ b/libs/domains/agenstra/frontend/feature-agent-console/src/lib/chat/chat.component.scss @@ -169,20 +169,6 @@ position: relative; } -.chat-command-mark { - display: flex; - align-items: center; - padding: 0.5rem 0.75rem 0; -} - -.chat-command-mark__badge { - max-width: 100%; -} - -.chat-command-mark__clear { - font-size: 0.65rem; -} - .chat-command-typeahead { position: absolute; left: 0; @@ -218,10 +204,6 @@ padding-left: 0; } -.chat-textarea-container--command-marked { - padding-top: 0; -} - // Textarea body .chat-textarea { min-width: 0; // Allow input to shrink @@ -242,6 +224,7 @@ } .chat-plan-refine-composer { + position: relative; display: flex; flex-direction: column; border: 1px solid var(--bs-border-color); diff --git a/libs/domains/agenstra/frontend/feature-agent-console/src/lib/chat/chat.component.ts b/libs/domains/agenstra/frontend/feature-agent-console/src/lib/chat/chat.component.ts index 9a1191231..79c6266a2 100644 --- a/libs/domains/agenstra/frontend/feature-agent-console/src/lib/chat/chat.component.ts +++ b/libs/domains/agenstra/frontend/feature-agent-console/src/lib/chat/chat.component.ts @@ -171,8 +171,8 @@ import { } from './agent-chat-response-markdown'; import { accumulateStreamingTurnFromEvents } from './agent-chat-streaming-aggregate'; import { AgentChatTodosExpandCoordinator } from './agent-chat-todos-expand.coordinator'; -import { sanitizeAndTrustMarkdownHtml } from './chat-markdown-html'; import { mergeTicketAutomationChatCardPayload } from './chat-automation-card-merge'; +import { sanitizeAndTrustMarkdownHtml } from './chat-markdown-html'; import { chatPlanStateBadgeColor as chatPlanStateBadgeColorFn, chatPlanStateLabel as chatPlanStateLabelFn, @@ -208,6 +208,8 @@ type ChatMessageWithFilter = { } | null; }; +type SlashCommandInputTarget = 'chat' | 'planRefine'; + /** Segment colors of the stacked workspace provisioning bar (cycled per environment). */ const WORKSPACE_PROGRESS_SEGMENT_VARIANTS: FpcProgressVariant[] = [ 'primary', @@ -858,20 +860,20 @@ export class AgentConsoleChatComponent implements OnInit, AfterViewChecked, OnDe /** Last reply error keyed by question id. */ readonly questionReplyErrors = signal>(new Map()); selectedChatModel = signal(null); - /** Marked slash command (OpenCode-style); prepended on send. */ - selectedCommand = signal(null); /** Dismiss typeahead without clearing the `/` fragment (Escape). */ private readonly slashMenuDismissed = signal(false); + private readonly slashCommandTarget = signal('chat'); slashHighlightIndex = signal(0); selectedAgentId = signal(null); /** Token after leading `/` while composing a slash command (`null` = menu inactive). */ readonly slashCommandQuery = computed(() => { - if (this.selectedCommand() || this.slashMenuDismissed()) { + if (this.slashMenuDismissed()) { return null; } - const match = this.chatMessage().match(/^\/([^\s\n]*)$/); + const value = this.slashCommandTarget() === 'planRefine' ? this.planRefineMessage() : this.chatMessage(); + const match = value.match(/^\/([^\s\n]*)$/); return match ? match[1] : null; }); @@ -3037,13 +3039,7 @@ export class AgentConsoleChatComponent implements OnInit, AfterViewChecked, OnDe } onSendMessage(): void { - let message = this.chatMessage().trim(); - // Append selected command to message if one is selected - const selectedCmd = this.selectedCommand(); - - if (selectedCmd) { - message = message ? `${selectedCmd}\n${message}` : selectedCmd; - } + const message = this.chatMessage().trim(); if (!message) { return; @@ -3071,8 +3067,6 @@ export class AgentConsoleChatComponent implements OnInit, AfterViewChecked, OnDe this.lastUserMessageTimestamp.set(Date.now()); this.chatMessage.set(''); - // Clear selected command after sending - this.selectedCommand.set(null); // Trigger scroll after sending message this.stickToBottom.set(true); this.shouldScrollToBottom = true; @@ -3083,7 +3077,7 @@ export class AgentConsoleChatComponent implements OnInit, AfterViewChecked, OnDe } onEnhanceMessage(): void { - this.enhanceText(this.chatMessage(), 'chat', true); + this.enhanceText(this.chatMessage(), 'chat'); } clearPlanRefineEnhanceError(): void { @@ -3094,13 +3088,8 @@ export class AgentConsoleChatComponent implements OnInit, AfterViewChecked, OnDe this.enhanceText(this.planRefineMessage(), 'planRefine'); } - private enhanceText(rawMessage: string, target: 'chat' | 'planRefine', includeSlashCommand = false): void { - let message = rawMessage.trim(); - const selectedCmd = this.selectedCommand(); - - if (includeSlashCommand && selectedCmd) { - message = message ? `${selectedCmd}\n${message}` : selectedCmd; - } + private enhanceText(rawMessage: string, target: 'chat' | 'planRefine'): void { + const message = rawMessage.trim(); if (!message) { return; @@ -3128,12 +3117,7 @@ export class AgentConsoleChatComponent implements OnInit, AfterViewChecked, OnDe } onPlanMessage(): void { - let message = this.chatMessage().trim(); - const selectedCmd = this.selectedCommand(); - - if (selectedCmd) { - message = message ? `${selectedCmd}\n${message}` : selectedCmd; - } + const message = this.chatMessage().trim(); if (!message) { return; @@ -3248,21 +3232,38 @@ export class AgentConsoleChatComponent implements OnInit, AfterViewChecked, OnDe } onChatInputKeydown(event: KeyboardEvent): void { - if (this.slashCommandMenuOpen()) { + if (this.handleSlashCommandKeydown(event, 'chat')) { + return; + } + + if (event.key === 'Enter' && !event.shiftKey && !event.ctrlKey && !event.metaKey && !event.altKey) { + event.preventDefault(); + this.onSendMessage(); + } + } + + onPlanRefineInputKeydown(event: KeyboardEvent): void { + this.handleSlashCommandKeydown(event, 'planRefine'); + } + + private handleSlashCommandKeydown(event: KeyboardEvent, target: SlashCommandInputTarget): boolean { + this.slashCommandTarget.set(target); + + if (this.isSlashCommandMenuOpenFor(target)) { const filtered = this.filteredSlashCommands(); if (event.key === 'ArrowDown') { event.preventDefault(); this.slashHighlightIndex.update((index) => Math.min(index + 1, filtered.length - 1)); - return; + return true; } if (event.key === 'ArrowUp') { event.preventDefault(); this.slashHighlightIndex.update((index) => Math.max(index - 1, 0)); - return; + return true; } if (event.key === 'Enter' || event.key === 'Tab') { @@ -3270,45 +3271,60 @@ export class AgentConsoleChatComponent implements OnInit, AfterViewChecked, OnDe const command = filtered[this.slashHighlightIndex()] ?? filtered[0]; if (command) { - this.pickSlashCommand(command); + this.pickSlashCommand(command, undefined, target); } - return; + return true; } if (event.key === 'Escape') { event.preventDefault(); this.slashMenuDismissed.set(true); - return; + return true; } } - if (event.key === 'Enter' && !event.shiftKey && !event.ctrlKey && !event.metaKey && !event.altKey) { - event.preventDefault(); - this.onSendMessage(); - } + return false; } onChatMessageInput(value: string): void { this.chatMessage.set(value); + this.slashCommandTarget.set('chat'); + this.slashMenuDismissed.set(false); + } + + onChatMessageFocus(): void { + this.slashCommandTarget.set('chat'); + } + + onPlanRefineMessageInput(value: string): void { + this.planRefineMessage.set(value); + this.slashCommandTarget.set('planRefine'); this.slashMenuDismissed.set(false); } - pickSlashCommand(command: AgentSlashCommand | string, event?: Event): void { + onPlanRefineMessageFocus(): void { + this.slashCommandTarget.set('planRefine'); + } + + isSlashCommandMenuOpenFor(target: SlashCommandInputTarget): boolean { + return this.slashCommandTarget() === target && this.slashCommandMenuOpen(); + } + + pickSlashCommand(command: AgentSlashCommand | string, event?: Event, target = this.slashCommandTarget()): void { event?.preventDefault(); const name = typeof command === 'string' ? command : command.name; - this.selectedCommand.set(name); - this.chatMessage.set( - this.chatMessage() - .replace(/^\/[^\s\n]*/, '') - .replace(/^\n/, ''), - ); - this.slashMenuDismissed.set(true); - } + const insertCommand = (value: string) => value.replace(/^\/[^\s\n]*/, `${name} `); + + if (target === 'planRefine') { + this.planRefineMessage.update(insertCommand); + } else { + this.chatMessage.update(insertCommand); + } - clearSelectedCommand(): void { - this.selectedCommand.set(null); + this.slashCommandTarget.set(target); + this.slashMenuDismissed.set(true); } onChatModelChange(value: string): void { @@ -5184,7 +5200,7 @@ export class AgentConsoleChatComponent implements OnInit, AfterViewChecked, OnDe } formatTimestamp(timestamp: number): string { - return new Date(timestamp).toLocaleTimeString(); + return new Date(timestamp).toLocaleString(); } openTicketAutomationRunFromChat(payload: TicketAutomationRunChatEventPayload): void {