mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-10-22 01:53:33 +00:00
🕯️ style: Settle the Applied Steer Receipt Once the Response Is Done (#15377)
The applied checkmarks kept the amber steering identity forever, so a conversation full of historical steers stayed lit long after anyone cared whether they landed. The receipt now keeps amber only while the owning response is still generating (isSubmitting, the window where it is the live thing at the end) and settles to the timestamp gray with a soft color transition. Reload, share, and search render settled.
This commit is contained in:
parent
9ac801ee45
commit
cd46d50810
4 changed files with 43 additions and 8 deletions
|
|
@ -84,6 +84,7 @@ const Part = memo(function Part({
|
|||
quotes={part.quotes}
|
||||
steerId={part.steerId}
|
||||
createdAt={part.createdAt}
|
||||
isSubmitting={isSubmitting}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ const SteerPart = memo(function SteerPart({
|
|||
quotes,
|
||||
steerId,
|
||||
createdAt,
|
||||
isSubmitting = false,
|
||||
}: {
|
||||
steer: string;
|
||||
files?: TMessage['files'];
|
||||
|
|
@ -38,6 +39,10 @@ const SteerPart = memo(function SteerPart({
|
|||
/** Anchors the part for the message-nav rail (`#steer-<id>` rib target). */
|
||||
steerId?: string;
|
||||
createdAt?: number;
|
||||
/** The owning response is still generating: the receipt keeps its amber
|
||||
* steering identity while it is the live thing at the end, then settles
|
||||
* to timestamp gray (always settled on reload, share, and search). */
|
||||
isSubmitting?: boolean;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
/** Read the atom rather than the auth context: AuthContextProvider mirrors the
|
||||
|
|
@ -151,7 +156,7 @@ const SteerPart = memo(function SteerPart({
|
|||
</CollapsibleText>
|
||||
</div>
|
||||
<div className="mt-1 flex min-h-8 items-center justify-end text-text-secondary">
|
||||
<SteerReceipt state="applied" animateIn={animateIn} />
|
||||
<SteerReceipt state="applied" live={isSubmitting} animateIn={animateIn} />
|
||||
<MessageTimestamp value={timestamp} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -260,3 +260,25 @@ describe('SteerPart live receipt draw-in', () => {
|
|||
expect(screen.getByTestId('live-ids').textContent).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('SteerPart receipt settling', () => {
|
||||
const checks = () => screen.getByLabelText('com_ui_steer_applied_info').querySelector('svg');
|
||||
|
||||
it('keeps the amber identity while the owning response is still generating', () => {
|
||||
render(
|
||||
<RecoilRoot initializeState={({ set }) => set(store.user, SEEDED_USER as never)}>
|
||||
<SteerPart steer="steered words" steerId="s1" createdAt={1} isSubmitting />
|
||||
</RecoilRoot>,
|
||||
);
|
||||
expect(checks()).toHaveClass('dark:text-amber-500');
|
||||
expect(checks()).not.toHaveClass('text-text-secondary');
|
||||
});
|
||||
|
||||
it('settles to timestamp gray once the response is done, and on reload/share', () => {
|
||||
// The default (no isSubmitting) is the settled, reload, share, and search
|
||||
// rendering: still a double check, no longer lit.
|
||||
renderPart();
|
||||
expect(checks()).toHaveClass('text-text-secondary');
|
||||
expect(checks()).not.toHaveClass('dark:text-amber-500');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -22,20 +22,27 @@ const AMBER_DOT = 'bg-amber-600 dark:bg-amber-500';
|
|||
* - `interrupting`: asked to seal generation at the next safe boundary — the
|
||||
* amber label and pulsing dot say "working, not stuck". Until the arm is
|
||||
* server-confirmed (`confirmed` false) the label shows without its check.
|
||||
* - `applied`: the running response durably absorbed the words (amber double
|
||||
* check, persistent — it renders identically live, on reload, and in
|
||||
* shared/search views).
|
||||
* - `applied`: the running response durably absorbed the words (double check,
|
||||
* persistent). It stays lit amber only while the owning response is still
|
||||
* generating — the window where "did it land" is the question — then
|
||||
* settles to the same muted gray as the timestamp, so a conversation full
|
||||
* of historical steers doesn't glow forever.
|
||||
*
|
||||
* The hover explanations render through the shared `InfoHoverCard` with the
|
||||
* receipt as the trigger, so the info text doubles as the accessible name.
|
||||
*/
|
||||
const SteerReceipt = memo(function SteerReceipt({
|
||||
state,
|
||||
live = false,
|
||||
confirmed = true,
|
||||
animateIn = false,
|
||||
className,
|
||||
}: {
|
||||
state: SteerReceiptState;
|
||||
/** Applied only: the owning response is still generating, so the receipt is
|
||||
* the newest thing on screen and keeps the amber steering identity. Off
|
||||
* (settled, reload, share, search) it dims to the timestamp gray. */
|
||||
live?: boolean;
|
||||
/** Server confirmation of the underlying enqueue/arm; an interrupt awaiting
|
||||
* its ACK shows the label and pulse without a check. */
|
||||
confirmed?: boolean;
|
||||
|
|
@ -62,15 +69,15 @@ const SteerReceipt = memo(function SteerReceipt({
|
|||
<span
|
||||
data-testid="steer-receipt"
|
||||
data-receipt-state="applied"
|
||||
data-receipt-live={live ? 'true' : undefined}
|
||||
className={cn('flex items-center', className)}
|
||||
>
|
||||
<InfoHoverCard side={ESide.Top} text={localize('com_ui_steer_applied_info')}>
|
||||
<CheckCheck
|
||||
className={cn(
|
||||
'h-4 w-4',
|
||||
AMBER_ICON,
|
||||
animateIn &&
|
||||
'duration-theme-normal ease-out animate-in fade-in-0 zoom-in-50 motion-reduce:animate-none',
|
||||
'h-4 w-4 transition-colors duration-theme-normal motion-reduce:transition-none',
|
||||
live ? AMBER_ICON : 'text-text-secondary',
|
||||
animateIn && 'ease-out animate-in fade-in-0 zoom-in-50 motion-reduce:animate-none',
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue