feat(desktop): show session metadata on sidebar chat hover (#10663)

This commit is contained in:
Michael A. Smith
2026-08-03 07:02:53 -04:00
committed by GitHub
parent 5ab0e6df34
commit 173d81f293
17 changed files with 562 additions and 29 deletions
@@ -15,6 +15,8 @@ import { AppEvents } from '../../constants/events';
import { InlineEditText } from '../common/InlineEditText';
import { SessionIndicators } from '../SessionIndicators';
import { acpRenameSession, type SessionListItem } from '../../acp/sessions';
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/Tooltip';
import { formatMessageTimestamp } from '../../utils/timeUtils';
import { cn } from '../../utils';
import type { ProjectGroup } from '../../utils/projectSessions';
import { defineMessages, useIntl } from '../../i18n';
@@ -39,6 +41,42 @@ const i18n = defineMessages({
id: 'navigationPanel.untitledSession',
defaultMessage: 'Untitled session',
},
metaModel: {
id: 'navigationPanel.metaModel',
defaultMessage: 'Model',
},
metaDirectory: {
id: 'navigationPanel.metaDirectory',
defaultMessage: 'Directory',
},
metaStatus: {
id: 'navigationPanel.metaStatus',
defaultMessage: 'Status',
},
metaCreated: {
id: 'navigationPanel.metaCreated',
defaultMessage: 'Created',
},
metaUpdated: {
id: 'navigationPanel.metaUpdated',
defaultMessage: 'Updated',
},
statusStreaming: {
id: 'navigationPanel.statusStreaming',
defaultMessage: 'Streaming',
},
statusError: {
id: 'navigationPanel.statusError',
defaultMessage: 'Error',
},
statusUnread: {
id: 'navigationPanel.statusUnread',
defaultMessage: 'Unread activity',
},
statusIdle: {
id: 'navigationPanel.statusIdle',
defaultMessage: 'Idle',
},
});
const navItemClass = (active: boolean) =>
@@ -78,43 +116,106 @@ interface SessionRowProps {
onRenamed: () => void;
}
const formatTimestamp = (value?: string): string | null => {
if (!value) return null;
const parsed = Date.parse(value);
if (Number.isNaN(parsed)) return null;
return formatMessageTimestamp(parsed / 1000);
};
const MetaRow: React.FC<{ label: string; value: string }> = ({ label, value }) => (
<div className="flex gap-2">
<span className="text-text-inverse/60 flex-shrink-0">{label}</span>
<span className="text-right ml-auto break-all">{value}</span>
</div>
);
interface SessionTooltipContentProps {
session: SessionListItem;
statusLabel: string;
}
const SessionTooltipContent: React.FC<SessionTooltipContentProps> = ({ session, statusLabel }) => {
const intl = useIntl();
const model = session.modelId
? session.providerId
? `${session.modelId} (${session.providerId})`
: session.modelId
: session.providerId;
const created = formatTimestamp(session.createdAt);
const updated = formatTimestamp(session.lastMessageAt ?? session.updatedAt);
return (
<div className="flex flex-col gap-1 text-xs">
<div className="font-medium break-words">
{session.name || intl.formatMessage(i18n.untitledSession)}
</div>
<div className="flex flex-col gap-0.5">
{model && <MetaRow label={intl.formatMessage(i18n.metaModel)} value={model} />}
{session.workingDir && (
<MetaRow label={intl.formatMessage(i18n.metaDirectory)} value={session.workingDir} />
)}
<MetaRow label={intl.formatMessage(i18n.metaStatus)} value={statusLabel} />
{created && <MetaRow label={intl.formatMessage(i18n.metaCreated)} value={created} />}
{updated && <MetaRow label={intl.formatMessage(i18n.metaUpdated)} value={updated} />}
</div>
</div>
);
};
const SessionRow: React.FC<SessionRowProps> = ({ session, active, status, onClick, onRenamed }) => {
const intl = useIntl();
const [isEditing, setIsEditing] = useState(false);
const [tooltipOpen, setTooltipOpen] = useState(false);
const isStreaming = status?.streamState === 'streaming';
const hasError = status?.streamState === 'error';
const hasUnread = status?.hasUnreadActivity ?? false;
const statusLabel = isStreaming
? intl.formatMessage(i18n.statusStreaming)
: hasError
? intl.formatMessage(i18n.statusError)
: hasUnread
? intl.formatMessage(i18n.statusUnread)
: intl.formatMessage(i18n.statusIdle);
return (
<div
onClick={() => !isEditing && onClick()}
className={cn(
'flex items-center gap-2 px-3 py-1.5 rounded-full cursor-pointer text-sm',
'hover:bg-background-tertiary/60 transition-colors',
active && 'bg-background-tertiary'
)}
>
<InlineEditText
value={session.name}
onSave={async (newName) => {
await acpRenameSession(session.id, newName);
window.dispatchEvent(
new CustomEvent(AppEvents.SESSION_RENAMED, {
detail: { sessionId: session.id, newName, userInitiated: true },
})
);
onRenamed();
}}
placeholder={intl.formatMessage(i18n.untitledSession)}
disabled={isStreaming}
singleClickEdit={false}
className="truncate text-text-primary flex-1 !px-0 !py-0 hover:bg-transparent"
editClassName="!text-sm"
onEditStart={() => setIsEditing(true)}
onEditEnd={() => setIsEditing(false)}
/>
<SessionIndicators isStreaming={isStreaming} hasUnread={hasUnread} hasError={hasError} />
</div>
<Tooltip open={tooltipOpen && !isEditing} onOpenChange={setTooltipOpen} delayDuration={400}>
<TooltipTrigger asChild>
<div
onClick={() => !isEditing && onClick()}
className={cn(
'flex items-center gap-2 px-3 py-1.5 rounded-full cursor-pointer text-sm',
'hover:bg-background-tertiary/60 transition-colors',
active && 'bg-background-tertiary'
)}
>
<InlineEditText
value={session.name}
onSave={async (newName) => {
await acpRenameSession(session.id, newName);
window.dispatchEvent(
new CustomEvent(AppEvents.SESSION_RENAMED, {
detail: { sessionId: session.id, newName, userInitiated: true },
})
);
onRenamed();
}}
placeholder={intl.formatMessage(i18n.untitledSession)}
disabled={isStreaming}
singleClickEdit={false}
className="truncate text-text-primary flex-1 !px-0 !py-0 hover:bg-transparent"
editClassName="!text-sm"
onEditStart={() => setIsEditing(true)}
onEditEnd={() => setIsEditing(false)}
/>
<SessionIndicators isStreaming={isStreaming} hasUnread={hasUnread} hasError={hasError} />
</div>
</TooltipTrigger>
<TooltipContent side="right" align="start" className="max-w-xs text-left">
<SessionTooltipContent session={session} statusLabel={statusLabel} />
</TooltipContent>
</Tooltip>
);
};
+27
View File
@@ -2360,9 +2360,36 @@
"navigationPanel.chats": {
"defaultMessage": "Chats"
},
"navigationPanel.metaCreated": {
"defaultMessage": "Erstellt"
},
"navigationPanel.metaDirectory": {
"defaultMessage": "Verzeichnis"
},
"navigationPanel.metaModel": {
"defaultMessage": "Modell"
},
"navigationPanel.metaStatus": {
"defaultMessage": "Status"
},
"navigationPanel.metaUpdated": {
"defaultMessage": "Aktualisiert"
},
"navigationPanel.noChats": {
"defaultMessage": "Keine aktuellen Chats"
},
"navigationPanel.statusError": {
"defaultMessage": "Fehler"
},
"navigationPanel.statusIdle": {
"defaultMessage": "Inaktiv"
},
"navigationPanel.statusStreaming": {
"defaultMessage": "Streaming"
},
"navigationPanel.statusUnread": {
"defaultMessage": "Ungelesene Aktivität"
},
"navigationPanel.untitledSession": {
"defaultMessage": "Unbenannte Sitzung"
},
+27
View File
@@ -2360,9 +2360,36 @@
"navigationPanel.chats": {
"defaultMessage": "Chats"
},
"navigationPanel.metaCreated": {
"defaultMessage": "Created"
},
"navigationPanel.metaDirectory": {
"defaultMessage": "Directory"
},
"navigationPanel.metaModel": {
"defaultMessage": "Model"
},
"navigationPanel.metaStatus": {
"defaultMessage": "Status"
},
"navigationPanel.metaUpdated": {
"defaultMessage": "Updated"
},
"navigationPanel.noChats": {
"defaultMessage": "No recent chats"
},
"navigationPanel.statusError": {
"defaultMessage": "Error"
},
"navigationPanel.statusIdle": {
"defaultMessage": "Idle"
},
"navigationPanel.statusStreaming": {
"defaultMessage": "Streaming"
},
"navigationPanel.statusUnread": {
"defaultMessage": "Unread activity"
},
"navigationPanel.untitledSession": {
"defaultMessage": "Untitled session"
},
+27
View File
@@ -2360,9 +2360,36 @@
"navigationPanel.chats": {
"defaultMessage": "Chats"
},
"navigationPanel.metaCreated": {
"defaultMessage": "Creado"
},
"navigationPanel.metaDirectory": {
"defaultMessage": "Directorio"
},
"navigationPanel.metaModel": {
"defaultMessage": "Modelo"
},
"navigationPanel.metaStatus": {
"defaultMessage": "Estado"
},
"navigationPanel.metaUpdated": {
"defaultMessage": "Actualizado"
},
"navigationPanel.noChats": {
"defaultMessage": "No hay chats recientes"
},
"navigationPanel.statusError": {
"defaultMessage": "Error"
},
"navigationPanel.statusIdle": {
"defaultMessage": "Inactivo"
},
"navigationPanel.statusStreaming": {
"defaultMessage": "Transmitiendo"
},
"navigationPanel.statusUnread": {
"defaultMessage": "Actividad sin leer"
},
"navigationPanel.untitledSession": {
"defaultMessage": "Sesión sin título"
},
+27
View File
@@ -2360,9 +2360,36 @@
"navigationPanel.chats": {
"defaultMessage": "Discussions"
},
"navigationPanel.metaCreated": {
"defaultMessage": "Créé"
},
"navigationPanel.metaDirectory": {
"defaultMessage": "Répertoire"
},
"navigationPanel.metaModel": {
"defaultMessage": "Modèle"
},
"navigationPanel.metaStatus": {
"defaultMessage": "Statut"
},
"navigationPanel.metaUpdated": {
"defaultMessage": "Mis à jour"
},
"navigationPanel.noChats": {
"defaultMessage": "Aucune discussion récente"
},
"navigationPanel.statusError": {
"defaultMessage": "Erreur"
},
"navigationPanel.statusIdle": {
"defaultMessage": "Inactif"
},
"navigationPanel.statusStreaming": {
"defaultMessage": "Diffusion en continu"
},
"navigationPanel.statusUnread": {
"defaultMessage": "Activité non lue"
},
"navigationPanel.untitledSession": {
"defaultMessage": "Session sans titre"
},
+27
View File
@@ -2360,9 +2360,36 @@
"navigationPanel.chats": {
"defaultMessage": "चैट"
},
"navigationPanel.metaCreated": {
"defaultMessage": "बनाया गया"
},
"navigationPanel.metaDirectory": {
"defaultMessage": "निर्देशिका"
},
"navigationPanel.metaModel": {
"defaultMessage": "मॉडल"
},
"navigationPanel.metaStatus": {
"defaultMessage": "स्थिति"
},
"navigationPanel.metaUpdated": {
"defaultMessage": "अपडेट किया गया"
},
"navigationPanel.noChats": {
"defaultMessage": "कोई हालिया चैट नहीं"
},
"navigationPanel.statusError": {
"defaultMessage": "त्रुटि"
},
"navigationPanel.statusIdle": {
"defaultMessage": "निष्क्रिय"
},
"navigationPanel.statusStreaming": {
"defaultMessage": "स्ट्रीमिंग"
},
"navigationPanel.statusUnread": {
"defaultMessage": "अपठित गतिविधि"
},
"navigationPanel.untitledSession": {
"defaultMessage": "शीर्षकहीन सत्र"
},
+27
View File
@@ -2360,9 +2360,36 @@
"navigationPanel.chats": {
"defaultMessage": "Obrolan"
},
"navigationPanel.metaCreated": {
"defaultMessage": "Dibuat"
},
"navigationPanel.metaDirectory": {
"defaultMessage": "Direktori"
},
"navigationPanel.metaModel": {
"defaultMessage": "Model"
},
"navigationPanel.metaStatus": {
"defaultMessage": "Status"
},
"navigationPanel.metaUpdated": {
"defaultMessage": "Diperbarui"
},
"navigationPanel.noChats": {
"defaultMessage": "Tidak ada obrolan terbaru"
},
"navigationPanel.statusError": {
"defaultMessage": "Kesalahan"
},
"navigationPanel.statusIdle": {
"defaultMessage": "Menganggur"
},
"navigationPanel.statusStreaming": {
"defaultMessage": "Streaming"
},
"navigationPanel.statusUnread": {
"defaultMessage": "Aktivitas belum dibaca"
},
"navigationPanel.untitledSession": {
"defaultMessage": "Sesi tanpa judul"
},
+27
View File
@@ -2360,9 +2360,36 @@
"navigationPanel.chats": {
"defaultMessage": "Chat"
},
"navigationPanel.metaCreated": {
"defaultMessage": "Creato"
},
"navigationPanel.metaDirectory": {
"defaultMessage": "Directory"
},
"navigationPanel.metaModel": {
"defaultMessage": "Modello"
},
"navigationPanel.metaStatus": {
"defaultMessage": "Stato"
},
"navigationPanel.metaUpdated": {
"defaultMessage": "Aggiornato"
},
"navigationPanel.noChats": {
"defaultMessage": "Nessuna chat recente"
},
"navigationPanel.statusError": {
"defaultMessage": "Errore"
},
"navigationPanel.statusIdle": {
"defaultMessage": "Inattivo"
},
"navigationPanel.statusStreaming": {
"defaultMessage": "In streaming"
},
"navigationPanel.statusUnread": {
"defaultMessage": "Attività non letta"
},
"navigationPanel.untitledSession": {
"defaultMessage": "Sessione senza titolo"
},
+27
View File
@@ -2360,9 +2360,36 @@
"navigationPanel.chats": {
"defaultMessage": "チャット"
},
"navigationPanel.metaCreated": {
"defaultMessage": "作成"
},
"navigationPanel.metaDirectory": {
"defaultMessage": "ディレクトリ"
},
"navigationPanel.metaModel": {
"defaultMessage": "モデル"
},
"navigationPanel.metaStatus": {
"defaultMessage": "ステータス"
},
"navigationPanel.metaUpdated": {
"defaultMessage": "更新"
},
"navigationPanel.noChats": {
"defaultMessage": "最近のチャットはありません"
},
"navigationPanel.statusError": {
"defaultMessage": "エラー"
},
"navigationPanel.statusIdle": {
"defaultMessage": "アイドル"
},
"navigationPanel.statusStreaming": {
"defaultMessage": "ストリーミング"
},
"navigationPanel.statusUnread": {
"defaultMessage": "未読のアクティビティ"
},
"navigationPanel.untitledSession": {
"defaultMessage": "無題のセッション"
},
+27
View File
@@ -2360,9 +2360,36 @@
"navigationPanel.chats": {
"defaultMessage": "채팅"
},
"navigationPanel.metaCreated": {
"defaultMessage": "생성됨"
},
"navigationPanel.metaDirectory": {
"defaultMessage": "디렉터리"
},
"navigationPanel.metaModel": {
"defaultMessage": "모델"
},
"navigationPanel.metaStatus": {
"defaultMessage": "상태"
},
"navigationPanel.metaUpdated": {
"defaultMessage": "업데이트됨"
},
"navigationPanel.noChats": {
"defaultMessage": "최근 채팅 없음"
},
"navigationPanel.statusError": {
"defaultMessage": "오류"
},
"navigationPanel.statusIdle": {
"defaultMessage": "유휴"
},
"navigationPanel.statusStreaming": {
"defaultMessage": "스트리밍"
},
"navigationPanel.statusUnread": {
"defaultMessage": "읽지 않은 활동"
},
"navigationPanel.untitledSession": {
"defaultMessage": "제목 없는 세션"
},
+27
View File
@@ -2360,9 +2360,36 @@
"navigationPanel.chats": {
"defaultMessage": "Sembang"
},
"navigationPanel.metaCreated": {
"defaultMessage": "Dicipta"
},
"navigationPanel.metaDirectory": {
"defaultMessage": "Direktori"
},
"navigationPanel.metaModel": {
"defaultMessage": "Model"
},
"navigationPanel.metaStatus": {
"defaultMessage": "Status"
},
"navigationPanel.metaUpdated": {
"defaultMessage": "Dikemas kini"
},
"navigationPanel.noChats": {
"defaultMessage": "Tiada sembang terkini"
},
"navigationPanel.statusError": {
"defaultMessage": "Ralat"
},
"navigationPanel.statusIdle": {
"defaultMessage": "Terbiar"
},
"navigationPanel.statusStreaming": {
"defaultMessage": "Penstriman"
},
"navigationPanel.statusUnread": {
"defaultMessage": "Aktiviti belum dibaca"
},
"navigationPanel.untitledSession": {
"defaultMessage": "Sesi tanpa tajuk"
},
+27
View File
@@ -2360,9 +2360,36 @@
"navigationPanel.chats": {
"defaultMessage": "Conversas"
},
"navigationPanel.metaCreated": {
"defaultMessage": "Criado"
},
"navigationPanel.metaDirectory": {
"defaultMessage": "Diretório"
},
"navigationPanel.metaModel": {
"defaultMessage": "Modelo"
},
"navigationPanel.metaStatus": {
"defaultMessage": "Status"
},
"navigationPanel.metaUpdated": {
"defaultMessage": "Atualizado"
},
"navigationPanel.noChats": {
"defaultMessage": "Sem conversas recentes"
},
"navigationPanel.statusError": {
"defaultMessage": "Erro"
},
"navigationPanel.statusIdle": {
"defaultMessage": "Inativo"
},
"navigationPanel.statusStreaming": {
"defaultMessage": "Transmitindo"
},
"navigationPanel.statusUnread": {
"defaultMessage": "Atividade não lida"
},
"navigationPanel.untitledSession": {
"defaultMessage": "Sessão sem título"
},
+27
View File
@@ -2360,9 +2360,36 @@
"navigationPanel.chats": {
"defaultMessage": "Чаты"
},
"navigationPanel.metaCreated": {
"defaultMessage": "Создано"
},
"navigationPanel.metaDirectory": {
"defaultMessage": "Каталог"
},
"navigationPanel.metaModel": {
"defaultMessage": "Модель"
},
"navigationPanel.metaStatus": {
"defaultMessage": "Статус"
},
"navigationPanel.metaUpdated": {
"defaultMessage": "Обновлено"
},
"navigationPanel.noChats": {
"defaultMessage": "Нет недавних чатов"
},
"navigationPanel.statusError": {
"defaultMessage": "Ошибка"
},
"navigationPanel.statusIdle": {
"defaultMessage": "Ожидание"
},
"navigationPanel.statusStreaming": {
"defaultMessage": "Потоковая передача"
},
"navigationPanel.statusUnread": {
"defaultMessage": "Непрочитанная активность"
},
"navigationPanel.untitledSession": {
"defaultMessage": "Безымянный сеанс"
},
+27
View File
@@ -2360,9 +2360,36 @@
"navigationPanel.chats": {
"defaultMessage": "Sohbetler"
},
"navigationPanel.metaCreated": {
"defaultMessage": "Oluşturuldu"
},
"navigationPanel.metaDirectory": {
"defaultMessage": "Dizin"
},
"navigationPanel.metaModel": {
"defaultMessage": "Model"
},
"navigationPanel.metaStatus": {
"defaultMessage": "Durum"
},
"navigationPanel.metaUpdated": {
"defaultMessage": "Güncellendi"
},
"navigationPanel.noChats": {
"defaultMessage": "Son sohbet yok"
},
"navigationPanel.statusError": {
"defaultMessage": "Hata"
},
"navigationPanel.statusIdle": {
"defaultMessage": "Boşta"
},
"navigationPanel.statusStreaming": {
"defaultMessage": "Akış"
},
"navigationPanel.statusUnread": {
"defaultMessage": "Okunmamış etkinlik"
},
"navigationPanel.untitledSession": {
"defaultMessage": "Başlıksız oturum"
},
+27
View File
@@ -2360,9 +2360,36 @@
"navigationPanel.chats": {
"defaultMessage": "Cuộc trò chuyện"
},
"navigationPanel.metaCreated": {
"defaultMessage": "Đã tạo"
},
"navigationPanel.metaDirectory": {
"defaultMessage": "Thư mục"
},
"navigationPanel.metaModel": {
"defaultMessage": "Mô hình"
},
"navigationPanel.metaStatus": {
"defaultMessage": "Trạng thái"
},
"navigationPanel.metaUpdated": {
"defaultMessage": "Đã cập nhật"
},
"navigationPanel.noChats": {
"defaultMessage": "Không có cuộc trò chuyện gần đây"
},
"navigationPanel.statusError": {
"defaultMessage": "Lỗi"
},
"navigationPanel.statusIdle": {
"defaultMessage": "Nhàn rỗi"
},
"navigationPanel.statusStreaming": {
"defaultMessage": "Đang truyền"
},
"navigationPanel.statusUnread": {
"defaultMessage": "Hoạt động chưa đọc"
},
"navigationPanel.untitledSession": {
"defaultMessage": "Phiên chưa đặt tên"
},
+27
View File
@@ -2360,9 +2360,36 @@
"navigationPanel.chats": {
"defaultMessage": "聊天"
},
"navigationPanel.metaCreated": {
"defaultMessage": "创建时间"
},
"navigationPanel.metaDirectory": {
"defaultMessage": "目录"
},
"navigationPanel.metaModel": {
"defaultMessage": "模型"
},
"navigationPanel.metaStatus": {
"defaultMessage": "状态"
},
"navigationPanel.metaUpdated": {
"defaultMessage": "更新时间"
},
"navigationPanel.noChats": {
"defaultMessage": "没有最近的聊天"
},
"navigationPanel.statusError": {
"defaultMessage": "错误"
},
"navigationPanel.statusIdle": {
"defaultMessage": "空闲"
},
"navigationPanel.statusStreaming": {
"defaultMessage": "流式传输"
},
"navigationPanel.statusUnread": {
"defaultMessage": "未读活动"
},
"navigationPanel.untitledSession": {
"defaultMessage": "未命名会话"
},
+27
View File
@@ -2360,9 +2360,36 @@
"navigationPanel.chats": {
"defaultMessage": "交談"
},
"navigationPanel.metaCreated": {
"defaultMessage": "建立時間"
},
"navigationPanel.metaDirectory": {
"defaultMessage": "目錄"
},
"navigationPanel.metaModel": {
"defaultMessage": "模型"
},
"navigationPanel.metaStatus": {
"defaultMessage": "狀態"
},
"navigationPanel.metaUpdated": {
"defaultMessage": "更新時間"
},
"navigationPanel.noChats": {
"defaultMessage": "沒有最近的交談"
},
"navigationPanel.statusError": {
"defaultMessage": "錯誤"
},
"navigationPanel.statusIdle": {
"defaultMessage": "閒置"
},
"navigationPanel.statusStreaming": {
"defaultMessage": "串流"
},
"navigationPanel.statusUnread": {
"defaultMessage": "未讀活動"
},
"navigationPanel.untitledSession": {
"defaultMessage": "未命名工作階段"
},