feat: 优化消息类型分布

This commit is contained in:
digua
2026-01-21 23:58:15 +08:00
parent 99ba871923
commit 21c8320ea5
12 changed files with 257 additions and 384 deletions
@@ -1,203 +0,0 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import type { HourlyActivity, WeekdayActivity, MonthlyActivity } from '@/types/analysis'
import { EChartBar } from '@/components/charts'
import type { EChartBarData } from '@/components/charts'
import { SectionCard } from '@/components/UI'
const { t, locale } = useI18n()
const props = defineProps<{
hourlyActivity: HourlyActivity[]
weekdayActivity: WeekdayActivity[]
monthlyActivity: MonthlyActivity[]
isLoadingWeekday: boolean
isLoadingMonthly: boolean
weekdayNames: string[]
weekdayVsWeekend: { weekday: number; weekend: number }
}>()
// --- 24小时分布逻辑 ---
// 24小时分布图数据
const hourlyChartData = computed<EChartBarData>(() => {
return {
labels: props.hourlyActivity.map((h) => `${h.hour}:00`),
values: props.hourlyActivity.map((h) => h.messageCount),
}
})
// 时段占比计算
const totalMessages = computed(() => props.hourlyActivity.reduce((sum, h) => sum + h.messageCount, 0))
const lateNightRatio = computed(() => {
const lateNight = props.hourlyActivity
.filter((h) => h.hour >= 0 && h.hour < 6)
.reduce((sum, h) => sum + h.messageCount, 0)
return totalMessages.value > 0 ? Math.round((lateNight / totalMessages.value) * 100) : 0
})
const morningRatio = computed(() => {
const morning = props.hourlyActivity
.filter((h) => h.hour >= 6 && h.hour < 12)
.reduce((sum, h) => sum + h.messageCount, 0)
return totalMessages.value > 0 ? Math.round((morning / totalMessages.value) * 100) : 0
})
const afternoonRatio = computed(() => {
const afternoon = props.hourlyActivity
.filter((h) => h.hour >= 12 && h.hour < 18)
.reduce((sum, h) => sum + h.messageCount, 0)
return totalMessages.value > 0 ? Math.round((afternoon / totalMessages.value) * 100) : 0
})
const eveningRatio = computed(() => {
const evening = props.hourlyActivity
.filter((h) => h.hour >= 18 && h.hour < 24)
.reduce((sum, h) => sum + h.messageCount, 0)
return totalMessages.value > 0 ? Math.round((evening / totalMessages.value) * 100) : 0
})
// --- 星期分布逻辑 ---
// 星期分布图数据
const weekdayChartData = computed<EChartBarData>(() => {
return {
labels: props.weekdayActivity.map((w) => props.weekdayNames[w.weekday - 1]),
values: props.weekdayActivity.map((w) => w.messageCount),
}
})
// --- 月份分布逻辑 ---
// 月份分布图数据
const monthlyChartData = computed<EChartBarData>(() => {
return {
labels: props.monthlyActivity.map((m) => {
// 中文用 X月,英文用 Jan, Feb 等
if (locale.value === 'zh-CN') {
return `${m.month}`
}
const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
return monthNames[m.month - 1] || `${m.month}`
}),
values: props.monthlyActivity.map((m) => m.messageCount),
}
})
</script>
<template>
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2 2xl:grid-cols-3">
<!-- 24小时分布 -->
<SectionCard :title="t('hourlyTitle')" :show-divider="false">
<div class="p-5">
<EChartBar :data="hourlyChartData" :height="256" />
<div class="mt-6 grid grid-cols-4 gap-2">
<div class="text-center">
<div class="text-xs text-gray-500 dark:text-gray-400">{{ t('lateNight') }}</div>
<div class="mt-1 text-base font-semibold text-gray-900 dark:text-white">{{ lateNightRatio }}%</div>
<div class="mx-auto mt-1 h-1 w-full max-w-12 overflow-hidden rounded-full bg-gray-100 dark:bg-gray-800">
<div class="h-full rounded-full bg-pink-300 transition-all" :style="{ width: `${lateNightRatio}%` }" />
</div>
</div>
<div class="text-center">
<div class="text-xs text-gray-500 dark:text-gray-400">{{ t('morning') }}</div>
<div class="mt-1 text-base font-semibold text-gray-900 dark:text-white">{{ morningRatio }}%</div>
<div class="mx-auto mt-1 h-1 w-full max-w-12 overflow-hidden rounded-full bg-gray-100 dark:bg-gray-800">
<div class="h-full rounded-full bg-pink-400 transition-all" :style="{ width: `${morningRatio}%` }" />
</div>
</div>
<div class="text-center">
<div class="text-xs text-gray-500 dark:text-gray-400">{{ t('afternoon') }}</div>
<div class="mt-1 text-base font-semibold text-gray-900 dark:text-white">{{ afternoonRatio }}%</div>
<div class="mx-auto mt-1 h-1 w-full max-w-12 overflow-hidden rounded-full bg-gray-100 dark:bg-gray-800">
<div class="h-full rounded-full bg-pink-500 transition-all" :style="{ width: `${afternoonRatio}%` }" />
</div>
</div>
<div class="text-center">
<div class="text-xs text-gray-500 dark:text-gray-400">{{ t('evening') }}</div>
<div class="mt-1 text-base font-semibold text-gray-900 dark:text-white">{{ eveningRatio }}%</div>
<div class="mx-auto mt-1 h-1 w-full max-w-12 overflow-hidden rounded-full bg-gray-100 dark:bg-gray-800">
<div class="h-full rounded-full bg-pink-600 transition-all" :style="{ width: `${eveningRatio}%` }" />
</div>
</div>
</div>
</div>
</SectionCard>
<!-- 星期分布 -->
<SectionCard :title="t('weekdayTitle')" :show-divider="false">
<div class="p-5">
<div v-if="isLoadingWeekday" class="flex h-64 items-center justify-center">
<UIcon name="i-heroicons-arrow-path" class="h-6 w-6 animate-spin text-pink-500" />
</div>
<template v-else>
<EChartBar :data="weekdayChartData" :height="256" />
<div class="mt-6 grid grid-cols-2 gap-4">
<div class="text-center">
<div class="text-xs text-gray-500 dark:text-gray-400">{{ t('weekdays') }}</div>
<div class="mt-1 text-lg font-semibold text-gray-900 dark:text-white">
{{ weekdayVsWeekend.weekday }}%
</div>
<div class="mx-auto mt-2 h-1 w-full max-w-24 overflow-hidden rounded-full bg-gray-100 dark:bg-gray-800">
<div
class="h-full rounded-full bg-pink-500 transition-all"
:style="{ width: `${weekdayVsWeekend.weekday}%` }"
/>
</div>
</div>
<div class="text-center">
<div class="text-xs text-gray-500 dark:text-gray-400">{{ t('weekend') }}</div>
<div class="mt-1 text-lg font-semibold text-gray-900 dark:text-white">
{{ weekdayVsWeekend.weekend }}%
</div>
<div class="mx-auto mt-2 h-1 w-full max-w-24 overflow-hidden rounded-full bg-gray-100 dark:bg-gray-800">
<div
class="h-full rounded-full bg-blue-500 transition-all"
:style="{ width: `${weekdayVsWeekend.weekend}%` }"
/>
</div>
</div>
</div>
</template>
</div>
</SectionCard>
<!-- 月份活跃分布 -->
<SectionCard :title="t('monthlyTitle')" :show-divider="false">
<div class="p-5">
<div v-if="isLoadingMonthly" class="flex h-64 items-center justify-center">
<UIcon name="i-heroicons-arrow-path" class="h-6 w-6 animate-spin text-pink-500" />
</div>
<EChartBar v-else :data="monthlyChartData" :height="256" />
</div>
</SectionCard>
</div>
</template>
<i18n>
{
"zh-CN": {
"hourlyTitle": "24小时活跃分布",
"lateNight": "凌晨",
"morning": "上午",
"afternoon": "下午",
"evening": "晚上",
"weekdayTitle": "星期活跃分布",
"weekdays": "工作日",
"weekend": "周末",
"monthlyTitle": "月份活跃分布"
},
"en-US": {
"hourlyTitle": "24-Hour Activity",
"lateNight": "Late Night",
"morning": "Morning",
"afternoon": "Afternoon",
"evening": "Evening",
"weekdayTitle": "Weekly Activity",
"weekdays": "Weekdays",
"weekend": "Weekend",
"monthlyTitle": "Monthly Activity"
}
}
</i18n>
@@ -66,6 +66,36 @@ const typeChartData = computed<EChartPieData>(() => {
}
})
//
const typeSummary = computed(() => {
const total = messageTypes.value.reduce((sum, t) => sum + t.count, 0)
const sorted = [...messageTypes.value].sort((a, b) => b.count - a.count)
return sorted.map((item) => ({
name: getMessageTypeName(item.type),
count: item.count,
percentage: total > 0 ? Math.round((item.count / total) * 100) : 0,
}))
})
// EChartPie
const typeColors = [
'#6366f1', // indigo
'#8b5cf6', // violet
'#ec4899', // pink
'#f43f5e', // rose
'#f97316', // orange
'#eab308', // yellow
'#22c55e', // green
'#14b8a6', // teal
'#06b6d4', // cyan
'#3b82f6', // blue
]
function getTypeColor(index: number): string {
return typeColors[index % typeColors.length]
}
//
const hourlyChartData = computed<EChartBarData>(() => {
// 24
@@ -198,7 +228,53 @@ watch(
<!-- 消息类型分布 -->
<SectionCard :title="t('typeDistribution')" :show-divider="false">
<div class="p-5">
<EChartPie v-if="typeChartData.values.length > 0" :data="typeChartData" :height="280" />
<div v-if="typeChartData.values.length > 0" class="flex flex-col gap-6 lg:flex-row lg:items-center">
<!-- 左侧饼图 -->
<div class="lg:w-1/2">
<EChartPie :data="typeChartData" :height="280" :show-legend="false" />
</div>
<!-- 右侧摘要列表 -->
<div class="lg:w-1/2">
<div class="space-y-3">
<div
v-for="(item, index) in typeSummary"
:key="index"
class="flex items-center gap-3"
>
<!-- 颜色标记 -->
<div
class="h-3 w-3 shrink-0 rounded-full"
:style="{ backgroundColor: getTypeColor(index) }"
/>
<!-- 类型名称 -->
<div class="min-w-20 shrink-0 text-sm text-gray-700 dark:text-gray-300">
{{ item.name }}
</div>
<!-- 进度条 -->
<div class="flex-1">
<div class="h-2 overflow-hidden rounded-full bg-gray-100 dark:bg-gray-800">
<div
class="h-full rounded-full transition-all"
:style="{
width: `${item.percentage}%`,
backgroundColor: getTypeColor(index),
}"
/>
</div>
</div>
<!-- 数量和占比 -->
<div class="shrink-0 text-right">
<span class="text-sm font-medium text-gray-900 dark:text-white">
{{ item.count.toLocaleString() }}
</span>
<span class="ml-1 text-xs text-gray-400">
({{ item.percentage }}%)
</span>
</div>
</div>
</div>
</div>
</div>
<div v-else class="flex h-48 items-center justify-center text-gray-400">
{{ t('noData') }}
</div>
+36
View File
@@ -0,0 +1,36 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
interface TimeFilter {
startTs?: number
endTs?: number
}
defineProps<{
sessionId: string
timeFilter?: TimeFilter
}>()
</script>
<template>
<div class="flex h-64 items-center justify-center">
<div class="text-center">
<UIcon name="i-heroicons-user-circle" class="mx-auto h-12 w-12 text-gray-300 dark:text-gray-600" />
<p class="mt-3 text-sm text-gray-400">{{ t('comingSoon') }}</p>
</div>
</div>
</template>
<i18n>
{
"zh-CN": {
"comingSoon": "对话画像功能开发中..."
},
"en-US": {
"comingSoon": "Chat portrait coming soon..."
}
}
</i18n>
+36
View File
@@ -0,0 +1,36 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
interface TimeFilter {
startTs?: number
endTs?: number
}
defineProps<{
sessionId: string
timeFilter?: TimeFilter
}>()
</script>
<template>
<div class="flex h-64 items-center justify-center">
<div class="text-center">
<UIcon name="i-heroicons-cloud" class="mx-auto h-12 w-12 text-gray-300 dark:text-gray-600" />
<p class="mt-3 text-sm text-gray-400">{{ t('comingSoon') }}</p>
</div>
</div>
</template>
<i18n>
{
"zh-CN": {
"comingSoon": "词云功能开发中..."
},
"en-US": {
"comingSoon": "Word cloud coming soon..."
}
}
</i18n>
+5
View File
@@ -0,0 +1,5 @@
// 视图组件统一导出
export { default as MessageView } from './MessageView.vue'
export { default as WordcloudView } from './WordcloudView.vue'
export { default as PortraitView } from './PortraitView.vue'
@@ -3,13 +3,7 @@ import { computed, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import type { AnalysisSession, MessageType } from '@/types/base'
import { getMessageTypeName } from '@/types/base'
import type {
MemberActivity,
HourlyActivity,
DailyActivity,
WeekdayActivity,
MonthlyActivity,
} from '@/types/analysis'
import type { MemberActivity, HourlyActivity, DailyActivity, WeekdayActivity } from '@/types/analysis'
import { EChartPie } from '@/components/charts'
import type { EChartPieData } from '@/components/charts'
import { SectionCard } from '@/components/UI'
@@ -17,7 +11,6 @@ import { useOverviewStatistics } from '@/composables/analysis/useOverviewStatist
import { useDailyTrend } from '@/composables/analysis/useDailyTrend'
import OverviewStatCards from '@/components/analysis/Overview/OverviewStatCards.vue'
import OverviewIdentityCard from '@/components/analysis/Overview/OverviewIdentityCard.vue'
import ActivityTimeDistribution from '@/components/analysis/Overview/ActivityTimeDistribution.vue'
import DailyTrendCard from '@/components/analysis/Overview/DailyTrendCard.vue'
const { t } = useI18n()
@@ -37,9 +30,8 @@ const props = defineProps<{
timeFilter?: { startTs?: number; endTs?: number }
}>()
// 星期活跃度数据
// 星期活跃度数据(用于统计信息计算)
const weekdayActivity = ref<WeekdayActivity[]>([])
const isLoadingWeekday = ref(false)
// 使用 Composables
const {
@@ -89,33 +81,13 @@ const memberChartData = computed<EChartPieData>(() => {
}
})
// 月份活跃度数据
const monthlyActivity = ref<MonthlyActivity[]>([])
const isLoadingMonthly = ref(false)
// 加载星期活跃度数据
// 加载星期活跃度数据(用于统计信息计算)
async function loadWeekdayActivity() {
if (!props.session.id) return
isLoadingWeekday.value = true
try {
weekdayActivity.value = await window.chatApi.getWeekdayActivity(props.session.id, props.timeFilter)
} catch (error) {
console.error('加载星期活跃度失败:', error)
} finally {
isLoadingWeekday.value = false
}
}
// 加载月份活跃度数据
async function loadMonthlyActivity() {
if (!props.session.id) return
isLoadingMonthly.value = true
try {
monthlyActivity.value = await window.chatApi.getMonthlyActivity(props.session.id, props.timeFilter)
} catch (error) {
console.error('加载月份活跃度失败:', error)
} finally {
isLoadingMonthly.value = false
}
}
@@ -124,7 +96,6 @@ watch(
() => [props.session.id, props.timeFilter],
() => {
loadWeekdayActivity()
loadMonthlyActivity()
},
{ immediate: true, deep: true }
)
@@ -173,17 +144,6 @@ watch(
</SectionCard>
</div>
<!-- 时间分布图表 -->
<ActivityTimeDistribution
:hourly-activity="hourlyActivity"
:weekday-activity="weekdayActivity"
:monthly-activity="monthlyActivity"
:is-loading-weekday="isLoadingWeekday"
:is-loading-monthly="isLoadingMonthly"
:weekday-names="weekdayNames"
:weekday-vs-weekend="weekdayVsWeekend"
/>
<!-- 每日消息趋势 -->
<DailyTrendCard :daily-activity="dailyActivity" :daily-chart-data="dailyChartData" />
</div>
@@ -0,0 +1,88 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { SubTabs } from '@/components/UI'
import { MessageView, WordcloudView, PortraitView } from '@/components/view'
const { t } = useI18n()
interface TimeFilter {
startTs?: number
endTs?: number
}
// Props
const props = defineProps<{
sessionId: string
timeFilter?: TimeFilter
}>()
// 子 Tab 配置
const subTabs = computed(() => [
{ id: 'message', label: t('message'), icon: 'i-heroicons-chat-bubble-left-right' },
{ id: 'wordcloud', label: t('wordcloud'), icon: 'i-heroicons-cloud' },
{ id: 'portrait', label: t('portrait'), icon: 'i-heroicons-user-circle' },
])
const activeSubTab = ref('message')
</script>
<template>
<div class="flex h-full flex-col">
<!-- Tab 导航 -->
<SubTabs v-model="activeSubTab" :items="subTabs" persist-key="groupViewTab" />
<!-- Tab 内容 -->
<div class="flex-1 min-h-0 overflow-auto">
<Transition name="fade" mode="out-in">
<!-- 消息 -->
<MessageView
v-if="activeSubTab === 'message'"
:session-id="props.sessionId"
:time-filter="props.timeFilter"
/>
<!-- 词云 -->
<WordcloudView
v-else-if="activeSubTab === 'wordcloud'"
:session-id="props.sessionId"
:time-filter="props.timeFilter"
/>
<!-- 对话画像 -->
<PortraitView
v-else-if="activeSubTab === 'portrait'"
:session-id="props.sessionId"
:time-filter="props.timeFilter"
/>
</Transition>
</div>
</div>
</template>
<style scoped>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.15s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
<i18n>
{
"zh-CN": {
"message": "消息",
"wordcloud": "词云",
"portrait": "对话画像"
},
"en-US": {
"message": "Messages",
"wordcloud": "Word Cloud",
"portrait": "Chat Portrait"
}
}
</i18n>
+8
View File
@@ -10,6 +10,7 @@ import CaptureButton from '@/components/common/CaptureButton.vue'
import UITabs from '@/components/UI/Tabs.vue'
import AITab from '@/components/analysis/AITab.vue'
import OverviewTab from './components/OverviewTab.vue'
import ViewTab from './components/ViewTab.vue'
import RankingTab from './components/RankingTab.vue'
import QuotesTab from './components/QuotesTab.vue'
import MemberTab from './components/MemberTab.vue'
@@ -57,6 +58,7 @@ const isInitialLoad = ref(true) // 用于跳过初始加载时的 watch 触发
// Tab 配置(带语言限制)
const allTabs = [
{ id: 'overview', labelKey: 'analysis.tabs.overview', icon: 'i-heroicons-chart-pie' },
{ id: 'view', labelKey: 'analysis.tabs.view', icon: 'i-heroicons-presentation-chart-bar' },
{ id: 'ranking', labelKey: 'analysis.tabs.ranking', icon: 'i-heroicons-trophy', feature: 'groupRanking' },
{ id: 'quotes', labelKey: 'analysis.tabs.groupQuotes', icon: 'i-heroicons-chat-bubble-bottom-center-text' },
{ id: 'members', labelKey: 'analysis.tabs.members', icon: 'i-heroicons-user-group' },
@@ -354,6 +356,12 @@ onMounted(() => {
:filtered-member-count="filteredMemberCount"
:time-filter="timeFilter"
/>
<ViewTab
v-else-if="activeTab === 'view'"
:key="'view-' + selectedYear"
:session-id="currentSessionId!"
:time-filter="timeFilter"
/>
<RankingTab
v-else-if="activeTab === 'ranking'"
:key="'ranking-' + selectedYear"
@@ -3,7 +3,7 @@ import { computed, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import type { AnalysisSession, MessageType } from '@/types/base'
import { getMessageTypeName } from '@/types/base'
import type { MemberActivity, HourlyActivity, DailyActivity, WeekdayActivity, MonthlyActivity } from '@/types/analysis'
import type { MemberActivity, HourlyActivity, DailyActivity, WeekdayActivity } from '@/types/analysis'
import { EChartPie } from '@/components/charts'
import type { EChartPieData } from '@/components/charts'
import { SectionCard } from '@/components/UI'
@@ -11,7 +11,6 @@ import { useOverviewStatistics } from '@/composables/analysis/useOverviewStatist
import { useDailyTrend } from '@/composables/analysis/useDailyTrend'
import OverviewStatCards from '@/components/analysis/Overview/OverviewStatCards.vue'
import OverviewIdentityCard from '@/components/analysis/Overview/OverviewIdentityCard.vue'
import ActivityTimeDistribution from '@/components/analysis/Overview/ActivityTimeDistribution.vue'
import DailyTrendCard from '@/components/analysis/Overview/DailyTrendCard.vue'
const { t } = useI18n()
@@ -29,9 +28,8 @@ const props = defineProps<{
timeFilter?: { startTs?: number; endTs?: number }
}>()
// 星期活跃度数据
// 星期活跃度数据(用于统计信息计算)
const weekdayActivity = ref<WeekdayActivity[]>([])
const isLoadingWeekday = ref(false)
// 使用 Composables
const {
@@ -99,33 +97,13 @@ const comparisonChartData = computed<EChartPieData>(() => {
}
})
// 月份活跃度数据
const monthlyActivity = ref<MonthlyActivity[]>([])
const isLoadingMonthly = ref(false)
// 加载星期活跃度数据
// 加载星期活跃度数据(用于统计信息计算)
async function loadWeekdayActivity() {
if (!props.session.id) return
isLoadingWeekday.value = true
try {
weekdayActivity.value = await window.chatApi.getWeekdayActivity(props.session.id, props.timeFilter)
} catch (error) {
console.error('加载星期活跃度失败:', error)
} finally {
isLoadingWeekday.value = false
}
}
// 加载月份活跃度数据
async function loadMonthlyActivity() {
if (!props.session.id) return
isLoadingMonthly.value = true
try {
monthlyActivity.value = await window.chatApi.getMonthlyActivity(props.session.id, props.timeFilter)
} catch (error) {
console.error('加载月份活跃度失败:', error)
} finally {
isLoadingMonthly.value = false
}
}
@@ -134,7 +112,6 @@ watch(
() => [props.session.id, props.timeFilter],
() => {
loadWeekdayActivity()
loadMonthlyActivity()
},
{ immediate: true, deep: true }
)
@@ -264,17 +241,6 @@ watch(
</SectionCard>
</div>
<!-- 时间分布图表 -->
<ActivityTimeDistribution
:hourly-activity="hourlyActivity"
:weekday-activity="weekdayActivity"
:monthly-activity="monthlyActivity"
:is-loading-weekday="isLoadingWeekday"
:is-loading-monthly="isLoadingMonthly"
:weekday-names="weekdayNames"
:weekday-vs-weekend="weekdayVsWeekend"
/>
<!-- 每日消息趋势 -->
<DailyTrendCard :daily-activity="dailyActivity" :daily-chart-data="dailyChartData" />
</div>
@@ -2,9 +2,7 @@
import { ref, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { SubTabs } from '@/components/UI'
import MessageView from './view/MessageView.vue'
import WordcloudView from './view/WordcloudView.vue'
import PortraitView from './view/PortraitView.vue'
import { MessageView, WordcloudView, PortraitView } from '@/components/view'
const { t } = useI18n()
@@ -88,4 +86,3 @@ const activeSubTab = ref('message')
}
}
</i18n>
@@ -1,48 +0,0 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
interface TimeFilter {
startTs?: number
endTs?: number
}
// Props
defineProps<{
sessionId: string
timeFilter?: TimeFilter
}>()
</script>
<template>
<div class="main-content flex h-full items-center justify-center p-6">
<div
class="flex h-full w-full items-center justify-center rounded-xl border-2 border-dashed border-gray-300 bg-gray-50 dark:border-gray-700 dark:bg-gray-900/50"
>
<div class="text-center">
<UIcon name="i-heroicons-user-circle" class="mx-auto h-12 w-12 text-gray-400" />
<p class="mt-3 text-sm font-medium text-gray-600 dark:text-gray-400">
{{ t('title') }}
</p>
<p class="mt-1 max-w-md px-4 text-sm text-gray-500">
{{ t('description') }}
</p>
</div>
</div>
</div>
</template>
<i18n>
{
"zh-CN": {
"title": "对话画像",
"description": "双方发言特征对比(消息长度、表情使用、消息类型等)"
},
"en-US": {
"title": "Chat Portrait",
"description": "Comparison of chat characteristics (message length, emoji usage, message types, etc.)"
}
}
</i18n>
@@ -1,48 +0,0 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
interface TimeFilter {
startTs?: number
endTs?: number
}
// Props
defineProps<{
sessionId: string
timeFilter?: TimeFilter
}>()
</script>
<template>
<div class="main-content flex h-full items-center justify-center p-6">
<div
class="flex h-full w-full items-center justify-center rounded-xl border-2 border-dashed border-gray-300 bg-gray-50 dark:border-gray-700 dark:bg-gray-900/50"
>
<div class="text-center">
<UIcon name="i-heroicons-cloud" class="mx-auto h-12 w-12 text-gray-400" />
<p class="mt-3 text-sm font-medium text-gray-600 dark:text-gray-400">
{{ t('title') }}
</p>
<p class="mt-1 max-w-md px-4 text-sm text-gray-500">
{{ t('description') }}
</p>
</div>
</div>
</div>
</template>
<i18n>
{
"zh-CN": {
"title": "词云视图",
"description": "双方高频词汇对比可视化"
},
"en-US": {
"title": "Word Cloud View",
"description": "Visual comparison of frequently used words between both parties"
}
}
</i18n>