diff --git a/packages/browseros-agent/apps/agent/entrypoints/sidepanel/index/Chat.tsx b/packages/browseros-agent/apps/agent/entrypoints/sidepanel/index/Chat.tsx index b860c9a97..3fe480b33 100644 --- a/packages/browseros-agent/apps/agent/entrypoints/sidepanel/index/Chat.tsx +++ b/packages/browseros-agent/apps/agent/entrypoints/sidepanel/index/Chat.tsx @@ -22,6 +22,7 @@ import { ChatError } from './ChatError' import { ChatFooter } from './ChatFooter' import { ChatMessages } from './ChatMessages' import type { ChatMode } from './chatTypes' +import { SessionCompletedFooter } from './SessionCompletedFooter' /** * @public @@ -240,6 +241,12 @@ export const Chat = () => { {chatError && ( )} + {messages.length > 0 && + status === 'ready' && + !chatError && + !agentUrlError && ( + + )} { + it('returns null when provider is undefined', () => { + expect(formatSessionCompletedLabel(undefined)).toBeNull() + }) + + it('returns the display name for an llm provider', () => { + const provider: Provider = { + id: 'p1', + name: 'OpenAI GPT-4', + type: 'openai', + kind: 'llm', + } + expect(formatSessionCompletedLabel(provider)).toBe('OpenAI GPT-4') + }) + + it('joins adapter name and model label for an acp target', () => { + const provider: Provider = { + id: 'a1', + name: 'Claude Code · Sonnet 3.5', + type: 'acp', + kind: 'acp', + agentId: 'a1', + adapterName: 'Claude Code', + modelLabel: 'Sonnet 3.5', + } + expect(formatSessionCompletedLabel(provider)).toBe( + 'Claude Code · Sonnet 3.5', + ) + }) + + it('falls back to adapter name when model label is missing for acp', () => { + const provider: Provider = { + id: 'a1', + name: 'Claude Code', + type: 'acp', + kind: 'acp', + agentId: 'a1', + adapterName: 'Claude Code', + } + expect(formatSessionCompletedLabel(provider)).toBe('Claude Code') + }) + + it('falls back to model label when adapter name is missing for acp', () => { + const provider: Provider = { + id: 'a1', + name: 'Sonnet 3.5', + type: 'acp', + kind: 'acp', + agentId: 'a1', + modelLabel: 'Sonnet 3.5', + } + expect(formatSessionCompletedLabel(provider)).toBe('Sonnet 3.5') + }) + + it('falls back to provider.name when both adapter name and model label are missing for acp', () => { + const provider: Provider = { + id: 'a1', + name: 'Some Agent', + type: 'acp', + kind: 'acp', + agentId: 'a1', + } + expect(formatSessionCompletedLabel(provider)).toBe('Some Agent') + }) +}) diff --git a/packages/browseros-agent/apps/agent/entrypoints/sidepanel/index/SessionCompletedFooter.tsx b/packages/browseros-agent/apps/agent/entrypoints/sidepanel/index/SessionCompletedFooter.tsx new file mode 100644 index 000000000..baef22d56 --- /dev/null +++ b/packages/browseros-agent/apps/agent/entrypoints/sidepanel/index/SessionCompletedFooter.tsx @@ -0,0 +1,35 @@ +import type { FC } from 'react' +import type { Provider } from '@/components/chat/chatComponentTypes' + +export function formatSessionCompletedLabel( + provider: Provider | undefined, +): string | null { + if (!provider) return null + if (provider.kind === 'acp') { + if (provider.adapterName && provider.modelLabel) { + return `${provider.adapterName} · ${provider.modelLabel}` + } + return provider.adapterName ?? provider.modelLabel ?? provider.name + } + return provider.name +} + +interface SessionCompletedFooterProps { + provider: Provider | undefined +} + +export const SessionCompletedFooter: FC = ({ + provider, +}) => { + const label = formatSessionCompletedLabel(provider) + if (!label) return null + + return ( + + + Session completed with{' '} + {label} + + + ) +}