diff --git a/apps/cli/src/http/routes/web/session-index.ts b/apps/cli/src/http/routes/web/session-index.ts index 16a03693..df8902de 100644 --- a/apps/cli/src/http/routes/web/session-index.ts +++ b/apps/cli/src/http/routes/web/session-index.ts @@ -2,6 +2,10 @@ import type { FastifyInstance } from 'fastify' import { sessionIndexService, type SessionRuntimeAdapter } from '@openchatlab/node-runtime' export function registerSessionIndexRoutes(server: FastifyInstance, adapter: SessionRuntimeAdapter): void { + server.get('/_web/sessions/index-stats', async () => { + return sessionIndexService.getAllIndexStats(adapter) + }) + server.post<{ Params: { id: string } Body: { gapThreshold?: number } diff --git a/apps/desktop/main/ipc/chat.ts b/apps/desktop/main/ipc/chat.ts index 4f8b4901..62bafe4c 100644 --- a/apps/desktop/main/ipc/chat.ts +++ b/apps/desktop/main/ipc/chat.ts @@ -836,6 +836,15 @@ export function registerChatHandlers(ctx: IpcContext): void { } }) + ipcMain.handle('session:getAllIndexStats', async () => { + try { + return await worker.getAllIndexStats() + } catch (error) { + console.error('Failed to get all index stats:', error) + return [] + } + }) + /** * 清空会话索引 */ diff --git a/apps/desktop/main/worker/dbWorker.ts b/apps/desktop/main/worker/dbWorker.ts index 191401f0..cb38a548 100644 --- a/apps/desktop/main/worker/dbWorker.ts +++ b/apps/desktop/main/worker/dbWorker.ts @@ -60,6 +60,7 @@ import { clearSessions, hasSessionIndex, getSessionStats, + getAllIndexStats, updateSessionGapThreshold, getSessions, getSessionsByTimeRange, @@ -228,6 +229,7 @@ const syncHandlers: Record any> = { clearSessions: (p) => clearSessions(p.sessionId), hasSessionIndex: (p) => hasSessionIndex(p.sessionId), getSessionStats: (p) => getSessionStats(p.sessionId), + getAllIndexStats: (p) => getAllIndexStats(p.sessionIds), updateSessionGapThreshold: (p) => updateSessionGapThreshold(p.sessionId, p.gapThreshold), getSessions: (p) => getSessions(p.sessionId), getSessionsByTimeRange: (p) => getSessionsByTimeRange(p.sessionId, p.startTs, p.endTs), diff --git a/apps/desktop/main/worker/query/session/index.ts b/apps/desktop/main/worker/query/session/index.ts index 8a0fd337..21a3b887 100644 --- a/apps/desktop/main/worker/query/session/index.ts +++ b/apps/desktop/main/worker/query/session/index.ts @@ -27,6 +27,7 @@ export { clearSessions, hasSessionIndex, getSessionStats, + getAllIndexStats, updateSessionGapThreshold, getSessions, getSessionsByTimeRange, diff --git a/apps/desktop/main/worker/query/session/sessionIndex.ts b/apps/desktop/main/worker/query/session/sessionIndex.ts index c0451c36..bc4c3f15 100644 --- a/apps/desktop/main/worker/query/session/sessionIndex.ts +++ b/apps/desktop/main/worker/query/session/sessionIndex.ts @@ -82,6 +82,19 @@ export function getSessionStats(sessionId: string): SessionIndexStats { }) } +export interface SessionIndexStatusItem { + sessionId: string + hasIndex: boolean + sessionCount: number +} + +export function getAllIndexStats(sessionIds: string[]): SessionIndexStatusItem[] { + return sessionIds.map((sessionId) => { + const stats = getSessionStats(sessionId) + return { sessionId, hasIndex: stats.hasIndex, sessionCount: stats.sessionCount } + }) +} + export function updateSessionGapThreshold(sessionId: string, gapThreshold: number | null): void { withWritableAdapter(sessionId, (adapter) => coreUpdateSessionGapThreshold(adapter, gapThreshold)) } diff --git a/apps/desktop/main/worker/workerManager.ts b/apps/desktop/main/worker/workerManager.ts index 0b580e7d..0b8afd33 100644 --- a/apps/desktop/main/worker/workerManager.ts +++ b/apps/desktop/main/worker/workerManager.ts @@ -746,6 +746,14 @@ export async function getSessionStats(sessionId: string): Promise return sendToWorker('getSessionStats', { sessionId }) } +export async function getAllIndexStats(): Promise< + Array<{ sessionId: string; hasIndex: boolean; sessionCount: number }> +> { + const sessions = await getAllSessions() + const sessionIds = sessions.map((s: any) => s.id) + return sendToWorker('getAllIndexStats', { sessionIds }) +} + /** * 更新单个聊天的会话切分阈值 */ diff --git a/apps/desktop/preload/apis/utils.ts b/apps/desktop/preload/apis/utils.ts index f53e4ac2..ffd69757 100644 --- a/apps/desktop/preload/apis/utils.ts +++ b/apps/desktop/preload/apis/utils.ts @@ -256,6 +256,13 @@ export const sessionApi = { return ipcRenderer.invoke('session:getStats', sessionId) }, + /** + * 批量获取所有会话的索引状态 + */ + getAllIndexStats: (): Promise> => { + return ipcRenderer.invoke('session:getAllIndexStats') + }, + /** * 清空会话索引 */ diff --git a/apps/desktop/preload/index.d.ts b/apps/desktop/preload/index.d.ts index 640ec4da..35685624 100644 --- a/apps/desktop/preload/index.d.ts +++ b/apps/desktop/preload/index.d.ts @@ -1128,6 +1128,7 @@ interface SessionApi { generateIncremental: (sessionId: string, gapThreshold?: number) => Promise hasIndex: (sessionId: string) => Promise getStats: (sessionId: string) => Promise + getAllIndexStats: () => Promise> clear: (sessionId: string) => Promise updateGapThreshold: (sessionId: string, gapThreshold: number | null) => Promise getSessions: (sessionId: string) => Promise diff --git a/packages/node-runtime/src/services/index.ts b/packages/node-runtime/src/services/index.ts index a037c95a..7d56e9fb 100644 --- a/packages/node-runtime/src/services/index.ts +++ b/packages/node-runtime/src/services/index.ts @@ -31,7 +31,9 @@ export { getFtsStatus, searchFts, rebuildFts, + getAllIndexStats, } from './session-index-service' +export type { SessionIndexStatusItem } from './session-index-service' // Summary service export { generateSummary, generateAllSummaries } from './summary-service' diff --git a/packages/node-runtime/src/services/session-index-service.ts b/packages/node-runtime/src/services/session-index-service.ts index 9b2adcd3..6b91b523 100644 --- a/packages/node-runtime/src/services/session-index-service.ts +++ b/packages/node-runtime/src/services/session-index-service.ts @@ -8,6 +8,7 @@ import { generateSessionIndex as coreGenerateSessionIndex, generateIncrementalSessionIndex as coreGenerateIncrementalSessionIndex, clearSessionIndex as coreClearSessionIndex, + getSessionIndexStats, } from '@openchatlab/core' import { hasFtsTable, searchByFts, rebuildFtsIndex } from '../fts' import type { SessionRuntimeAdapter } from './adapters' @@ -51,3 +52,22 @@ export function rebuildFts(adapter: SessionRuntimeAdapter, sessionId: string) { const db = adapter.ensureWritable(sessionId) return rebuildFtsIndex(db) } + +export interface SessionIndexStatusItem { + sessionId: string + hasIndex: boolean + sessionCount: number +} + +export function getAllIndexStats(adapter: SessionRuntimeAdapter): SessionIndexStatusItem[] { + const ids = adapter.listSessionIds() + return ids.map((sessionId) => { + try { + const db = adapter.ensureReadonly(sessionId) + const stats = getSessionIndexStats(db) + return { sessionId, hasIndex: stats.hasIndex, sessionCount: stats.sessionCount } + } catch { + return { sessionId, hasIndex: false, sessionCount: 0 } + } + }) +} diff --git a/src/components/AIChat/chat/ChatStatusBar.vue b/src/components/AIChat/chat/ChatStatusBar.vue index a32119e9..c67bee95 100644 --- a/src/components/AIChat/chat/ChatStatusBar.vue +++ b/src/components/AIChat/chat/ChatStatusBar.vue @@ -27,10 +27,8 @@ const props = defineProps<{ const promptStore = usePromptStore() const llmStore = useLLMStore() const { aiGlobalSettings } = storeToRefs(promptStore) -const { configs, defaultAssistantConfig, isLoading: isLoadingLLM } = storeToRefs(llmStore) +const { defaultAssistantConfig, isLoading: isLoadingLLM } = storeToRefs(llmStore) -// 下拉菜单状态 -const isModelPopoverOpen = ref(false) const isOpeningLog = ref(false) const agentPhaseText = computed(() => { @@ -121,20 +119,8 @@ function openChatSettings() { layoutStore.openSettings('ai', 'chat') } -// 切换 AI 模型配置 -async function switchModelConfig(configId: string) { - const config = llmStore.configs.find((c) => c.id === configId) - const success = await llmStore.setDefaultAssistantModel(configId, config?.model || '') - if (success) { - isModelPopoverOpen.value = false - } else { - toast.fail(t('ai.chat.statusBar.model.switchFailed')) - } -} - function openModelSettings() { - isModelPopoverOpen.value = false - layoutStore.openSettings('ai', 'model') + layoutStore.openSettings('ai', 'defaultModel') } // 导出当前对话 @@ -258,71 +244,21 @@ const thinkingLevelLabel = computed(() => {
- - - - + diff --git a/src/components/common/Settings/AI/SessionIndexSection.vue b/src/components/common/Settings/AI/SessionIndexSection.vue index 2dd3b611..341d3112 100644 --- a/src/components/common/Settings/AI/SessionIndexSection.vue +++ b/src/components/common/Settings/AI/SessionIndexSection.vue @@ -3,7 +3,7 @@ * 会话索引管理区块 * 配置会话索引阈值和批量生成功能 */ -import { ref, onMounted, computed } from 'vue' +import { ref, onMounted, onUnmounted, computed } from 'vue' import { useI18n } from 'vue-i18n' import UITabs from '@/components/UI/Tabs.vue' import { useDataService, useSessionIndexService } from '@/services' @@ -72,35 +72,25 @@ function onSummaryStrategyChange(val: string | number) { patchUiConfig({ summary_strategy: val as SummaryStrategy }) } -// 加载所有会话的索引状态 +// 加载所有会话的索引状态(单次批量请求) async function loadSessionIndexStatus() { isLoadingSessionStatus.value = true try { - // 获取所有会话 - const sessions = await useDataService().getSessions() + const [sessions, indexStats] = await Promise.all([ + useDataService().getSessions(), + useSessionIndexService().getAllIndexStats(), + ]) - // 获取每个会话的索引状态 - const statusList: SessionIndexStatus[] = [] - for (const session of sessions) { - try { - const stats = await useSessionIndexService().getStats(session.id) - statusList.push({ - id: session.id, - name: session.name, - hasIndex: stats.hasIndex, - sessionCount: stats.sessionCount, - }) - } catch { - statusList.push({ - id: session.id, - name: session.name, - hasIndex: false, - sessionCount: 0, - }) + const statsMap = new Map(indexStats.map((s) => [s.sessionId, s])) + allSessionsStatus.value = sessions.map((session) => { + const stats = statsMap.get(session.id) + return { + id: session.id, + name: session.name, + hasIndex: stats?.hasIndex ?? false, + sessionCount: stats?.sessionCount ?? 0, } - } - - allSessionsStatus.value = statusList + }) } catch (error) { console.error('加载会话索引状态失败:', error) } finally { @@ -189,16 +179,35 @@ async function batchRegenerateAll() { isBatchGenerating.value = false } -// 组件挂载时加载数据 +// 使用 IntersectionObserver 实现懒加载:仅当组件进入视口时才加载会话状态 +const sectionRef = ref(null) +let observer: IntersectionObserver | null = null +let hasLoadedStatus = false + onMounted(() => { loadSessionThreshold() summaryStrategy.value = getSummaryStrategy() - loadSessionIndexStatus() + + observer = new IntersectionObserver( + (entries) => { + if (entries[0]?.isIntersecting && !hasLoadedStatus) { + hasLoadedStatus = true + loadSessionIndexStatus() + observer?.disconnect() + } + }, + { threshold: 0.1 } + ) + if (sectionRef.value) observer.observe(sectionRef.value) +}) + +onUnmounted(() => { + observer?.disconnect() })