refactor(adapters): replace window.chatApi calls with QueryAdapter

Phase 3.0.3: Systematically replace all data-query window.chatApi
calls across 24 files with getAdapter() from the new adapter layer.

Replaced categories:
- Session CRUD (getSessions, getSession, deleteSession, renameSession)
- Statistics (getMemberActivity, getHourlyActivity, getDailyActivity,
  getWeekdayActivity, getMessageTypeDistribution, getAvailableYears)
- Member management (getMembers, getMembersPaginated, updateMemberAliases,
  mergeMembers, deleteMember, getMemberNameHistory)
- Social analysis (getMentionAnalysis, getLaughAnalysis,
  getCatchphraseAnalysis, getLanguagePreferenceAnalysis,
  getRelationshipStats)
- SQL Lab (executeSQL, getSchema)

Remaining window.chatApi calls are Electron-specific operations
(import, migration, file dialog, demo, export) that will be handled
separately with IS_ELECTRON guards in future phases.

Also adds initAdapter() call in App.vue onMounted to bootstrap the
adapter before any data loading occurs.
This commit is contained in:
digua committed 2026-05-13 19:46:41 +08:00
1 parent f6b3dabc31
commit 9ac4276465
24 files changed
+69 -45

No files matched your search

+3
View File
@@ -13,6 +13,7 @@ import { useSessionStore } from '@/stores/session'
import { useLayoutStore } from '@/stores/layout'
import { useSettingsStore } from '@/stores/settings'
import { useLLMStore } from '@/stores/llm'
import { initAdapter } from '@/adapters'
const { t } = useI18n()
@@ -58,6 +59,8 @@ onMounted(async () => {
// 初始化语言设置(同步 i18n 和 dayjs,异步加载脱敏规则)
await settingsStore.initLocale()
// 初始化数据访问适配器(Electron / Web 自动选择)
await initAdapter()
// 初始化 LLM 配置(预加载,避免首次使用时延迟)
llmStore.init()
// 从数据库加载会话列表
+2 -1
View File
@@ -7,6 +7,7 @@ import SlashCommandMenu from './SlashCommandMenu.vue'
import { useSkillStore, type SkillSummary } from '@/stores/skill'
import type { MentionedMemberContext } from '@/composables/useAIChat'
import type { MemberWithStats } from '@/types/analysis'
import { getAdapter } from '@/adapters'
const { t } = useI18n()
@@ -157,7 +158,7 @@ async function loadMentionMembers() {
isLoadingMentionMembers.value = true
try {
const members = await window.chatApi.getMembers(props.sessionId)
const members = await getAdapter().getMembers(props.sessionId)
mentionMembers.value = [...members].sort((a, b) => b.messageCount - a.messageCount)
} catch (error) {
console.error('加载 AI @ 成员列表失败:', error)
+3 -2
View File
@@ -2,6 +2,7 @@
import { ref, computed, watch, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import type { TableSchema, SQLResult } from '@/components/analysis/SQLLab'
import { getAdapter } from '@/adapters'
import { getColumnLabel } from '@/components/analysis/SQLLab'
import type { LocaleType } from '@/i18n/types'
import { UITabs } from '@/components/UI'
@@ -95,7 +96,7 @@ async function runSQL(sql: string): Promise<SQLResult> {
if (dbSource.value === 'ai') {
return await window.aiApi.executeAiSQL(sql)
}
return await window.chatApi.executeSQL(props.sessionId, sql)
return await getAdapter().executeSQL(props.sessionId, sql)
}
async function loadSchema() {
@@ -103,7 +104,7 @@ async function loadSchema() {
if (dbSource.value === 'ai') {
schema.value = await window.aiApi.getAiSchema()
} else {
schema.value = await window.chatApi.getSchema(props.sessionId)
schema.value = await getAdapter().getSchema(props.sessionId)
}
if (sortedSchema.value.length > 0) {
const defaultTable = isAiDb.value ? 'ai_message' : sortedSchema.value[0].name
@@ -8,6 +8,7 @@ import { ref, computed, onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useSessionStore } from '@/stores/session'
import type { MemberWithStats } from '@/types/analysis'
import { getAdapter } from '@/adapters'
import Tabs from '@/components/UI/Tabs.vue'
const { t } = useI18n()
@@ -66,7 +67,7 @@ async function loadMembers() {
isLoadingMembers.value = true
try {
members.value = await window.chatApi.getMembers(sessionId)
members.value = await getAdapter().getMembers(sessionId)
} catch (error) {
console.error('加载成员失败:', error)
} finally {
@@ -80,7 +81,7 @@ async function loadTimeRange() {
if (!sessionId) return
try {
const range = await window.chatApi.getTimeRange(sessionId)
const range = await getAdapter().getTimeRange(sessionId)
if (range) {
dbTimeRange.value = range
}
@@ -2,6 +2,7 @@
import { computed, ref, watch, onMounted, onUnmounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { useDark } from '@vueuse/core'
import { getAdapter } from '@/adapters'
import * as echarts from 'echarts/core'
import { HeatmapChart, CustomChart } from 'echarts/charts'
import { CalendarComponent, TooltipComponent, VisualMapComponent } from 'echarts/components'
@@ -37,7 +38,7 @@ const weekdayActivity = ref<WeekdayActivity[]>([])
async function loadWeekdayActivity() {
if (!props.session.id) return
try {
weekdayActivity.value = await window.chatApi.getWeekdayActivity(props.session.id, props.timeFilter)
weekdayActivity.value = await getAdapter().getWeekdayActivity(props.session.id, props.timeFilter)
} catch (error) {
console.error('加载星期活跃度失败:', error)
}
@@ -2,6 +2,7 @@
import { ref, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import type { TableSchema } from './types'
import { getAdapter } from '@/adapters'
import { getTableLabel, getColumnLabel } from './types'
import type { LocaleType } from '@/i18n/types'
@@ -25,7 +26,7 @@ const expandedTables = ref<Set<string>>(new Set())
// 加载 Schema
async function loadSchema() {
try {
schema.value = await window.chatApi.getSchema(props.sessionId)
schema.value = await getAdapter().getSchema(props.sessionId)
// 默认展开所有表
schema.value.forEach((table) => expandedTables.value.add(table.name))
} catch (err) {
+2 -1
View File
@@ -3,6 +3,7 @@ import { ref, onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { SchemaPanel, AIHistoryModal, ResultTable, getTableLabel, getColumnLabel } from './SQLLab'
import type { AIHistory, SQLResult, TableSchema } from './SQLLab'
import { getAdapter } from '@/adapters'
const { t, locale } = useI18n()
@@ -97,7 +98,7 @@ async function executeSQL() {
resultTableRef.value?.resetSort()
try {
result.value = await window.chatApi.executeSQL(props.sessionId, sql.value)
result.value = await getAdapter().executeSQL(props.sessionId, sql.value)
} catch (err: any) {
error.value = err.message || String(err)
} finally {
@@ -2,6 +2,7 @@
import { ref, watch, computed, defineAsyncComponent } from 'vue'
import { useI18n } from 'vue-i18n'
import type { CatchphraseAnalysis } from '@/types/analysis'
import { getAdapter } from '@/adapters'
const EChartWordcloud = defineAsyncComponent(() => import('@/components/charts/EChartWordcloud.vue'))
import type { EChartWordcloudData } from '@/components/charts'
import { LoadingState, EmptyState, UITabs } from '@/components/UI'
@@ -96,7 +97,7 @@ async function loadCatchphraseAnalysis() {
if (!props.sessionId) return
isLoading.value = true
try {
catchphraseAnalysis.value = await window.chatApi.getCatchphraseAnalysis(props.sessionId, props.timeFilter)
catchphraseAnalysis.value = await getAdapter().getCatchphraseAnalysis(props.sessionId, props.timeFilter)
console.log(
'[CatchphraseTab] API result: members:',
catchphraseAnalysis.value?.members.length,
@@ -2,6 +2,7 @@
import { computed, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import type { LaughAnalysis, KeywordTemplate as BaseKeywordTemplate } from '@/types/analysis'
import { getAdapter } from '@/adapters'
import { ListPro } from '@/components/charts'
import type { RankItem } from '@/components/charts'
import { LoadingState } from '@/components/UI'
@@ -304,9 +305,7 @@ async function loadAnalysis() {
isLoading.value = true
try {
analysis.value = await window.chatApi.getLaughAnalysis(props.sessionId, props.timeFilter, [
...currentKeywords.value,
])
analysis.value = await getAdapter().getLaughAnalysis(props.sessionId, props.timeFilter, [...currentKeywords.value])
} catch (error) {
console.error('加载词频分析失败:', error)
analysis.value = null
@@ -2,6 +2,7 @@
import { ref, watch, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import type { LanguagePreferenceResult } from '@/types/quotes/languagePreference'
import { getAdapter } from '@/adapters'
import { LoadingState, EmptyState } from '@/components/UI'
import { useLayoutStore } from '@/stores/layout'
import LanguagePreferenceCard from './LanguagePreferenceCard.vue'
@@ -28,7 +29,7 @@ async function loadData() {
if (!props.sessionId) return
isLoading.value = true
try {
data.value = await window.chatApi.getLanguagePreferenceAnalysis(props.sessionId, locale.value, props.timeFilter)
data.value = await getAdapter().getLanguagePreferenceAnalysis(props.sessionId, locale.value, props.timeFilter)
} catch (error) {
console.error('[LanguagePreferenceTab] Failed to load:', error)
} finally {
@@ -5,6 +5,7 @@ import { ThemeCard } from '@/components/UI'
import { useSettingsStore } from '@/stores/settings'
import type { SharedWord } from '@/types/quotes/languagePreference'
import type { MemberWithStats } from '@/types/analysis'
import { getAdapter } from '@/adapters'
import type { TimeFilter } from '@openchatlab/shared-types'
const { t } = useI18n()
@@ -48,7 +49,7 @@ async function loadSharedWords() {
try {
if (memberList.value.length === 0) {
memberList.value = await window.chatApi.getMembers(props.sessionId)
memberList.value = await getAdapter().getMembers(props.sessionId)
}
const topTwo = [...memberList.value].sort((a, b) => b.messageCount - a.messageCount).slice(0, 2)
if (topTwo.length < 2) {
@@ -5,6 +5,7 @@
*/
import { ref, onMounted, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { getAdapter } from '@/adapters'
const { t } = useI18n()
@@ -61,7 +62,7 @@ async function loadSessionIndexStatus() {
isLoadingSessionStatus.value = true
try {
// 获取所有会话
const sessions = await window.chatApi.getSessions()
const sessions = await getAdapter().getSessions()
// 获取每个会话的索引状态
const statusList: SessionIndexStatus[] = []
+4 -2
View File
@@ -3,6 +3,7 @@ import { ref, computed, watch, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import dayjs from 'dayjs'
import { formatDateRange } from '@/utils'
import { getAdapter } from '@/adapters'
import UITabs from '@/components/UI/Tabs.vue'
import DatePicker from '@/components/UI/DatePicker.vue'
@@ -378,9 +379,10 @@ async function loadData() {
}
try {
const adapter = getAdapter()
const [years, range] = await Promise.all([
window.chatApi.getAvailableYears(props.sessionId),
window.chatApi.getTimeRange(props.sessionId),
adapter.getAvailableYears(props.sessionId),
adapter.getTimeRange(props.sessionId),
])
availableYears.value = years
fullTimeRange.value = range
+2 -1
View File
@@ -2,6 +2,7 @@
import { ref, computed, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import type { MemberWithStats } from '@/types/analysis'
import { getAdapter } from '@/adapters'
const { t } = useI18n()
@@ -53,7 +54,7 @@ async function loadMembers() {
if (!props.sessionId) return
isLoading.value = true
try {
const result = await window.chatApi.getMembers(props.sessionId)
const result = await getAdapter().getMembers(props.sessionId)
// 按消息数排序
members.value = result.sort((a, b) => b.messageCount - a.messageCount)
} catch (error) {
+7 -6
View File
@@ -3,6 +3,7 @@ import { ref, computed, watch, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import type { MemberWithStats } from '@/types/analysis'
import OwnerSelector from '@/components/analysis/member/OwnerSelector.vue'
import { getAdapter } from '@/adapters'
const { t } = useI18n()
@@ -90,13 +91,13 @@ async function loadMembers() {
if (!props.sessionId) return
isLoading.value = true
try {
const result = await window.chatApi.getMembersPaginated(props.sessionId, {
const result = await getAdapter().getMembersPaginated(props.sessionId, {
page: currentPage.value,
pageSize,
search: searchQuery.value.trim(),
sortOrder: sortOrder.value,
})
members.value = result.members
members.value = result.items
total.value = result.total
totalPages.value = result.totalPages
} catch (error) {
@@ -110,7 +111,7 @@ async function loadMembers() {
async function loadAllMembers() {
if (!props.sessionId) return
try {
allMembers.value = await window.chatApi.getMembers(props.sessionId)
allMembers.value = await getAdapter().getMembers(props.sessionId)
} catch (error) {
console.error('加载所有成员失败:', error)
}
@@ -128,7 +129,7 @@ async function updateAliases(member: MemberWithStats, newAliases: string[]) {
savingAliasesId.value = member.id
try {
const success = await window.chatApi.updateMemberAliases(props.sessionId, member.id, aliasesToSave)
const success = await getAdapter().updateMemberAliases(props.sessionId, member.id, aliasesToSave)
if (success) {
// 更新本地数据 - 找到对应成员并更新
const idx = members.value.findIndex((m) => m.id === member.id)
@@ -161,7 +162,7 @@ async function confirmDelete() {
if (!deletingMember.value) return
isDeleting.value = true
try {
const success = await window.chatApi.deleteMember(props.sessionId, deletingMember.value.id)
const success = await getAdapter().deleteMember(props.sessionId, deletingMember.value.id)
if (success) {
// 重新加载当前页数据
await loadMembers()
@@ -201,7 +202,7 @@ async function confirmMerge() {
if (!mergePlan.value) return
isMerging.value = true
try {
const success = await window.chatApi.mergeMembers(
const success = await getAdapter().mergeMembers(
props.sessionId,
mergePlan.value.primary.id,
mergePlan.value.secondary.id
@@ -6,6 +6,7 @@ import type { MemberActivity, HourlyActivity, DailyActivity } from '@/types/anal
import { useI18n } from 'vue-i18n'
import { formatLocalizedDate } from '@/utils'
import { useTimeSelect } from './useTimeSelect'
import { getAdapter } from '@/adapters'
interface UseSessionAnalysisPageBaseOptions {
route: RouteLocationNormalizedLoaded
@@ -64,7 +65,7 @@ export function useSessionAnalysisPageBase(options: UseSessionAnalysisPageBaseOp
if (!currentSessionId.value) return
try {
const sessionData = await window.chatApi.getSession(currentSessionId.value)
const sessionData = await getAdapter().getSession(currentSessionId.value)
session.value = sessionData
} catch (error) {
console.error('加载基础数据失败:', error)
@@ -79,11 +80,12 @@ export function useSessionAnalysisPageBase(options: UseSessionAnalysisPageBaseOp
try {
const filter = timeFilter.value
const adapter = getAdapter()
const [members, hourly, daily, types] = await Promise.all([
window.chatApi.getMemberActivity(currentSessionId.value, filter),
window.chatApi.getHourlyActivity(currentSessionId.value, filter),
window.chatApi.getDailyActivity(currentSessionId.value, filter),
window.chatApi.getMessageTypeDistribution(currentSessionId.value, filter),
adapter.getMemberActivity(currentSessionId.value, filter),
adapter.getHourlyActivity(currentSessionId.value, filter),
adapter.getDailyActivity(currentSessionId.value, filter),
adapter.getMessageTypeDistribution(currentSessionId.value, filter),
])
memberActivity.value = members
@@ -2,6 +2,7 @@
import { ref, watch, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import type { MemberWithStats, MemberNameHistory } from '@/types/analysis'
import { getAdapter } from '@/adapters'
import { SectionCard, EmptyState, LoadingState } from '@/components/UI'
import { formatPeriod } from '@/utils'
@@ -34,7 +35,7 @@ function getDisplayName(member: MemberWithStats): string {
async function loadMembers() {
if (!props.sessionId) return
try {
members.value = await window.chatApi.getMembers(props.sessionId)
members.value = await getAdapter().getMembers(props.sessionId)
} catch (error) {
console.error('加载成员列表失败:', error)
}
@@ -47,9 +48,7 @@ async function loadMembersWithNicknameChanges() {
const membersWithChanges: MemberWithHistory[] = []
try {
const historyPromises = members.value.map((member) =>
window.chatApi.getMemberNameHistory(props.sessionId, member.id)
)
const historyPromises = members.value.map((member) => getAdapter().getMemberNameHistory(props.sessionId, member.id))
const allHistories = await Promise.all(historyPromises)
@@ -2,6 +2,7 @@
import { computed, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import type { MentionAnalysis, MemberMentionDetail } from '@/types/analysis'
import { getAdapter } from '@/adapters'
import { RankListPro } from '@/components/charts'
import type { RankItem } from '@/components/charts'
import { SectionCard, EmptyState, LoadingState } from '@/components/UI'
@@ -24,7 +25,7 @@ async function loadMentionAnalysis() {
if (!props.sessionId) return
isLoadingMention.value = true
try {
mentionAnalysis.value = await window.chatApi.getMentionAnalysis(props.sessionId, props.timeFilter)
mentionAnalysis.value = await getAdapter().getMentionAnalysis(props.sessionId, props.timeFilter)
} catch (error) {
console.error('加载 @ 互动分析失败:', error)
} finally {
@@ -5,6 +5,7 @@ import { useI18n } from 'vue-i18n'
import { useSessionStore } from '@/stores/session'
import { useSettingsStore } from '@/stores/settings'
import { getChatlabSiteLocalePath } from '@/utils/chatlabSiteLocale'
import { getAdapter } from '@/adapters'
const { t } = useI18n()
const router = useRouter()
@@ -16,7 +17,7 @@ const stage = ref<'downloading' | 'importing'>('downloading')
const error = ref<string | null>(null)
async function navigateToSession(sessionId: string) {
const session = await window.chatApi.getSession(sessionId)
const session = await getAdapter().getSession(sessionId)
if (session) {
const routeName = session.type === 'private' ? 'private-chat' : 'group-chat'
router.push({ name: routeName, params: { id: sessionId } })
+2 -1
View File
@@ -9,6 +9,7 @@ import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { getChatlabSiteLocalePath } from '@/utils/chatlabSiteLocale'
import { useSessionStore, type BatchFileInfo, type MergeFileInfo } from '@/stores/session'
import { getAdapter } from '@/adapters'
import DemoImportButton from './DemoImportButton.vue'
const { t } = useI18n()
@@ -102,7 +103,7 @@ function translateError(error: string): string {
// 根据会话类型导航到对应页面
async function navigateToSession(sessionId: string) {
const session = await window.chatApi.getSession(sessionId)
const session = await getAdapter().getSession(sessionId)
if (session) {
const routeName = session.type === 'private' ? 'private-chat' : 'group-chat'
router.push({ name: routeName, params: { id: sessionId } })
@@ -3,6 +3,7 @@ import { ref, computed, watch, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import type { MemberWithStats } from '@/types/analysis'
import OwnerSelector from '@/components/analysis/member/OwnerSelector.vue'
import { getAdapter } from '@/adapters'
const { t } = useI18n()
@@ -71,7 +72,7 @@ async function loadMembers() {
if (!props.sessionId) return
isLoading.value = true
try {
members.value = await window.chatApi.getMembers(props.sessionId)
members.value = await getAdapter().getMembers(props.sessionId)
} catch (error) {
console.error('加载成员列表失败:', error)
} finally {
@@ -89,7 +90,7 @@ async function updateAliases(member: MemberWithStats, newAliases: string[]) {
savingAliasesId.value = member.id
try {
const success = await window.chatApi.updateMemberAliases(props.sessionId, member.id, aliasesToSave)
const success = await getAdapter().updateMemberAliases(props.sessionId, member.id, aliasesToSave)
if (success) {
const idx = members.value.findIndex((m) => m.id === member.id)
if (idx !== -1) {
@@ -129,7 +130,7 @@ async function confirmMerge() {
if (!mergePlan.value) return
isMerging.value = true
try {
const success = await window.chatApi.mergeMembers(
const success = await getAdapter().mergeMembers(
props.sessionId,
mergePlan.value.primary.id,
mergePlan.value.secondary.id
@@ -2,6 +2,7 @@
import { ref, computed, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import type { RelationshipStats, RelationshipMonthStats } from '@/types/analysis'
import { getAdapter } from '@/adapters'
import { ThemeCard, SectionCard, EmptyState, LoadingState } from '@/components/UI'
import { EChart } from '@/components/charts'
import RelationshipMetricCard from './RelationshipMetricCard.vue'
@@ -31,7 +32,7 @@ async function loadData(options?: { localOnly?: 'perseverance' }) {
isLoading.value = true
}
try {
stats.value = await window.chatApi.getRelationshipStats(props.sessionId, props.timeFilter, {
stats.value = await getAdapter().getRelationshipStats(props.sessionId, props.timeFilter, {
perseveranceThreshold: perseveranceThreshold.value,
})
} catch (error) {
+2 -1
View File
@@ -10,6 +10,7 @@ import { storeToRefs } from 'pinia'
import { usePromptStore } from '@/stores/prompt'
import { useSessionStore } from '@/stores/session'
import { useSettingsStore } from '@/stores/settings'
import { getAdapter } from '@/adapters'
import { useAssistantStore } from '@/stores/assistant'
import { useSkillStore } from '@/stores/skill'
import type { TokenUsage, AgentRuntimeStatus, SerializedErrorInfo } from '@electron/shared/types'
@@ -348,7 +349,7 @@ export const useAIChatStore = defineStore('aiChatRuntime', () => {
}
try {
const members = await window.chatApi.getMembers(state.sessionId)
const members = await getAdapter().getMembers(state.sessionId)
const ownerMember = members.find((member) => member.platformId === ownerId)
state.ownerInfo = ownerMember
? {
+5 -4
View File
@@ -1,6 +1,7 @@
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import type { AnalysisSession, ImportProgress, ChatType } from '@/types/base'
import { getAdapter } from '@/adapters'
/** 侧边栏筛选类型 */
export type SessionFilterType = 'all' | ChatType
@@ -160,7 +161,7 @@ export const useSessionStore = defineStore(
*/
async function loadSessions() {
try {
const list = await window.chatApi.getSessions()
const list = await getAdapter().getSessions()
sessions.value = list
// 如果当前选中的会话不存在了,清除选中状态
if (currentSessionId.value && !list.find((s) => s.id === currentSessionId.value)) {
@@ -639,7 +640,7 @@ export const useSessionStore = defineStore(
*/
async function deleteSession(id: string): Promise<boolean> {
try {
const success = await window.chatApi.deleteSession(id)
const success = await getAdapter().deleteSession(id)
if (success) {
const index = sessions.value.findIndex((s) => s.id === id)
if (index !== -1) {
@@ -662,7 +663,7 @@ export const useSessionStore = defineStore(
*/
async function renameSession(id: string, newName: string): Promise<boolean> {
try {
const success = await window.chatApi.renameSession(id, newName)
const success = await getAdapter().renameSession(id, newName)
if (success) {
const session = sessions.value.find((s) => s.id === id)
if (session) {
@@ -688,7 +689,7 @@ export const useSessionStore = defineStore(
*/
async function updateSessionOwnerId(id: string, ownerId: string | null): Promise<boolean> {
try {
const success = await window.chatApi.updateSessionOwnerId(id, ownerId)
const success = await getAdapter().updateSessionOwnerId(id, ownerId)
if (success) {
const session = sessions.value.find((s) => s.id === id)
if (session) {