Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions dashboard/src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,10 @@
"charts": {
"growthTrend": "Growth Trend",
"growthTrendDesc": "Daily memory creation volume",
"growthTrendNoData": "No memory creation data yet",
"memoryCategories": "Memory Categories",
"memoryCategoriesDesc": "Distribution by classification",
"memoryCategoriesNoData": "No memory categories yet",
"hotMemories": "Hot Memories",
"hotMemoriesDesc": "Top retrieved records by access count",
"contentSnippet": "Content Snippet",
Expand Down
2 changes: 2 additions & 0 deletions dashboard/src/i18n/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,10 @@
"charts": {
"growthTrend": "增长趋势",
"growthTrendDesc": "每日记忆创建量",
"growthTrendNoData": "暂无记忆创建数据",
"memoryCategories": "记忆分类",
"memoryCategoriesDesc": "按分类分布",
"memoryCategoriesNoData": "暂无记忆分类数据",
"hotMemories": "热门记忆",
"hotMemoriesDesc": "按访问次数排序的热门记录",
"contentSnippet": "内容摘要",
Expand Down
131 changes: 81 additions & 50 deletions dashboard/src/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
RefreshCcw,
TrendingUp,
} from "lucide-react";
import { useState } from "react";
import { useState, type ReactNode } from "react";
import { useTranslation } from "react-i18next";
import {
Bar,
Expand Down Expand Up @@ -251,10 +251,12 @@ function OverviewPage() {
const trendData = Object.entries(stats.growth_trend)
.sort()
.map(([date, count]) => ({ date, count }));
const hasTrendData = trendData.some((item) => item.count > 0);

const ageData = Object.entries(stats.age_distribution).map(
([name, value]) => ({ name, value }),
);
const hasTypeData = typeData.some((item) => item.value > 0);

const dynamicChartConfig = typeData.reduce((acc, curr) => {
acc[curr.name] = {
Expand Down Expand Up @@ -368,30 +370,37 @@ function OverviewPage() {
<CardDescription>{t("dashboard.charts.growthTrendDesc")}</CardDescription>
</CardHeader>
<CardContent>
<ChartContainer config={chartConfig} className="h-[300px] w-full">
<LineChart
data={trendData}
margin={{ top: 20, left: 12, right: 12 }}
>
<CartesianGrid vertical={false} strokeDasharray="3 3" />
<XAxis
dataKey="date"
tickLine={false}
axisLine={false}
tickMargin={8}
minTickGap={32}
/>
<YAxis tickLine={false} axisLine={false} tickMargin={8} />
<ChartTooltip content={<ChartTooltipContent />} />
<Line
type="monotone"
dataKey="count"
stroke="var(--color-count)"
strokeWidth={2}
dot={false}
/>
</LineChart>
</ChartContainer>
{hasTrendData ? (
<ChartContainer config={chartConfig} className="h-[300px] w-full">
<LineChart
data={trendData}
margin={{ top: 20, left: 12, right: 12 }}
>
<CartesianGrid vertical={false} strokeDasharray="3 3" />
<XAxis
dataKey="date"
tickLine={false}
axisLine={false}
tickMargin={8}
minTickGap={32}
/>
<YAxis tickLine={false} axisLine={false} tickMargin={8} />
<ChartTooltip content={<ChartTooltipContent />} />
<Line
type="monotone"
dataKey="count"
stroke="var(--color-count)"
strokeWidth={2}
dot={false}
/>
</LineChart>
</ChartContainer>
) : (
<EmptyChartState
icon={<TrendingUp className="size-6" />}
message={t("dashboard.charts.growthTrendNoData")}
/>
)}
</CardContent>
</Card>
</ErrorBoundary>
Expand All @@ -407,30 +416,37 @@ function OverviewPage() {
<CardDescription>{t("dashboard.charts.memoryCategoriesDesc")}</CardDescription>
</CardHeader>
<CardContent>
<ChartContainer
config={dynamicChartConfig}
className="h-[300px] w-full"
>
<PieChart>
<Pie
data={typeData}
dataKey="value"
nameKey="name"
innerRadius={60}
outerRadius={80}
strokeWidth={5}
>
{typeData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.fill} />
))}
</Pie>
<ChartTooltip content={<ChartTooltipContent hideLabel />} />
<ChartLegend
content={<ChartLegendContent nameKey="name" />}
className="-translate-y-2 flex-wrap gap-2 [&>*]:basis-1/4 [&>*]:justify-center"
/>
</PieChart>
</ChartContainer>
{hasTypeData ? (
<ChartContainer
config={dynamicChartConfig}
className="h-[300px] w-full"
>
<PieChart>
<Pie
data={typeData}
dataKey="value"
nameKey="name"
innerRadius={60}
outerRadius={80}
strokeWidth={5}
>
{typeData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.fill} />
))}
</Pie>
<ChartTooltip content={<ChartTooltipContent hideLabel />} />
<ChartLegend
content={<ChartLegendContent nameKey="name" />}
className="-translate-y-2 flex-wrap gap-2 [&>*]:basis-1/4 [&>*]:justify-center"
/>
</PieChart>
</ChartContainer>
) : (
<EmptyChartState
icon={<BarChart3 className="size-6" />}
message={t("dashboard.charts.memoryCategoriesNoData")}
/>
)}
</CardContent>
</Card>
</ErrorBoundary>
Expand Down Expand Up @@ -542,7 +558,7 @@ function StatCard({
value,
description,
}: {
icon: React.ReactNode;
icon: ReactNode;
label: string;
value: string;
description: string;
Expand All @@ -562,3 +578,18 @@ function StatCard({
</Card>
);
}

function EmptyChartState({
icon,
message,
}: {
icon: ReactNode;
message: string;
}) {
return (
<div className="h-[300px] w-full flex flex-col items-center justify-center gap-3 rounded-md border border-dashed text-muted-foreground">
<div className="rounded-full bg-muted p-3">{icon}</div>
<p className="text-sm">{message}</p>
</div>
);
}
Loading