Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/host/app/components/ai-assistant/button.gts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const AiAssistantButton: TemplateOnlyComponent<Signature> = <template>
}
.ai-assistant-button:hover {
cursor: pointer;
border-color: rgba(255, 255, 255, 1);
}

.ai-assistant-button.is-active {
Expand Down
9 changes: 9 additions & 0 deletions packages/host/app/components/ai-assistant/llm-select.gts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,15 @@ export default class LLMSelect extends Component<Signature> {
scroll-timeline: --pill-menu-content-scroll-timeline;
}

/* LLMSelect renders its own footer-like row inside .llm-list rather
than forwarding a :footer block to PillMenu, so PillMenu never marks
itself `has-footer` and its default bottom scroll-shadow (meant to be
swapped out when a real footer exists) is left dangling over the
list content instead of framing the true bottom edge. */
:deep(.menu-content::after) {
display: none;
}

.llm-option {
background: var(--boxel-light);
border-radius: var(--boxel-border-radius);
Expand Down
90 changes: 75 additions & 15 deletions packages/host/app/components/ai-assistant/past-session-item.gts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { tracked } from '@glimmer/tracking';
import ExternalLink from '@cardstack/boxel-icons/external-link';

import { format as formatDate, isSameDay, isSameYear } from 'date-fns';
import { modifier } from 'ember-modifier';

import {
BoxelDropdown,
Expand Down Expand Up @@ -45,13 +46,51 @@ interface Signature {

export default class PastSessionItem extends Component<Signature> {
@tracked private preventMenuClose = false;
private closeDropdownAction: (() => void) | null = null;
private closeDropdownTimer: ReturnType<typeof setTimeout> | null = null;
private triggerElement: HTMLElement | null = null;

private registerCloseAction = modifier(
(_element: HTMLElement, [closeFn]: [() => void]) => {
this.closeDropdownAction = closeFn;
return () => {
this.closeDropdownAction = null;
};
},
);

private registerTriggerElement = modifier((element: HTMLElement) => {
this.triggerElement = element;
return () => {
this.triggerElement = null;
};
});

@action
private scheduleCloseDropdown() {
this.cancelCloseDropdown();
this.closeDropdownTimer = setTimeout(() => {
this.closeDropdownAction?.();
this.triggerElement?.blur();
}, 200);
}

@action
private cancelCloseDropdown() {
if (this.closeDropdownTimer) {
clearTimeout(this.closeDropdownTimer);
this.closeDropdownTimer = null;
}
}

<template>
<li
class='session'
data-test-joined-room={{@session.roomId}}
data-room-id={{@session.roomId}}
data-is-current-room={{@isCurrentRoom}}
{{on 'mouseenter' this.cancelCloseDropdown}}
{{on 'mouseleave' this.scheduleCloseDropdown}}
>
<button
class='view-session-button'
Expand Down Expand Up @@ -87,7 +126,7 @@ export default class PastSessionItem extends Component<Signature> {
{{/if}}
</div>
</button>
<BoxelDropdown>
<BoxelDropdown @contentClass='past-session-menu-dropdown'>
<:trigger as |bindings|>
<Tooltip @placement='top'>
<:trigger>
Expand All @@ -98,6 +137,7 @@ export default class PastSessionItem extends Component<Signature> {
@label='past session options'
data-test-past-session-options-button={{@session.roomId}}
{{bindings}}
{{this.registerTriggerElement}}
/>
</:trigger>
<:content>
Expand All @@ -108,6 +148,9 @@ export default class PastSessionItem extends Component<Signature> {
<:content as |dd|>
<Menu
class='menu past-session-menu'
{{this.registerCloseAction dd.close}}
{{on 'mouseenter' this.cancelCloseDropdown}}
{{on 'mouseleave' this.scheduleCloseDropdown}}
@closeMenu={{fn this.handleCloseMenu dd.close}}
@items={{array
(menuItem
Expand Down Expand Up @@ -139,37 +182,49 @@ export default class PastSessionItem extends Component<Signature> {
}

.session {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
border-top: 1px solid var(--past-sessions-divider-color);
padding: var(--boxel-sp) var(--boxel-sp-sm);
margin-right: var(--boxel-sp-xs);
margin-left: var(--boxel-sp-xs);
border-radius: var(--boxel-border-radius-xs);
}

.session:first-child {
border-top: none;
.session::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 1px;
background: #4f4b57;
}

.session:first-child::before {
display: none;
}

.session:hover {
.session:hover,
.session:has(.menu-button[aria-expanded='true']) {
background-color: var(--ai-assistant-menu-hover-background);
border-radius: calc(var(--boxel-border-radius-xs) + 2px);
cursor: pointer;
}
.session[data-is-current-room] {
border: 1px solid var(--past-sessions-divider-color);
}
.session:hover + .session:not([data-is-current-room]),
.session[data-is-current-room] + .session {
border-top-color: transparent;
.session:hover::before,
.session:has(.menu-button[aria-expanded='true'])::before,
.session:hover + .session::before,
.session:has(.menu-button[aria-expanded='true']) + .session::before {
background: transparent;
}
.name {
font-weight: 600;
}
.date {
margin-top: var(--boxel-sp-xxs);
margin-top: calc(var(--boxel-sp-xxs) - 5px);
color: var(--boxel-400);
font-size: 12px;
}
.view-session-button {
color: var(--boxel-light);
Expand All @@ -195,12 +250,17 @@ export default class PastSessionItem extends Component<Signature> {
visibility: visible;
}

:global(.past-session-menu-dropdown) {
border: none;
box-shadow: none;
}

.menu {
--boxel-menu-item-content-padding: var(--boxel-sp-xxs)
var(--boxel-sp-sm);

background: var(--ai-assistant-menu-background);
border: 1px solid var(--past-sessions-divider-color);
background: #3b394b;
border: 1px solid rgba(255, 255, 255, 0.25);
color: var(--boxel-light);
padding: var(--boxel-sp-xs);
box-shadow: var(--boxel-deep-box-shadow);
Expand All @@ -218,7 +278,7 @@ export default class PastSessionItem extends Component<Signature> {
}

.menu :deep(.boxel-menu__item:hover) {
background-color: var(--ai-assistant-menu-hover-background);
background-color: #272330;
border-radius: var(--boxel-border-radius-xs);
}

Expand Down
96 changes: 57 additions & 39 deletions packages/host/app/components/ai-assistant/past-sessions.gts
Original file line number Diff line number Diff line change
Expand Up @@ -47,53 +47,71 @@ export default class AiAssistantPastSessionsList extends Component<Signature> {
});

<template>
<AiAssistantPanelPopover
@onClose={{@onClose}}
data-test-past-sessions
...attributes
>
<:header>
Past Sessions
</:header>
<:body>
{{#if @sessions}}
<ul class='past-sessions' {{this.checkScroll}}>
{{#each @sessions key='roomId' as |session|}}
<PastSessionItem
@session={{session}}
@isCurrentRoom={{eq session.roomId @currentRoomId}}
@actions={{@roomActions}}
/>
{{/each}}
{{#if this.matrixService.isLoadingMoreAIRooms}}
<li
class='loading-indicator-container'
data-test-loading-more-rooms
>
<LoadingIndicator
@color='var(--boxel-dark)'
class='loading-indicator'
<div class='past-sessions-menu'>
<AiAssistantPanelPopover
@onClose={{@onClose}}
data-test-past-sessions
...attributes
>
<:header>
Past Sessions
</:header>
<:body>
{{#if @sessions}}
<ul class='past-sessions' {{this.checkScroll}}>
{{#each @sessions key='roomId' as |session|}}
<PastSessionItem
@session={{session}}
@isCurrentRoom={{eq session.roomId @currentRoomId}}
@actions={{@roomActions}}
/>
</li>
{{/if}}
</ul>
{{else}}
<div class='empty-collection'>
No past sessions to show.
</div>
{{/if}}
</:body>
</AiAssistantPanelPopover>
{{/each}}
{{#if this.matrixService.isLoadingMoreAIRooms}}
<li
class='loading-indicator-container'
data-test-loading-more-rooms
>
<LoadingIndicator
@color='var(--boxel-dark)'
class='loading-indicator'
/>
</li>
{{/if}}
</ul>
{{else}}
<div class='empty-collection'>
No past sessions to show.
</div>
{{/if}}
</:body>
</AiAssistantPanelPopover>
</div>

<style scoped>
.past-sessions-menu :deep(.panel-popover) {
left: 30px;
right: 30px;
max-width: none;
background: #3b394b;
border: 1px solid rgba(255, 255, 255, 0.25);
}
.past-sessions-menu :deep(.header) {
font: 700 var(--boxel-font-sm);
letter-spacing: var(--boxel-lsp-sm);
}
.past-sessions-menu :deep(.session:hover),
.past-sessions-menu
:deep(.session:has(.menu-button[aria-expanded='true'])) {
background-color: #272330;
}
.past-sessions-menu :deep(.body) {
scroll-timeline: --past-sessions-scroll-timeline block;
}
.past-sessions {
list-style-type: none;
padding: 0;
margin: 0;
margin-bottom: var(--boxel-sp-xs);
max-height: 400px;
overflow-y: auto;
scroll-timeline: --past-sessions-scroll-timeline block;
}
.empty-collection {
padding: var(--boxel-sp-sm);
Expand Down
27 changes: 17 additions & 10 deletions packages/host/app/components/matrix/room.gts
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ export default class Room extends Component<Signature> {
<li class='llm-select-footer'>
{{#if this.systemCardId}}
<BoxelButton
@kind='text-only'
@kind='primary'
@size='extra-small'
class='llm-select-footer-action'
{{on 'click' this.goToSystemCard}}
Expand All @@ -335,7 +335,7 @@ export default class Room extends Component<Signature> {
{{/if}}
{{#unless this.isDefaultSystemCard}}
<BoxelButton
@kind='text-only'
@kind='primary'
@size='extra-small'
class='llm-select-footer-action'
{{on
Expand Down Expand Up @@ -534,22 +534,29 @@ export default class Room extends Component<Signature> {

.llm-select-footer {
display: flex;
flex-direction: column;
gap: var(--boxel-sp-xxxs);
flex-wrap: wrap;
gap: 5px;
border-top: 1px solid var(--boxel-200);
padding-top: var(--boxel-sp-xxxs);
}

.llm-select-footer-action {
--boxel-button-padding: var(--boxel-sp-xxxs) var(--boxel-sp-sm);
--boxel-button-font: 600 var(--boxel-font-xs);
--boxel-button-border: 1px solid var(--boxel-400);
--boxel-button-color: var(--boxel-dark);
--boxel-button-padding: var(--boxel-sp-5xs) var(--boxel-sp-sm);
--boxel-button-min-height: unset;
justify-content: flex-start;
font: 500 var(--boxel-font-xs);
color: var(--boxel-500);

gap: var(--boxel-sp-xs);
background: none;
}

.llm-select-footer-action:hover {
color: var(--boxel-dark);
.llm-select-footer-action:hover:not(:disabled),
.llm-select-footer-action:focus:not(:disabled) {
--icon-color: var(--boxel-600);
color: var(--boxel-600);
background: none;
box-shadow: none;
}

.chat-input-area :deep(.minimized-arrow) {
Expand Down
Loading
Loading