import React, { useState, useEffect } from 'react';
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
ResponsiveContainer
} from 'recharts';
import { PROVIDERS_MAP } from '../../utils/providers';
import {
fetchAllModelData,
generateMonthlyData,
getTotalMonthlyData,
processDetailedModelData,
MonthlyActivity,
DetailedModelData,
convertToCSV
} from '../../utils/modelData';
import Link from 'next/link';
interface TrendProps {
monthlyData: MonthlyActivity[];
totalData: MonthlyActivity[];
detailedData: DetailedModelData[];
error?: string;
}
const COLORS = Object.fromEntries(
Object.entries(PROVIDERS_MAP).map(([provider, { color }]) => [provider, color])
);
COLORS['Total'] = '#4CAF50';
const CustomTooltip = ({ active, payload, label }: any) => {
if (active && payload && payload.length) {
return (
{label}
{payload.map((entry: any) => (
{entry.name}: {entry.value} models
))}
);
}
return null;
};
const formatDate = (dateStr: string) => {
const date = new Date(dateStr);
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
});
};
const TrendPage: React.FC = ({ monthlyData = [], totalData = [], detailedData = [], error }) => {
const [showTotal, setShowTotal] = useState(true);
const [selectedProviders, setSelectedProviders] = useState([]);
const [minLikes, setMinLikes] = useState(100);
const [isLoading, setIsLoading] = useState(false);
const [contentType, setContentType] = useState<'all' | 'models' | 'datasets'>('all');
const toggleProvider = (provider: string) => {
setSelectedProviders(prev =>
prev.includes(provider)
? prev.filter(p => p !== provider)
: [...prev, provider]
);
};
// Filter total data based on content type
const filteredTotalData = totalData.filter(d => {
if (contentType === 'all') {
return d.isDataset === null; // Show only combined counts for 'all'
}
return (contentType === 'datasets' && d.isDataset === true) ||
(contentType === 'models' && d.isDataset === false);
});
// Group data by provider
const providerData = Object.fromEntries(
Object.keys(PROVIDERS_MAP).map(provider => {
const providerMonthlyData = monthlyData.filter(d => {
if (contentType === 'all') {
return d.provider === provider; // Show all items for the provider
}
const matchesContentType = (contentType === 'datasets' && d.isDataset === true) ||
(contentType === 'models' && d.isDataset === false);
return d.provider === provider && matchesContentType;
});
// If showing all, aggregate the counts for each month
if (contentType === 'all') {
const aggregatedData: Record = {};
providerMonthlyData.forEach(d => {
if (!aggregatedData[d.date]) {
aggregatedData[d.date] = {
date: d.date,
count: 0,
provider: d.provider,
isDataset: null
};
}
aggregatedData[d.date].count += d.count;
});
return [provider, Object.values(aggregatedData).sort((a, b) => a.date.localeCompare(b.date))];
}
return [provider, providerMonthlyData || []];
})
);
// Filter and group detailed model data
const filteredModels = (detailedData || [])
.filter(model => {
const matchesContentType = contentType === 'all' ||
(contentType === 'datasets' && model.isDataset) ||
(contentType === 'models' && !model.isDataset);
return model.likes >= minLikes &&
(selectedProviders.length === 0 || selectedProviders.includes(model.provider)) &&
matchesContentType;
})
.reduce>((acc, model) => {
if (!acc[model.monthKey]) {
acc[model.monthKey] = [];
}
acc[model.monthKey].push(model);
return acc;
}, {});
useEffect(() => {
console.log('Content type changed:', contentType);
console.log('Selected providers:', selectedProviders);
console.log('Min likes:', minLikes);
}, [contentType, selectedProviders, minLikes]);
if (error) {
return (
Chinese AI Model Release Trends 📈
Error loading data: {error}
Please try again later.
);
}
return (
Chinese AI Open Source Trends 🤗
Track the growth of Chinese AI models and datasets over time
View Heatmap
Track the growth of Chinese AI models and datasets over time
setContentType('all')}
className={`px-4 py-2 rounded-lg ${
contentType === 'all'
? 'bg-blue-500 text-white'
: 'bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-300'
}`}
>
All
setContentType('models')}
className={`px-4 py-2 rounded-lg ${
contentType === 'models'
? 'bg-blue-500 text-white'
: 'bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-300'
}`}
>
Models
setContentType('datasets')}
className={`px-4 py-2 rounded-lg ${
contentType === 'datasets'
? 'bg-blue-500 text-white'
: 'bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-300'
}`}
>
Datasets
setShowTotal(!showTotal)}
className={`px-4 py-2 rounded-lg ${
showTotal
? 'bg-blue-500 text-white'
: 'bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-300'
}`}
>
{showTotal ? 'Hide Total' : 'Show Total'}
{Object.entries(PROVIDERS_MAP).map(([provider, { color }]) => (
toggleProvider(provider)}
className={`px-4 py-2 rounded-lg transition-colors ${
selectedProviders.includes(provider)
? 'text-white'
: 'text-gray-600 dark:text-gray-400'
}`}
style={{
backgroundColor: selectedProviders.includes(provider)
? color
: 'transparent'
}}
>
{provider}
))}
{/* Chart */}
} />
{/* Total Line */}
{showTotal && (
)}
{/* Provider Lines */}
{selectedProviders.map(provider => (
))}
{/* Download Button */}
{
// Get the currently visible data based on filters
const visibleData = [];
// Add provider data if showing
selectedProviders.forEach(provider => {
visibleData.push(...providerData[provider].map(d => ({ ...d, provider })));
});
// Add total data if showing
if (showTotal) {
visibleData.push(...filteredTotalData);
}
const csvContent = convertToCSV(visibleData);
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = `${contentType}_counts.csv`;
link.click();
URL.revokeObjectURL(link.href);
}}
>
Download CSV
{/* Major Releases Section */}
{Object.entries(filteredModels)
.map(([monthKey, models]) => ({
monthKey,
models,
sortKey: models[0]?.sortKey || '' // Use the first model's sortKey
}))
.sort((a, b) => b.sortKey.localeCompare(a.sortKey)) // Sort by sortKey in descending order
.map(({ monthKey, models }) => (
{monthKey}
{models
.sort((a, b) => b.likes - a.likes) // Sort models within each month by likes
.map(model => (
❤️ {model.likes}
{formatDate(model.createdAt)}
))}
))}
);
};
export const getStaticProps = async () => {
try {
const modelData = await fetchAllModelData();
const monthlyData = generateMonthlyData(modelData);
const totalData = getTotalMonthlyData(monthlyData);
const filteredModels = processDetailedModelData(modelData);
return {
props: {
monthlyData,
totalData,
filteredModels,
},
// Revalidate every 12 hours
revalidate: 43200,
};
} catch (error) {
console.error('Error in getStaticProps:', error);
return {
props: {
monthlyData: [],
totalData: [],
filteredModels: {},
},
// Retry more frequently if there was an error
revalidate: 3600,
};
}
};
export default TrendPage;