mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-13 09:01:54 +00:00
Show platform sources in desktop sessions
This commit is contained in:
parent
1c68f6f81f
commit
9d6992ee8a
5 changed files with 97 additions and 5 deletions
|
|
@ -2,12 +2,14 @@ import { useStore } from '@nanostores/react'
|
|||
import type * as React from 'react'
|
||||
|
||||
import { writeSessionDrag } from '@/app/chat/composer/inline-refs'
|
||||
import { PlatformAvatar } from '@/app/messaging/platform-icon'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Codicon } from '@/components/ui/codicon'
|
||||
import type { SessionInfo } from '@/hermes'
|
||||
import { type Translations, useI18n } from '@/i18n'
|
||||
import { sessionTitle } from '@/lib/chat-runtime'
|
||||
import { triggerHaptic } from '@/lib/haptics'
|
||||
import { normalizeSessionSource, sessionSourceLabel } from '@/lib/session-source'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { $attentionSessionIds } from '@/store/session'
|
||||
|
||||
|
|
@ -66,6 +68,9 @@ export function SidebarSessionRow({
|
|||
const r = t.sidebar.row
|
||||
const title = sessionTitle(session)
|
||||
const age = formatAge(session.last_active || session.started_at, r)
|
||||
const sourceId = normalizeSessionSource(session.source)
|
||||
const sourceLabel = sessionSourceLabel(sourceId)
|
||||
const showSource = Boolean(sourceId && sourceLabel && !['desktop', 'local', 'tui'].includes(sourceId))
|
||||
const handleLabel = `Reorder ${title}`
|
||||
// Subscribe per-row (the leaf) instead of drilling a set through the list —
|
||||
// the atom is tiny and rarely non-empty. True when a clarify prompt in this
|
||||
|
|
@ -176,12 +181,25 @@ export function SidebarSessionRow({
|
|||
needsInput ? 'overflow-visible' : 'overflow-hidden'
|
||||
)}
|
||||
>
|
||||
<SidebarRowDot isWorking={isWorking} needsInput={needsInput} />
|
||||
</span>
|
||||
<SidebarRowDot isWorking={isWorking} needsInput={needsInput} />
|
||||
</span>
|
||||
)}
|
||||
<span className="min-w-0 flex-1 truncate text-[0.8125rem] font-normal text-(--ui-text-secondary) group-hover:text-foreground group-data-[working=true]:text-foreground/90">
|
||||
{title}
|
||||
</span>
|
||||
{showSource && sourceId && sourceLabel && (
|
||||
<span
|
||||
className="hidden shrink-0 items-center gap-1 rounded-[4px] bg-(--ui-bg-tertiary) px-1.5 py-0.5 text-[0.625rem] leading-none text-(--ui-text-tertiary) sm:inline-flex"
|
||||
title={`${sourceLabel} session`}
|
||||
>
|
||||
<PlatformAvatar
|
||||
className="size-3.5 rounded-[3px] text-[0.5rem] [&_svg]:size-2.5"
|
||||
platformId={sourceId}
|
||||
platformName={sourceLabel}
|
||||
/>
|
||||
<span className="max-w-16 truncate">{sourceLabel}</span>
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
<div className="relative z-2 grid w-[1.375rem] place-items-center">
|
||||
{!isWorking && (
|
||||
|
|
|
|||
|
|
@ -28,15 +28,17 @@ import { cn } from '@/lib/utils'
|
|||
type IconKind = 'brand' | 'generic'
|
||||
|
||||
interface PlatformIconSpec {
|
||||
Icon: ComponentType<SVGProps<SVGSVGElement>>
|
||||
Icon?: ComponentType<SVGProps<SVGSVGElement>>
|
||||
color: string
|
||||
kind: IconKind
|
||||
monogram?: string
|
||||
}
|
||||
|
||||
const PLATFORM_ICONS: Record<string, PlatformIconSpec> = {
|
||||
telegram: { Icon: SiTelegram, color: '#26A5E4', kind: 'brand' },
|
||||
discord: { Icon: SiDiscord, color: '#5865F2', kind: 'brand' },
|
||||
// Slack removed from Simple Icons by Salesforce request — letter monogram.
|
||||
slack: { color: '#4A154B', kind: 'brand', monogram: 'S' },
|
||||
mattermost: { Icon: SiMattermost, color: '#0058CC', kind: 'brand' },
|
||||
matrix: { Icon: SiMatrix, color: '#000000', kind: 'brand' },
|
||||
signal: { Icon: SiSignal, color: '#3A76F0', kind: 'brand' },
|
||||
|
|
@ -87,7 +89,7 @@ export function PlatformAvatar({ className, platformId, platformName }: Platform
|
|||
color
|
||||
}}
|
||||
>
|
||||
<Icon className="size-3.5" />
|
||||
{Icon ? <Icon className="size-3.5" /> : spec.monogram || platformName.charAt(0).toUpperCase()}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,14 @@ describe('sessionMatchesSearch', () => {
|
|||
expect(sessionMatchesSearch(session, 'hermes-agent')).toBe(true)
|
||||
})
|
||||
|
||||
it('matches sessions by source platform and aliases', () => {
|
||||
expect(sessionMatchesSearch(makeSession({ source: 'telegram' }), 'Telegram')).toBe(true)
|
||||
expect(sessionMatchesSearch(makeSession({ source: 'whatsapp' }), 'WhatsApp')).toBe(true)
|
||||
expect(sessionMatchesSearch(makeSession({ source: 'whatsapp' }), 'wa')).toBe(true)
|
||||
expect(sessionMatchesSearch(makeSession({ source: 'slack' }), 'slack')).toBe(true)
|
||||
expect(sessionMatchesSearch(makeSession({ source: 'bluebubbles' }), 'imessage')).toBe(true)
|
||||
})
|
||||
|
||||
it('does not match unrelated queries', () => {
|
||||
expect(sessionMatchesSearch(makeSession(), 'totally-unrelated')).toBe(false)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import type { SessionInfo } from '@/types/hermes'
|
||||
|
||||
import { sessionTitle } from './chat-runtime'
|
||||
import { sessionSourceSearchTerms } from './session-source'
|
||||
|
||||
export function sessionMatchesSearch(session: SessionInfo, query: string): boolean {
|
||||
const needle = query.trim().toLowerCase()
|
||||
|
|
@ -14,6 +15,7 @@ export function sessionMatchesSearch(session: SessionInfo, query: string): boole
|
|||
session._lineage_root_id ?? '',
|
||||
sessionTitle(session),
|
||||
session.preview ?? '',
|
||||
session.cwd ?? ''
|
||||
session.cwd ?? '',
|
||||
...sessionSourceSearchTerms(session.source)
|
||||
].some(value => value.toLowerCase().includes(needle))
|
||||
}
|
||||
|
|
|
|||
62
apps/desktop/src/lib/session-source.ts
Normal file
62
apps/desktop/src/lib/session-source.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
const SOURCE_LABELS: Record<string, string> = {
|
||||
api_server: 'API',
|
||||
bluebubbles: 'iMessage',
|
||||
cli: 'CLI',
|
||||
codex: 'Codex',
|
||||
desktop: 'Desktop',
|
||||
discord: 'Discord',
|
||||
email: 'Email',
|
||||
gateway: 'Gateway',
|
||||
local: 'Local',
|
||||
matrix: 'Matrix',
|
||||
mattermost: 'Mattermost',
|
||||
qqbot: 'QQ',
|
||||
signal: 'Signal',
|
||||
slack: 'Slack',
|
||||
sms: 'SMS',
|
||||
telegram: 'Telegram',
|
||||
tui: 'TUI',
|
||||
webhook: 'Webhook',
|
||||
weixin: 'WeChat',
|
||||
whatsapp: 'WhatsApp',
|
||||
yuanbao: 'Yuanbao'
|
||||
}
|
||||
|
||||
const SOURCE_ALIASES: Record<string, string[]> = {
|
||||
bluebubbles: ['apple messages', 'imessage'],
|
||||
cli: ['terminal'],
|
||||
desktop: ['app', 'gui'],
|
||||
local: ['machine'],
|
||||
qqbot: ['qq'],
|
||||
telegram: ['tg'],
|
||||
tui: ['terminal'],
|
||||
weixin: ['wechat'],
|
||||
whatsapp: ['wa']
|
||||
}
|
||||
|
||||
export function normalizeSessionSource(source: null | string | undefined): string | null {
|
||||
const id = source?.trim().toLowerCase()
|
||||
|
||||
return id || null
|
||||
}
|
||||
|
||||
export function sessionSourceLabel(source: null | string | undefined): string | null {
|
||||
const id = normalizeSessionSource(source)
|
||||
|
||||
if (!id) {
|
||||
return null
|
||||
}
|
||||
|
||||
return SOURCE_LABELS[id] || id.replace(/[_-]+/g, ' ').replace(/\b\w/g, char => char.toUpperCase())
|
||||
}
|
||||
|
||||
export function sessionSourceSearchTerms(source: null | string | undefined): string[] {
|
||||
const id = normalizeSessionSource(source)
|
||||
const label = sessionSourceLabel(id)
|
||||
|
||||
if (!id) {
|
||||
return []
|
||||
}
|
||||
|
||||
return [id, label ?? '', ...(SOURCE_ALIASES[id] ?? [])].filter(Boolean)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue