feat: show model in working indicator and fix streaming bottom jitter
- Working indicator shows provider icon and model name with the live status (e.g. 'Fable 5 is reading file'), localized via chat.statusRow.modelStatus - Pin auto-follow to the exact fractional scroll maximum and re-pin on every passive follow instead of skipping within tolerance - Round message text line-height to whole pixels so streamed content grows on the pixel grid; kills the 1px vertical jitter of bottom-anchored rows during streaming
This commit is contained in:
@@ -133,6 +133,8 @@ interface StatusRowProps {
|
||||
showAssistantStatus?: boolean;
|
||||
showTodos?: boolean;
|
||||
agentName?: string;
|
||||
modelName?: string | null;
|
||||
providerId?: string | null;
|
||||
leftAccessory?: React.ReactNode;
|
||||
}
|
||||
|
||||
@@ -150,6 +152,8 @@ export const StatusRow: React.FC<StatusRowProps> = ({
|
||||
showAssistantStatus = true,
|
||||
showTodos = true,
|
||||
agentName,
|
||||
modelName,
|
||||
providerId,
|
||||
leftAccessory,
|
||||
}) => {
|
||||
const { t } = useI18n();
|
||||
@@ -313,6 +317,8 @@ export const StatusRow: React.FC<StatusRowProps> = ({
|
||||
isWaitingForPermission={isWaitingForPermission}
|
||||
retryInfo={retryInfo}
|
||||
agentName={agentName}
|
||||
modelName={modelName}
|
||||
providerId={providerId}
|
||||
/>
|
||||
) : leftAccessory ? (
|
||||
leftAccessory
|
||||
|
||||
@@ -3,6 +3,7 @@ import React from 'react';
|
||||
import { useAssistantStatus } from '@/hooks/useAssistantStatus';
|
||||
import { useConfigStore } from '@/stores/useConfigStore';
|
||||
import { useSessionUIStore } from '@/sync/session-ui-store';
|
||||
import { getProviderModelDisplayName } from '@/lib/modelDisplay';
|
||||
import { StatusRow } from './StatusRow';
|
||||
|
||||
/**
|
||||
@@ -22,6 +23,19 @@ export const StatusRowContainer: React.FC = React.memo(() => {
|
||||
);
|
||||
const { working } = useAssistantStatus();
|
||||
const currentAgentName = useConfigStore((state) => state.currentAgentName);
|
||||
const currentProviderId = useConfigStore((state) => state.currentProviderId);
|
||||
const currentModelId = useConfigStore((state) => state.currentModelId);
|
||||
const providers = useConfigStore((state) => state.providers);
|
||||
|
||||
const modelDisplayName = React.useMemo(() => {
|
||||
if (!currentModelId) {
|
||||
return null;
|
||||
}
|
||||
const provider = currentProviderId && providers.length > 0
|
||||
? providers.find((candidate) => candidate.id === currentProviderId)
|
||||
: undefined;
|
||||
return getProviderModelDisplayName(provider, currentModelId) || null;
|
||||
}, [currentProviderId, currentModelId, providers]);
|
||||
|
||||
const wasAborted = Boolean(abortRecord && !abortRecord.acknowledged);
|
||||
|
||||
@@ -37,6 +51,8 @@ export const StatusRowContainer: React.FC = React.memo(() => {
|
||||
showAssistantStatus
|
||||
showTodos={false}
|
||||
agentName={currentAgentName}
|
||||
modelName={modelDisplayName}
|
||||
providerId={currentProviderId ?? null}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import React from 'react';
|
||||
import { BusyDots } from './BusyDots';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import { useProviderLogo } from '@/hooks/useProviderLogo';
|
||||
import { useThemeSystem } from '@/contexts/useThemeSystem';
|
||||
|
||||
interface WorkingPlaceholderProps {
|
||||
isWorking: boolean;
|
||||
@@ -8,6 +11,8 @@ interface WorkingPlaceholderProps {
|
||||
isWaitingForPermission?: boolean;
|
||||
retryInfo?: { attempt?: number; next?: number } | null;
|
||||
agentName?: string;
|
||||
modelName?: string | null;
|
||||
providerId?: string | null;
|
||||
}
|
||||
|
||||
const STATUS_DISPLAY_TIME_MS = 1200;
|
||||
@@ -58,7 +63,13 @@ export function WorkingPlaceholder({
|
||||
isGenericStatus,
|
||||
isWaitingForPermission,
|
||||
retryInfo,
|
||||
modelName,
|
||||
providerId,
|
||||
}: WorkingPlaceholderProps) {
|
||||
const { t } = useI18n();
|
||||
const { src: providerLogoSrc, onError: handleProviderLogoError, hasLogo: hasProviderLogo } = useProviderLogo(providerId ?? null);
|
||||
const { currentTheme } = useThemeSystem();
|
||||
const isDarkTheme = currentTheme?.metadata.variant === 'dark';
|
||||
const [displayedText, setDisplayedText] = React.useState<string | null>(null);
|
||||
const [displayedPermission, setDisplayedPermission] = React.useState<boolean>(false);
|
||||
const displayedTextRef = React.useRef(displayedText);
|
||||
@@ -211,7 +222,10 @@ export function WorkingPlaceholder({
|
||||
return null;
|
||||
}
|
||||
|
||||
const label = displayedText.charAt(0).toUpperCase() + displayedText.slice(1);
|
||||
const trimmedModelName = typeof modelName === 'string' ? modelName.trim() : '';
|
||||
const label = trimmedModelName.length > 0
|
||||
? t('chat.statusRow.modelStatus', { model: trimmedModelName, status: displayedText })
|
||||
: displayedText.charAt(0).toUpperCase() + displayedText.slice(1);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -224,6 +238,18 @@ export function WorkingPlaceholder({
|
||||
data-waiting={displayedPermission ? 'true' : undefined}
|
||||
>
|
||||
<span className="typography-ui-header">
|
||||
{hasProviderLogo && providerLogoSrc ? (
|
||||
<img
|
||||
src={providerLogoSrc}
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
className="inline-block h-3.5 w-3.5 mr-1.5 align-[-2px]"
|
||||
style={{
|
||||
filter: isDarkTheme ? 'brightness(0.9) contrast(1.1) invert(1)' : 'brightness(0.9) contrast(1.1)',
|
||||
}}
|
||||
onError={handleProviderLogoError}
|
||||
/>
|
||||
) : null}
|
||||
{label}
|
||||
<BusyDots />
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user