+
{{ loadErrorText }}
{{ t('common.back') }}
diff --git a/src/components/UI/InsightLoadingDots.vue b/src/components/UI/InsightLoadingDots.vue
new file mode 100644
index 00000000..342e2b46
--- /dev/null
+++ b/src/components/UI/InsightLoadingDots.vue
@@ -0,0 +1,61 @@
+
+
+
+
+
diff --git a/src/components/UI/InsightViewTransition.vue b/src/components/UI/InsightViewTransition.vue
new file mode 100644
index 00000000..2e7193dc
--- /dev/null
+++ b/src/components/UI/InsightViewTransition.vue
@@ -0,0 +1,221 @@
+
+
+
+
+
+
+
diff --git a/src/components/UI/LoadingState.vue b/src/components/UI/LoadingState.vue
index 210c94a5..794c0077 100644
--- a/src/components/UI/LoadingState.vue
+++ b/src/components/UI/LoadingState.vue
@@ -4,7 +4,8 @@
* 支持行内加载、页面加载和蒙层覆盖
*/
-import { computed } from 'vue'
+import { computed, ref } from 'vue'
+import { useInsightViewLoading } from './insight-view-loading'
const props = withDefaults(
defineProps<{
@@ -33,10 +34,15 @@ const containerClass = computed(() => {
return `${base} ${props.height || 'py-8'}`
}
})
+
+const pageLoadingCoordinator = useInsightViewLoading(ref(true))
+const isCoordinatedPageLoading = computed(
+ () => pageLoadingCoordinator !== null && (pageLoadingCoordinator.suppress?.value ?? true)
+)
-
+
{{ text }}
diff --git a/src/components/UI/index.ts b/src/components/UI/index.ts
index 4be89bf2..4034e1b3 100644
--- a/src/components/UI/index.ts
+++ b/src/components/UI/index.ts
@@ -9,6 +9,7 @@ export { default as SectionCard } from './SectionCard.vue'
export { default as StatCard } from './StatCard.vue'
export { default as EmptyState } from './EmptyState.vue'
export { default as LoadingState } from './LoadingState.vue'
+export { default as InsightViewTransition } from './InsightViewTransition.vue'
export { default as StartupLoading } from './StartupLoading.vue'
export { default as Tabs } from './Tabs.vue'
export { default as UITabs } from './Tabs.vue'
diff --git a/src/components/UI/insight-view-loading.test.ts b/src/components/UI/insight-view-loading.test.ts
new file mode 100644
index 00000000..3226f59a
--- /dev/null
+++ b/src/components/UI/insight-view-loading.test.ts
@@ -0,0 +1,33 @@
+import assert from 'node:assert/strict'
+import test from 'node:test'
+import { ref } from 'vue'
+import { createInsightViewLoadingBinding } from './insight-view-loading'
+
+test('keeps custom async views registered until their loading state finishes', async () => {
+ const pendingLoaders = ref(0)
+ const binding = createInsightViewLoadingBinding({
+ register: () => {
+ pendingLoaders.value++
+ let active = true
+ return () => {
+ if (!active) return
+ active = false
+ pendingLoaders.value--
+ }
+ },
+ })
+
+ binding.sync(true)
+ binding.sync(true)
+ await Promise.resolve()
+ await Promise.resolve()
+
+ assert.equal(pendingLoaders.value, 1)
+
+ binding.sync(false)
+ assert.equal(pendingLoaders.value, 0)
+
+ binding.sync(true)
+ binding.dispose()
+ assert.equal(pendingLoaders.value, 0)
+})
diff --git a/src/components/UI/insight-view-loading.ts b/src/components/UI/insight-view-loading.ts
new file mode 100644
index 00000000..48aec9ac
--- /dev/null
+++ b/src/components/UI/insight-view-loading.ts
@@ -0,0 +1,48 @@
+import { inject, onBeforeUnmount, watch } from 'vue'
+import type { InjectionKey, Ref } from 'vue'
+
+export interface InsightViewLoadingCoordinator {
+ register: () => () => void
+ suppress?: Readonly
[>
+}
+
+export interface InsightViewLoadingBinding {
+ sync: (loading: boolean) => void
+ dispose: () => void
+}
+
+export const insightViewLoadingCoordinatorKey: InjectionKey = Symbol(
+ 'insight-view-loading-coordinator'
+)
+
+export function createInsightViewLoadingBinding(
+ coordinator: InsightViewLoadingCoordinator | null
+): InsightViewLoadingBinding {
+ let unregister: (() => void) | null = null
+
+ function release() {
+ unregister?.()
+ unregister = null
+ }
+
+ return {
+ sync(loading) {
+ if (loading) {
+ if (!unregister && coordinator) unregister = coordinator.register()
+ return
+ }
+ release()
+ },
+ dispose: release,
+ }
+}
+
+export function useInsightViewLoading(loading: Readonly][>): InsightViewLoadingCoordinator | null {
+ const coordinator = inject(insightViewLoadingCoordinatorKey, null)
+ const binding = createInsightViewLoadingBinding(coordinator)
+
+ watch(loading, (value) => binding.sync(value), { immediate: true, flush: 'sync' })
+ onBeforeUnmount(binding.dispose)
+
+ return coordinator
+}
diff --git a/src/components/analysis/quotes/WordcloudTab.vue b/src/components/analysis/quotes/WordcloudTab.vue
index 8478b6ed..27b792c7 100644
--- a/src/components/analysis/quotes/WordcloudTab.vue
+++ b/src/components/analysis/quotes/WordcloudTab.vue
@@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n'
const EChartWordcloud = defineAsyncComponent(() => import('@/components/charts/EChartWordcloud.vue'))
import type { EChartWordcloudData } from '@/components/charts'
import { LoadingState, UITabs, SectionCard } from '@/components/UI'
+import { useInsightViewLoading } from '@/components/UI/insight-view-loading'
import TopicProfileCard from './TopicProfileCard.vue'
import SharedTopicsCard from './SharedTopicsCard.vue'
import type { WordFrequencyItem, PosTagStat } from './topicProfileTypes'
@@ -48,7 +49,8 @@ const props = withDefaults(
}
)
-const isLoading = ref(false)
+const isLoading = ref(true)
+useInsightViewLoading(isLoading)
// 完整词表:按最大档一次性获取并缓存,切换词数时本地切片,避免重复请求与重复分词
const allWords = ref([])
const stats = ref({
diff --git a/src/components/analysis/relationships/ClusterView.vue b/src/components/analysis/relationships/ClusterView.vue
index 95d7060c..26096317 100644
--- a/src/components/analysis/relationships/ClusterView.vue
+++ b/src/components/analysis/relationships/ClusterView.vue
@@ -12,6 +12,7 @@ import { TooltipComponent, GridComponent, VisualMapComponent } from 'echarts/com
import { CanvasRenderer } from 'echarts/renderers'
import type { EChartsOption } from 'echarts'
import { ThemeCard } from '@/components/UI'
+import { useInsightViewLoading } from '@/components/UI/insight-view-loading'
import { useDataService } from '@/services/data/service'
import type { ClusterGraphData, ClusterGraphOptions } from '@/types/analysis'
import type { TimeFilter } from '@openchatlab/shared-types'
@@ -29,6 +30,7 @@ const isDark = computed(() => colorMode.value === 'dark')
// 数据状态
const isLoading = ref(true)
+useInsightViewLoading(isLoading)
const graphData = ref(null)
// 视图模式
diff --git a/src/components/analysis/relationships/InteractionView.vue b/src/components/analysis/relationships/InteractionView.vue
index 54673255..53263289 100644
--- a/src/components/analysis/relationships/InteractionView.vue
+++ b/src/components/analysis/relationships/InteractionView.vue
@@ -7,6 +7,7 @@ import { ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { EChartGraph } from '@/components/charts'
import type { EChartGraphData } from '@/components/charts'
+import { useInsightViewLoading } from '@/components/UI/insight-view-loading'
import { useDataService } from '@/services/data/service'
import type { TimeFilter } from '@openchatlab/shared-types'
@@ -19,6 +20,7 @@ const { t } = useI18n()
// 数据状态
const isLoading = ref(true)
+useInsightViewLoading(isLoading)
const graphData = ref({ nodes: [], links: [], maxLinkValue: 0 })
// 布局切换
diff --git a/src/components/common/TimeSelect.vue b/src/components/common/TimeSelect.vue
index 5ccb29d0..baf8e4dc 100644
--- a/src/components/common/TimeSelect.vue
+++ b/src/components/common/TimeSelect.vue
@@ -1,5 +1,5 @@
diff --git a/src/components/layout/session/SessionAnalysisHeader.vue b/src/components/layout/session/SessionAnalysisHeader.vue
index 9439f60d..3421db11 100644
--- a/src/components/layout/session/SessionAnalysisHeader.vue
+++ b/src/components/layout/session/SessionAnalysisHeader.vue
@@ -39,6 +39,7 @@ const emit = defineEmits<{
(e: 'openChatRecord'): void
(e: 'update:fullRange', value: { start: number; end: number } | null): void
(e: 'update:availableYears', value: number[]): void
+ (e: 'timeRangeInitialized', hasRange: boolean): void
}>()
const { t } = useI18n()
@@ -106,16 +107,17 @@ const navigationItems = computed(() =>
diff --git a/src/composables/useSessionAnalysisPageBase.test.ts b/src/composables/useSessionAnalysisPageBase.test.ts
index 9f15ae00..d82973cf 100644
--- a/src/composables/useSessionAnalysisPageBase.test.ts
+++ b/src/composables/useSessionAnalysisPageBase.test.ts
@@ -1,6 +1,6 @@
import assert from 'node:assert/strict'
import test from 'node:test'
-import { effectScope, reactive, ref } from 'vue'
+import { effectScope, nextTick, reactive, ref } from 'vue'
import type { RouteLocationNormalizedLoaded, Router } from 'vue-router'
import { MessageType } from '@/types/base'
import type { DailyActivity, HourlyActivity, MemberActivity } from '@/types/analysis'
@@ -20,6 +20,10 @@ function createDeferred(): Deferred {
return { promise, resolve }
}
+function createDeferredLoads(count: number): Deferred[] {
+ return Array.from({ length: count }, () => createDeferred())
+}
+
function createMember(name: string): MemberActivity {
return {
memberId: 1,
@@ -30,6 +34,10 @@ function createMember(name: string): MemberActivity {
}
}
+async function flushPromises() {
+ await new Promise((resolve) => setImmediate(resolve))
+}
+
test('only the latest analysis load may update results and loading state', async (t) => {
await t.mock.module('@/utils', {
namedExports: {
@@ -38,17 +46,14 @@ test('only the latest analysis load may update results and loading state', async
})
const { useSessionAnalysisPageBase } = await import('./useSessionAnalysisPageBase')
- const memberLoads = [createDeferred(), createDeferred()]
- const hourlyLoads = [createDeferred(), createDeferred()]
- const dailyLoads = [createDeferred(), createDeferred()]
- const typeLoads = [
- createDeferred>(),
- createDeferred>(),
- ]
+ const memberLoads = createDeferredLoads(2)
+ const hourlyLoads = createDeferredLoads(2)
+ const dailyLoads = createDeferredLoads(2)
+ const typeLoads = createDeferredLoads>(2)
let loadIndex = 0
registerAdapter('data', {
- getSession: async () => null,
+ getSession: async () => ({ id: 'session-one' }),
getMemberActivity: () => memberLoads[loadIndex]!.promise,
getHourlyActivity: () => hourlyLoads[loadIndex]!.promise,
getDailyActivity: () => dailyLoads[loadIndex]!.promise,
@@ -72,6 +77,8 @@ test('only the latest analysis load may update results and loading state', async
})
)!
+ assert.equal(page.isSessionSwitching.value, true)
+
const staleLoad = page.loadAnalysisData()
const latestLoad = page.loadAnalysisData()
@@ -83,6 +90,7 @@ test('only the latest analysis load may update results and loading state', async
assert.equal(page.memberActivity.value.length, 0)
assert.equal(page.isLoading.value, true)
+ assert.equal(page.isSessionSwitching.value, true)
memberLoads[1]!.resolve([createMember('latest')])
hourlyLoads[1]!.resolve([{ hour: 2, messageCount: 2 }])
@@ -93,4 +101,142 @@ test('only the latest analysis load may update results and loading state', async
assert.equal(page.memberActivity.value[0]?.name, 'latest')
assert.deepEqual(page.hourlyActivity.value, [{ hour: 2, messageCount: 2 }])
assert.equal(page.isLoading.value, false)
+ assert.equal(page.isSessionSwitching.value, false)
+})
+
+test('session loading coordinates required data, missing ranges, and non-analysis tabs', async (t) => {
+ await t.mock.module('@/utils', {
+ namedExports: {
+ formatLocalizedDate: () => '',
+ },
+ })
+ const { useSessionAnalysisPageBase } = await import('./useSessionAnalysisPageBase')
+
+ const sessionLoads = createDeferredLoads<{ id: string } | null>(4)
+ const memberLoads = createDeferredLoads(4)
+ const hourlyLoads = createDeferredLoads(4)
+ const dailyLoads = createDeferredLoads(4)
+ const typeLoads = createDeferredLoads>(4)
+ let sessionLoadIndex = 0
+ let analysisLoadIndex = 0
+
+ registerAdapter('data', {
+ getSession: () => sessionLoads[sessionLoadIndex++]!.promise,
+ getMemberActivity: () => memberLoads[analysisLoadIndex]!.promise,
+ getHourlyActivity: () => hourlyLoads[analysisLoadIndex]!.promise,
+ getDailyActivity: () => dailyLoads[analysisLoadIndex]!.promise,
+ getMessageTypeDistribution: () => typeLoads[analysisLoadIndex++]!.promise,
+ } as unknown as DataAdapter)
+
+ t.mock.method(console, 'warn', () => undefined)
+ const scope = effectScope()
+ t.after(() => scope.stop())
+ const route = reactive({ params: { id: 'session-one' }, query: {} }) as unknown as RouteLocationNormalizedLoaded
+ const router = { replace: async () => undefined } as unknown as Router
+ const currentSessionId = ref('session-one')
+ const page = scope.run(() =>
+ useSessionAnalysisPageBase({
+ route,
+ router,
+ currentSessionId,
+ selectSession: () => undefined,
+ defaultTab: 'insights',
+ validTabIds: ['insights', 'ranking', 'ai-chat'],
+ })
+ )!
+
+ const firstAnalysisLoad = page.loadAnalysisData()
+ memberLoads[0]!.resolve([createMember('session-one')])
+ hourlyLoads[0]!.resolve([])
+ dailyLoads[0]!.resolve([])
+ typeLoads[0]!.resolve([])
+ await firstAnalysisLoad
+
+ assert.equal(page.session.value?.id ?? null, null)
+ assert.equal(page.isSessionSwitching.value, true)
+
+ sessionLoads[0]!.resolve({ id: 'session-one' })
+ await flushPromises()
+
+ assert.equal(page.session.value?.id, 'session-one')
+ assert.equal(page.isSessionSwitching.value, false)
+
+ currentSessionId.value = 'session-two'
+ await nextTick()
+ sessionLoads[1]!.resolve({ id: 'session-two' })
+ await flushPromises()
+
+ assert.equal(page.session.value?.id, 'session-two')
+ assert.equal(page.isSessionSwitching.value, true)
+
+ const secondAnalysisLoad = page.loadAnalysisData()
+ memberLoads[1]!.resolve([createMember('session-two')])
+ hourlyLoads[1]!.resolve([])
+ dailyLoads[1]!.resolve([])
+ typeLoads[1]!.resolve([])
+ await secondAnalysisLoad
+
+ assert.equal(page.memberActivity.value[0]?.name, 'session-two')
+ assert.equal(page.isSessionSwitching.value, false)
+
+ currentSessionId.value = 'session-three'
+ await nextTick()
+ sessionLoads[2]!.resolve({ id: 'session-three' })
+ await flushPromises()
+
+ assert.equal(page.isSessionSwitching.value, true)
+ page.handleTimeRangeInitialized(false)
+ memberLoads[2]!.resolve([createMember('session-three')])
+ hourlyLoads[2]!.resolve([])
+ dailyLoads[2]!.resolve([])
+ typeLoads[2]!.resolve([])
+ await flushPromises()
+
+ assert.equal(page.memberActivity.value[0]?.name, 'session-three')
+ assert.equal(page.isSessionSwitching.value, false)
+
+ page.activeTab.value = 'ranking'
+ await nextTick()
+
+ assert.equal(page.isLoading.value, false)
+ assert.equal(analysisLoadIndex, 3)
+
+ page.activeTab.value = 'ai-chat'
+ await nextTick()
+ currentSessionId.value = 'session-four'
+ await nextTick()
+ sessionLoads[3]!.resolve({ id: 'session-four' })
+ await flushPromises()
+
+ assert.equal(page.isSessionSwitching.value, false)
+ assert.equal(page.isLoading.value, false)
+ page.timeRangeValue.value = {
+ startTs: 1,
+ endTs: 2,
+ displayLabel: 'test',
+ isFullRange: false,
+ state: { mode: 'custom', customStart: '1970-01-01', customEnd: '1970-01-01' },
+ }
+ await nextTick()
+
+ assert.equal(analysisLoadIndex, 3)
+
+ page.timeRangeValue.value = null
+ page.activeTab.value = 'insights'
+ await nextTick()
+
+ assert.equal(page.isLoading.value, true)
+ assert.equal(analysisLoadIndex, 3)
+
+ page.handleTimeRangeInitialized(false)
+ assert.equal(analysisLoadIndex, 4)
+
+ memberLoads[3]!.resolve([createMember('session-four')])
+ hourlyLoads[3]!.resolve([])
+ dailyLoads[3]!.resolve([])
+ typeLoads[3]!.resolve([])
+ await flushPromises()
+
+ assert.equal(page.memberActivity.value[0]?.name, 'session-four')
+ assert.equal(page.isLoading.value, false)
})
diff --git a/src/composables/useSessionAnalysisPageBase.ts b/src/composables/useSessionAnalysisPageBase.ts
index 4ed8c058..4f0c1538 100644
--- a/src/composables/useSessionAnalysisPageBase.ts
+++ b/src/composables/useSessionAnalysisPageBase.ts
@@ -31,12 +31,21 @@ export function useSessionAnalysisPageBase(options: UseSessionAnalysisPageBaseOp
const isLoading = ref(true)
const isInitialLoad = ref(true)
+ const isSessionSwitching = ref(true)
const session = ref(null)
const memberActivity = ref([])
const hourlyActivity = ref([])
const dailyActivity = ref([])
const messageTypes = ref>([])
+ let baseLoadVersion = 0
let analysisLoadVersion = 0
+ let baseDataReady = false
+ let analysisDataReady = false
+
+ function finishSessionSwitchIfReady() {
+ if (!isSessionSwitching.value || !baseDataReady) return
+ if (!session.value || analysisDataReady) isSessionSwitching.value = false
+ }
function resolveActiveTabFromRoute(): string {
const routeTab = route.query.tab as string | undefined
@@ -45,6 +54,8 @@ export function useSessionAnalysisPageBase(options: UseSessionAnalysisPageBaseOp
}
const activeTab = ref(resolveActiveTabFromRoute())
+ const activeTabUsesOverviewAnalytics = computed(() => activeTab.value === 'insights')
+ const activeTabUsesTimeRange = computed(() => ['insights', 'ranking'].includes(activeTab.value))
watch(
activeTab,
@@ -60,12 +71,16 @@ export function useSessionAnalysisPageBase(options: UseSessionAnalysisPageBaseOp
{ immediate: true }
)
- const { timeRangeValue, fullTimeRange, availableYears, timeFilter, initialTimeState } = useTimeSelect(route, router, {
- activeTab,
- isInitialLoad,
- currentSessionId,
- onTimeRangeChange: () => loadAnalysisData(),
- })
+ const { timeRangeValue, fullTimeRange, availableYears, timeFilter, initialTimeState, resetTimeRange } = useTimeSelect(
+ route,
+ router,
+ {
+ activeTab,
+ isInitialLoad,
+ currentSessionId,
+ onTimeRangeChange: () => loadAnalysisData(),
+ }
+ )
function syncSession() {
const id = route.params.id as string
@@ -77,22 +92,19 @@ export function useSessionAnalysisPageBase(options: UseSessionAnalysisPageBaseOp
}
}
- async function loadBaseData() {
- if (!currentSessionId.value) return
-
- try {
- const sessionData = await useDataService().getSession(currentSessionId.value)
- session.value = sessionData
- } catch (error) {
- console.error('加载基础数据失败:', error)
- }
- }
-
async function loadAnalysisData() {
const sessionId = currentSessionId.value
if (!sessionId) return
const loadVersion = ++analysisLoadVersion
+ if (!activeTabUsesOverviewAnalytics.value) {
+ isLoading.value = false
+ analysisDataReady = true
+ finishSessionSwitchIfReady()
+ return
+ }
+
+ if (isSessionSwitching.value) analysisDataReady = false
isLoading.value = true
try {
@@ -119,18 +131,58 @@ export function useSessionAnalysisPageBase(options: UseSessionAnalysisPageBaseOp
} finally {
if (loadVersion === analysisLoadVersion) {
isLoading.value = false
+ analysisDataReady = true
+ finishSessionSwitchIfReady()
}
}
}
- async function loadData() {
- if (!currentSessionId.value) return
-
- isInitialLoad.value = true
- await loadBaseData()
- isInitialLoad.value = false
+ function handleTimeRangeInitialized(hasRange: boolean) {
+ if (hasRange) return
+ timeRangeValue.value = null
+ void loadAnalysisData()
}
+ async function loadData() {
+ const sessionId = currentSessionId.value
+ if (!sessionId) return
+
+ const loadVersion = ++baseLoadVersion
+ isInitialLoad.value = true
+ try {
+ const sessionData = await useDataService().getSession(sessionId)
+ if (loadVersion !== baseLoadVersion || currentSessionId.value !== sessionId) return
+ session.value = sessionData
+ } catch (error) {
+ if (loadVersion === baseLoadVersion && currentSessionId.value === sessionId) {
+ session.value = null
+ console.error('加载基础数据失败:', error)
+ }
+ } finally {
+ if (loadVersion === baseLoadVersion && currentSessionId.value === sessionId) {
+ isInitialLoad.value = false
+ baseDataReady = true
+ finishSessionSwitchIfReady()
+ }
+ }
+ }
+
+ watch(activeTab, () => {
+ if (!activeTabUsesOverviewAnalytics.value) {
+ analysisLoadVersion++
+ abortAnalyticsRequests()
+ isLoading.value = false
+ if (!activeTabUsesTimeRange.value) {
+ analysisDataReady = true
+ finishSessionSwitchIfReady()
+ }
+ return
+ }
+
+ isLoading.value = true
+ if (currentSessionId.value && timeRangeValue.value) void loadAnalysisData()
+ })
+
watch(
() => route.params.id,
() => {
@@ -150,9 +202,14 @@ export function useSessionAnalysisPageBase(options: UseSessionAnalysisPageBaseOp
currentSessionId,
() => {
analysisLoadVersion++
+ resetTimeRange()
+ baseDataReady = false
+ analysisDataReady = !activeTabUsesTimeRange.value
+ isLoading.value = activeTabUsesOverviewAnalytics.value
+ isSessionSwitching.value = true
// 切换会话时,上一会话的分析请求立即作废(切换后子 Tab 会按新 key 重挂并重新取数)。
abortAnalyticsRequests()
- loadData()
+ void loadData()
},
{ immediate: true }
)
@@ -165,6 +222,7 @@ export function useSessionAnalysisPageBase(options: UseSessionAnalysisPageBaseOp
activeTab,
isLoading,
isInitialLoad,
+ isSessionSwitching,
session,
memberActivity,
hourlyActivity,
@@ -178,6 +236,7 @@ export function useSessionAnalysisPageBase(options: UseSessionAnalysisPageBaseOp
syncSession,
loadData,
loadAnalysisData,
+ handleTimeRangeInitialized,
}
}
diff --git a/src/pages/group-chat/components/insights/GroupChatInsights.vue b/src/pages/group-chat/components/insights/GroupChatInsights.vue
index d759dd2b..1bad585f 100644
--- a/src/pages/group-chat/components/insights/GroupChatInsights.vue
+++ b/src/pages/group-chat/components/insights/GroupChatInsights.vue
@@ -1,5 +1,5 @@
- ]
-
-
+
+
+
+
-
+
{
@open-chat-record="openChatRecordViewer"
@update:full-range="fullTimeRange = $event"
@update:available-years="availableYears = $event"
+ @time-range-initialized="handleTimeRangeInitialized"
/>
-
+
@@ -194,7 +209,7 @@ const filteredMemberCount = computed(() => {
-
+
{{ t('analysis.groupChat.loadError') }}
diff --git a/src/pages/private-chat/components/insights/PrivateChatInsights.vue b/src/pages/private-chat/components/insights/PrivateChatInsights.vue
index 391946c8..6420b1c6 100644
--- a/src/pages/private-chat/components/insights/PrivateChatInsights.vue
+++ b/src/pages/private-chat/components/insights/PrivateChatInsights.vue
@@ -1,5 +1,5 @@
-
-
-
+
+
+
+
-
+
{
@open-member-management="showMemberManagementModal = true"
@open-chat-record="openChatRecordViewer"
@update:full-range="fullTimeRange = $event"
+ @time-range-initialized="handleTimeRangeInitialized"
/>
-
+
@@ -206,7 +223,7 @@ const otherMemberAvatar = computed(() => {
-
+
{{ t('analysis.privateChat.loadError') }}