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:
@@ -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>
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user