feat(chat): add direct Live badge and harden Live monitor backend (#138)

* feat(chat): add direct live badge to session rows

* fix(live): use session DB for conversations monitor

* docs: add chat vs live monitor direction plan

* fix(search): avoid numeric session search 500 without FTS table
This commit is contained in:
Zhicheng Han
2026-04-23 04:49:00 +02:00
committed by GitHub
parent 32dc084b66
commit 5f40ae6258
12 changed files with 1435 additions and 24 deletions
@@ -605,6 +605,8 @@ async function handleRenameConfirm() {
:deep(.session-item-title) {
display: block;
flex: 1 1 auto;
min-width: 0;
font-size: 13px;
white-space: nowrap;
overflow: hidden;
@@ -624,6 +626,25 @@ async function handleRenameConfirm() {
filter: drop-shadow(0 0 6px rgba(var(--accent-primary-rgb), 0.35));
}
:deep(.session-item-live-badge) {
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
padding: 0 8px;
min-height: 20px;
border-radius: 999px;
font-size: 11px;
line-height: 20px;
font-weight: 700;
letter-spacing: 0.05em;
text-transform: uppercase;
color: $accent-primary;
background: rgba(var(--accent-primary-rgb), 0.18);
border: 1px solid rgba(var(--accent-primary-rgb), 0.34);
box-shadow: 0 0 0 1px rgba(var(--accent-primary-rgb), 0.06), 0 0 10px rgba(var(--accent-primary-rgb), 0.14);
}
:deep(.session-item-pin) {
display: inline-flex;
align-items: center;
@@ -45,6 +45,7 @@ const { t } = useI18n()
</svg>
</span>
<span class="session-item-title">{{ session.title }}</span>
<span v-if="live" class="session-item-live-badge">{{ t('chat.liveMode') }}</span>
</span>
<span class="session-item-meta">
<span v-if="session.model" class="session-item-model">{{ session.model }}</span>
+10 -1
View File
@@ -40,6 +40,8 @@ export interface Session {
messageCount?: number
inputTokens?: number
outputTokens?: number
endedAt?: number | null
lastActiveAt?: number
}
function uid(): string {
@@ -155,6 +157,8 @@ function mapHermesSession(s: SessionSummary): Session {
model: s.model,
provider: (s as any).billing_provider || '',
messageCount: s.message_count,
endedAt: s.ended_at != null ? Math.round(s.ended_at * 1000) : null,
lastActiveAt: s.last_active != null ? Math.round(s.last_active * 1000) : undefined,
}
}
@@ -169,6 +173,7 @@ const LEGACY_SESSIONS_CACHE_KEY = 'hermes_sessions_cache_v1'
const IN_FLIGHT_TTL_MS = 15 * 60 * 1000 // Give up after 15 minutes
const POLL_INTERVAL_MS = 2000
const POLL_STABLE_EXITS = 3 // 3 × 2s = 6s of no change → assume run finished
const LIVE_BADGE_WINDOW_MS = 5 * 60 * 1000
// 获取当前 profile 名称,用于隔离缓存。
// 从 profiles store 的 activeProfileName(同步 localStorage)读取,
@@ -324,7 +329,11 @@ export const useChatStore = defineStore('chat', () => {
const messages = computed<Message[]>(() => activeSession.value?.messages || [])
function isSessionLive(sessionId: string): boolean {
return streamStates.value.has(sessionId) || resumingRuns.value.has(sessionId)
if (streamStates.value.has(sessionId) || resumingRuns.value.has(sessionId)) return true
const session = sessions.value.find(candidate => candidate.id === sessionId)
if (!session?.lastActiveAt || session.endedAt != null) return false
return Date.now() - session.lastActiveAt <= LIVE_BADGE_WINDOW_MS
}
function persistSessionsList() {