scope session search by selected profile (#889)
This commit is contained in:
@@ -71,11 +71,12 @@ export async function fetchHermesSessions(source?: string, limit?: number): Prom
|
|||||||
return res.sessions
|
return res.sessions
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function searchSessions(q: string, source?: string, limit?: number): Promise<SessionSearchResult[]> {
|
export async function searchSessions(q: string, source?: string, limit?: number, profile?: string): Promise<SessionSearchResult[]> {
|
||||||
const params = new URLSearchParams()
|
const params = new URLSearchParams()
|
||||||
params.set('q', q)
|
params.set('q', q)
|
||||||
if (source) params.set('source', source)
|
if (source) params.set('source', source)
|
||||||
if (limit) params.set('limit', String(limit))
|
if (limit) params.set('limit', String(limit))
|
||||||
|
if (profile) params.set('profile', profile)
|
||||||
const query = params.toString()
|
const query = params.toString()
|
||||||
const res = await request<{ results: SessionSearchResult[] }>(`/api/hermes/search/sessions?${query}`)
|
const res = await request<{ results: SessionSearchResult[] }>(`/api/hermes/search/sessions?${query}`)
|
||||||
return res.results
|
return res.results
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ const showRenameModal = ref(false);
|
|||||||
const renameValue = ref("");
|
const renameValue = ref("");
|
||||||
const renameSessionId = ref<string | null>(null);
|
const renameSessionId = ref<string | null>(null);
|
||||||
const renameInputRef = ref<InstanceType<typeof NInput> | null>(null);
|
const renameInputRef = ref<InstanceType<typeof NInput> | null>(null);
|
||||||
const sessionProfileFilter = ref<string | null>(null);
|
const sessionProfileFilter = computed(() => chatStore.sessionProfileFilter);
|
||||||
const profileFilterOptions = computed(() => [
|
const profileFilterOptions = computed(() => [
|
||||||
{ label: t("chat.allProfiles"), value: "__all__" },
|
{ label: t("chat.allProfiles"), value: "__all__" },
|
||||||
...profilesStore.profiles.map((profile) => ({
|
...profilesStore.profiles.map((profile) => ({
|
||||||
@@ -96,8 +96,8 @@ const profileFilterOptions = computed(() => [
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
async function handleProfileFilterChange(value: string) {
|
async function handleProfileFilterChange(value: string) {
|
||||||
sessionProfileFilter.value = value === "__all__" ? null : value;
|
chatStore.sessionProfileFilter = value === "__all__" ? null : value;
|
||||||
await chatStore.loadSessions(sessionProfileFilter.value);
|
await chatStore.loadSessions(chatStore.sessionProfileFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortSessionsWithActiveFirst(items: Session[]): Session[] {
|
function sortSessionsWithActiveFirst(items: Session[]): Session[] {
|
||||||
@@ -311,7 +311,7 @@ async function handleBatchDelete() {
|
|||||||
|
|
||||||
// Remove deleted sessions from local store (without calling API again)
|
// Remove deleted sessions from local store (without calling API again)
|
||||||
// Use loadSessions to refresh from server instead of manual filtering
|
// Use loadSessions to refresh from server instead of manual filtering
|
||||||
await chatStore.loadSessions();
|
await chatStore.loadSessions(chatStore.sessionProfileFilter);
|
||||||
|
|
||||||
message.success(t("chat.batchDeleteSuccess", { count: result.deleted }));
|
message.success(t("chat.batchDeleteSuccess", { count: result.deleted }));
|
||||||
if (result.failed > 0) {
|
if (result.failed > 0) {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const recentSessions = ref<SessionSummary[]>([])
|
|||||||
const searchResults = ref<SessionSearchResult[]>([])
|
const searchResults = ref<SessionSearchResult[]>([])
|
||||||
const activeIndex = ref(0)
|
const activeIndex = ref(0)
|
||||||
const inputRef = ref<InstanceType<typeof NInput> | null>(null)
|
const inputRef = ref<InstanceType<typeof NInput> | null>(null)
|
||||||
|
const profileFilter = computed(() => chatStore.sessionProfileFilter || undefined)
|
||||||
|
|
||||||
let debounceTimer: ReturnType<typeof setTimeout> | null = null
|
let debounceTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
let requestSeq = 0
|
let requestSeq = 0
|
||||||
@@ -79,7 +80,9 @@ async function loadRecentSessions() {
|
|||||||
const seq = ++requestSeq
|
const seq = ++requestSeq
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const sessions = await fetchSessions(undefined, 8)
|
const sessions = profileFilter.value
|
||||||
|
? await fetchSessions(undefined, 8, profileFilter.value)
|
||||||
|
: await fetchSessions(undefined, 8)
|
||||||
if (seq !== requestSeq) return
|
if (seq !== requestSeq) return
|
||||||
recentSessions.value = sessions
|
recentSessions.value = sessions
|
||||||
searchResults.value = []
|
searchResults.value = []
|
||||||
@@ -99,7 +102,9 @@ async function runSearch(text: string) {
|
|||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const results = text.trim()
|
const results = text.trim()
|
||||||
? await searchSessions(text.trim(), undefined, 10)
|
? profileFilter.value
|
||||||
|
? await searchSessions(text.trim(), undefined, 10, profileFilter.value)
|
||||||
|
: await searchSessions(text.trim(), undefined, 10)
|
||||||
: []
|
: []
|
||||||
if (seq !== requestSeq) return
|
if (seq !== requestSeq) return
|
||||||
searchResults.value = results
|
searchResults.value = results
|
||||||
@@ -116,7 +121,7 @@ async function runSearch(text: string) {
|
|||||||
|
|
||||||
async function ensureChatSessionsLoaded() {
|
async function ensureChatSessionsLoaded() {
|
||||||
if (chatStore.sessions.length === 0) {
|
if (chatStore.sessions.length === 0) {
|
||||||
await chatStore.loadSessions()
|
await chatStore.loadSessions(chatStore.sessionProfileFilter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,6 +130,23 @@ async function openItem(item: SearchItem) {
|
|||||||
sessionSearchOpen.value = false
|
sessionSearchOpen.value = false
|
||||||
|
|
||||||
await ensureChatSessionsLoaded()
|
await ensureChatSessionsLoaded()
|
||||||
|
if (!chatStore.sessions.some(session => session.id === item.id) && typeof chatStore.addOrUpdateSession === 'function') {
|
||||||
|
chatStore.addOrUpdateSession({
|
||||||
|
id: item.id,
|
||||||
|
profile: item.profile || 'default',
|
||||||
|
title: item.title || '',
|
||||||
|
source: item.source,
|
||||||
|
messages: [],
|
||||||
|
createdAt: Math.round(item.started_at * 1000),
|
||||||
|
updatedAt: Math.round((item.last_active || item.ended_at || item.started_at) * 1000),
|
||||||
|
model: item.model,
|
||||||
|
provider: item.provider || item.billing_provider || '',
|
||||||
|
messageCount: item.message_count,
|
||||||
|
endedAt: item.ended_at != null ? Math.round(item.ended_at * 1000) : null,
|
||||||
|
lastActiveAt: item.last_active != null ? Math.round(item.last_active * 1000) : undefined,
|
||||||
|
workspace: item.workspace || null,
|
||||||
|
})
|
||||||
|
}
|
||||||
await chatStore.switchSession(item.id, messageId)
|
await chatStore.switchSession(item.id, messageId)
|
||||||
if (router.currentRoute.value.name !== 'hermes.chat') {
|
if (router.currentRoute.value.name !== 'hermes.chat') {
|
||||||
await router.push({ name: 'hermes.chat' })
|
await router.push({ name: 'hermes.chat' })
|
||||||
|
|||||||
@@ -333,6 +333,7 @@ export const useChatStore = defineStore('chat', () => {
|
|||||||
const streamStates = ref<Map<string, { abort: () => void }>>(new Map())
|
const streamStates = ref<Map<string, { abort: () => void }>>(new Map())
|
||||||
/** sessionId → server-reported isWorking status */
|
/** sessionId → server-reported isWorking status */
|
||||||
const serverWorking = ref<Set<string>>(new Set())
|
const serverWorking = ref<Set<string>>(new Set())
|
||||||
|
const sessionProfileFilter = ref<string | null>(null)
|
||||||
/** sessionId → queued message count */
|
/** sessionId → queued message count */
|
||||||
const queueLengths = ref<Map<string, number>>(new Map())
|
const queueLengths = ref<Map<string, number>>(new Map())
|
||||||
/** sessionId → queued user messages not yet visible in the transcript */
|
/** sessionId → queued user messages not yet visible in the transcript */
|
||||||
@@ -1945,6 +1946,7 @@ export const useChatStore = defineStore('chat', () => {
|
|||||||
isStreaming,
|
isStreaming,
|
||||||
isRunActive,
|
isRunActive,
|
||||||
isSessionLive,
|
isSessionLive,
|
||||||
|
sessionProfileFilter,
|
||||||
compressionState,
|
compressionState,
|
||||||
abortState,
|
abortState,
|
||||||
isAborting,
|
isAborting,
|
||||||
|
|||||||
@@ -168,9 +168,16 @@ export async function listHermesSessions(ctx: any) {
|
|||||||
export async function search(ctx: any) {
|
export async function search(ctx: any) {
|
||||||
const q = typeof ctx.query.q === 'string' ? ctx.query.q : ''
|
const q = typeof ctx.query.q === 'string' ? ctx.query.q : ''
|
||||||
const limit = ctx.query.limit ? parseInt(ctx.query.limit as string, 10) : undefined
|
const limit = ctx.query.limit ? parseInt(ctx.query.limit as string, 10) : undefined
|
||||||
const profile = getActiveProfileName()
|
const profile = typeof ctx.query.profile === 'string' && ctx.query.profile.trim()
|
||||||
|
? ctx.query.profile.trim()
|
||||||
|
: undefined
|
||||||
const results = localSearchSessions(profile, q, limit && limit > 0 ? limit : 20)
|
const results = localSearchSessions(profile, q, limit && limit > 0 ? limit : 20)
|
||||||
ctx.body = { results: filterPendingDeletedSessions(results) }
|
const knownProfiles = profile ? null : new Set(listProfileNamesFromDisk())
|
||||||
|
ctx.body = {
|
||||||
|
results: filterPendingDeletedSessions(results.filter(s =>
|
||||||
|
!knownProfiles || knownProfiles.has(s.profile || 'default'),
|
||||||
|
)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function get(ctx: any) {
|
export async function get(ctx: any) {
|
||||||
|
|||||||
@@ -260,11 +260,12 @@ export function listSessions(profile?: string, source?: string, limit = 2000): H
|
|||||||
return rows.map(mapSessionRow)
|
return rows.map(mapSessionRow)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function searchSessions(profile: string, query: string, limit = 20): HermesSessionSearchRow[] {
|
export function searchSessions(profile: string | null | undefined, query: string, limit = 20): HermesSessionSearchRow[] {
|
||||||
if (!isSqliteAvailable()) return []
|
if (!isSqliteAvailable()) return []
|
||||||
|
const profileFilter = profile?.trim()
|
||||||
const trimmed = query.trim()
|
const trimmed = query.trim()
|
||||||
if (!trimmed) {
|
if (!trimmed) {
|
||||||
return listSessions(profile, undefined, limit).map(s => ({ ...s, snippet: s.preview || '', matched_message_id: null }))
|
return listSessions(profileFilter, undefined, limit).map(s => ({ ...s, snippet: s.preview || '', matched_message_id: null }))
|
||||||
}
|
}
|
||||||
const db = getDb()!
|
const db = getDb()!
|
||||||
const lowered = trimmed.toLowerCase()
|
const lowered = trimmed.toLowerCase()
|
||||||
@@ -273,12 +274,21 @@ export function searchSessions(profile: string, query: string, limit = 20): Herm
|
|||||||
// Step 1: Find matching sessions
|
// Step 1: Find matching sessions
|
||||||
const sessionRows = db.prepare(
|
const sessionRows = db.prepare(
|
||||||
`SELECT * FROM ${SESSIONS_TABLE}
|
`SELECT * FROM ${SESSIONS_TABLE}
|
||||||
WHERE profile = ? AND (
|
WHERE 1 = 1
|
||||||
|
${profileFilter ? 'AND profile = ?' : ''}
|
||||||
|
AND (
|
||||||
LOWER(title) LIKE ? OR LOWER(preview) LIKE ?
|
LOWER(title) LIKE ? OR LOWER(preview) LIKE ?
|
||||||
OR id IN (SELECT DISTINCT session_id FROM ${MESSAGES_TABLE} WHERE LOWER(content) LIKE ? OR LOWER(COALESCE(tool_name, '')) LIKE ?)
|
OR id IN (SELECT DISTINCT session_id FROM ${MESSAGES_TABLE} WHERE LOWER(content) LIKE ? OR LOWER(COALESCE(tool_name, '')) LIKE ?)
|
||||||
)
|
)
|
||||||
ORDER BY last_active DESC LIMIT ?`,
|
ORDER BY last_active DESC LIMIT ?`,
|
||||||
).all(profile, pattern, pattern, pattern, pattern, limit) as Record<string, unknown>[]
|
).all(...[
|
||||||
|
...(profileFilter ? [profileFilter] : []),
|
||||||
|
pattern,
|
||||||
|
pattern,
|
||||||
|
pattern,
|
||||||
|
pattern,
|
||||||
|
limit,
|
||||||
|
]) as Record<string, unknown>[]
|
||||||
|
|
||||||
if (sessionRows.length === 0) return []
|
if (sessionRows.length === 0) return []
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user