Commit 
							
							·
						
						c798beb
	
1
								Parent(s):
							
							17388ad
								
add path param for user page
Browse files- next.config.mjs +1 -1
- src/pages/[author]/index.tsx +109 -0
- src/pages/_document.tsx +1 -1
- src/pages/index.tsx +8 -84
- src/types/heatmap.ts +26 -0
- src/utils/calendar.ts +66 -0
- tailwind.config.ts +1 -1
    	
        next.config.mjs
    CHANGED
    
    | @@ -1,6 +1,6 @@ | |
| 1 | 
             
            /** @type {import('next').NextConfig} */
         | 
| 2 | 
             
            const nextConfig = {
         | 
| 3 | 
            -
              output:  | 
| 4 | 
             
              reactStrictMode: true,
         | 
| 5 | 
             
            };
         | 
| 6 |  | 
|  | |
| 1 | 
             
            /** @type {import('next').NextConfig} */
         | 
| 2 | 
             
            const nextConfig = {
         | 
| 3 | 
            +
              output: "standalone",
         | 
| 4 | 
             
              reactStrictMode: true,
         | 
| 5 | 
             
            };
         | 
| 6 |  | 
    	
        src/pages/[author]/index.tsx
    ADDED
    
    | @@ -0,0 +1,109 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            import React, { useState, useEffect } from "react";
         | 
| 2 | 
            +
            import ActivityCalendar from "react-activity-calendar";
         | 
| 3 | 
            +
            import { Tooltip } from "@mui/material";
         | 
| 4 | 
            +
            import { GetServerSidePropsContext } from "next";
         | 
| 5 | 
            +
            import {  OpenSourceHeatmapProps } from "../../types/heatmap";
         | 
| 6 | 
            +
            import { generateCalendarData } from "../../utils/calendar";
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            const DEFAULT_COLOR = "#DB4437";
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            const OpenSourceHeatmap: React.FC<OpenSourceHeatmapProps> = ({
         | 
| 11 | 
            +
              calendarData,
         | 
| 12 | 
            +
              providers,
         | 
| 13 | 
            +
            }) => {
         | 
| 14 | 
            +
              const [isLoading, setIsLoading] = useState(true);
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              useEffect(() => {
         | 
| 17 | 
            +
                if (calendarData && Object.keys(calendarData).length > 0) {
         | 
| 18 | 
            +
                  setIsLoading(false);
         | 
| 19 | 
            +
                }
         | 
| 20 | 
            +
              }, [calendarData]);
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              return (
         | 
| 23 | 
            +
                <div className="w-full max-w-screen-lg mx-auto p-4">
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  {isLoading ? (
         | 
| 26 | 
            +
                    <p className="text-center">Loading...</p>
         | 
| 27 | 
            +
                  ) : (
         | 
| 28 | 
            +
                    <div className="space-y-8">
         | 
| 29 | 
            +
                      {Object.entries(providers)
         | 
| 30 | 
            +
                        .sort(
         | 
| 31 | 
            +
                          ([keyA], [keyB]) =>
         | 
| 32 | 
            +
                            calendarData[keyB].reduce((sum, day) => sum + day.count, 0) -
         | 
| 33 | 
            +
                            calendarData[keyA].reduce((sum, day) => sum + day.count, 0),
         | 
| 34 | 
            +
                        )
         | 
| 35 | 
            +
                        .map(([providerName, { color }]) => (
         | 
| 36 | 
            +
                          <div key={providerName} className="flex flex-col items-center">
         | 
| 37 | 
            +
                            <h2 className="text-xl font-bold mb-4">{providerName}</h2>
         | 
| 38 | 
            +
                            <div className="w-full overflow-x-auto flex justify-center">
         | 
| 39 | 
            +
                              <ActivityCalendar
         | 
| 40 | 
            +
                                data={calendarData[providerName]}
         | 
| 41 | 
            +
                                theme={{
         | 
| 42 | 
            +
                                  dark: ["#161b22", color],
         | 
| 43 | 
            +
                                  light: ["#e0e0e0", color],
         | 
| 44 | 
            +
                                }}
         | 
| 45 | 
            +
                                hideTotalCount
         | 
| 46 | 
            +
                                renderBlock={(block, activity) => (
         | 
| 47 | 
            +
                                  <Tooltip
         | 
| 48 | 
            +
                                    title={`${activity.count} models created on ${activity.date}`}
         | 
| 49 | 
            +
                                    arrow
         | 
| 50 | 
            +
                                  >
         | 
| 51 | 
            +
                                    {block}
         | 
| 52 | 
            +
                                  </Tooltip>
         | 
| 53 | 
            +
                                )}
         | 
| 54 | 
            +
                              />
         | 
| 55 | 
            +
                            </div>
         | 
| 56 | 
            +
                          </div>
         | 
| 57 | 
            +
                        ))}
         | 
| 58 | 
            +
                    </div>
         | 
| 59 | 
            +
                  )}
         | 
| 60 | 
            +
                </div>
         | 
| 61 | 
            +
              );
         | 
| 62 | 
            +
            };
         | 
| 63 | 
            +
             | 
| 64 | 
            +
            export async function getServerSideProps(context: GetServerSidePropsContext) {
         | 
| 65 | 
            +
              const { author, color } = context.query;
         | 
| 66 | 
            +
             | 
| 67 | 
            +
              const authorColor = color || DEFAULT_COLOR;
         | 
| 68 | 
            +
             | 
| 69 | 
            +
              const providers = {
         | 
| 70 | 
            +
                [author as string]: {
         | 
| 71 | 
            +
                  color: authorColor as string,
         | 
| 72 | 
            +
                  authors: [author as string],
         | 
| 73 | 
            +
                },
         | 
| 74 | 
            +
              };
         | 
| 75 | 
            +
             | 
| 76 | 
            +
              try {
         | 
| 77 | 
            +
                const response = await fetch(
         | 
| 78 | 
            +
                  `https://huggingface.co/api/models?author=${author}&sort=createdAt&direction=-1`
         | 
| 79 | 
            +
                );
         | 
| 80 | 
            +
                const data = await response.json();
         | 
| 81 | 
            +
                
         | 
| 82 | 
            +
                const modelData = data.map((item: any) => ({
         | 
| 83 | 
            +
                  createdAt: item.createdAt,
         | 
| 84 | 
            +
                  id: item.id,
         | 
| 85 | 
            +
                }));
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                const calendarData = generateCalendarData(modelData, providers);
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                return {
         | 
| 90 | 
            +
                  props: {
         | 
| 91 | 
            +
                    calendarData,
         | 
| 92 | 
            +
                    color: authorColor,
         | 
| 93 | 
            +
                    providers,
         | 
| 94 | 
            +
                  },
         | 
| 95 | 
            +
                };
         | 
| 96 | 
            +
              } catch (error) {
         | 
| 97 | 
            +
                console.error("Error fetching data:", error);
         | 
| 98 | 
            +
                return {
         | 
| 99 | 
            +
                  props: {
         | 
| 100 | 
            +
                    calendarData: {},
         | 
| 101 | 
            +
                    color: authorColor,
         | 
| 102 | 
            +
                    providers,
         | 
| 103 | 
            +
                  },
         | 
| 104 | 
            +
                };
         | 
| 105 | 
            +
              }
         | 
| 106 | 
            +
            }
         | 
| 107 | 
            +
             | 
| 108 | 
            +
             | 
| 109 | 
            +
            export default OpenSourceHeatmap;
         | 
    	
        src/pages/_document.tsx
    CHANGED
    
    | @@ -10,4 +10,4 @@ export default function Document() { | |
| 10 | 
             
                  </body>
         | 
| 11 | 
             
                </Html>
         | 
| 12 | 
             
              );
         | 
| 13 | 
            -
            }
         | 
|  | |
| 10 | 
             
                  </body>
         | 
| 11 | 
             
                </Html>
         | 
| 12 | 
             
              );
         | 
| 13 | 
            +
            }
         | 
    	
        src/pages/index.tsx
    CHANGED
    
    | @@ -1,11 +1,8 @@ | |
| 1 | 
             
            import React, { useState, useEffect } from 'react';
         | 
| 2 | 
             
            import ActivityCalendar from "react-activity-calendar";
         | 
| 3 | 
             
            import { Tooltip } from '@mui/material';
         | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 6 | 
            -
              color: string;
         | 
| 7 | 
            -
              authors: string[];
         | 
| 8 | 
            -
            }
         | 
| 9 |  | 
| 10 | 
             
            const PROVIDERS_MAP: Record<string, ProviderInfo> = {
         | 
| 11 | 
             
              "Mistral AI": { color: "#ff7000", authors: ["mistralai"] },
         | 
| @@ -24,77 +21,6 @@ const PROVIDERS_MAP: Record<string, ProviderInfo> = { | |
| 24 | 
             
              "Stability AI": { color: "#A020F0", authors: ["stabilityai"] },
         | 
| 25 | 
             
            };
         | 
| 26 |  | 
| 27 | 
            -
            interface ModelData {
         | 
| 28 | 
            -
              createdAt: string;
         | 
| 29 | 
            -
              id: string;
         | 
| 30 | 
            -
            }
         | 
| 31 | 
            -
             | 
| 32 | 
            -
            interface Activity {
         | 
| 33 | 
            -
              date: string;
         | 
| 34 | 
            -
              count: number;
         | 
| 35 | 
            -
              level: number;
         | 
| 36 | 
            -
            }
         | 
| 37 | 
            -
             | 
| 38 | 
            -
            interface CalendarData {
         | 
| 39 | 
            -
              [key: string]: Activity[];
         | 
| 40 | 
            -
            }
         | 
| 41 | 
            -
             | 
| 42 | 
            -
            const generateCalendarData = (modelData: ModelData[]): CalendarData => {
         | 
| 43 | 
            -
              const data: Record<string, Activity[]> = Object.fromEntries(
         | 
| 44 | 
            -
                Object.keys(PROVIDERS_MAP).map(provider => [provider, []])
         | 
| 45 | 
            -
              );
         | 
| 46 | 
            -
             | 
| 47 | 
            -
              const today = new Date();
         | 
| 48 | 
            -
              const startDate = new Date(today);
         | 
| 49 | 
            -
              startDate.setMonth(today.getMonth() - 11);
         | 
| 50 | 
            -
              startDate.setDate(1);
         | 
| 51 | 
            -
             | 
| 52 | 
            -
              // create a map to store counts for each provider and date
         | 
| 53 | 
            -
              const countMap: Record<string, Record<string, number>> = {};
         | 
| 54 | 
            -
             | 
| 55 | 
            -
              modelData.forEach(item => {
         | 
| 56 | 
            -
                const dateString = item.createdAt.split('T')[0];
         | 
| 57 | 
            -
                Object.entries(PROVIDERS_MAP).forEach(([provider, { authors }]) => {
         | 
| 58 | 
            -
                  if (authors.some(author => item.id.startsWith(author))) {
         | 
| 59 | 
            -
                    countMap[provider] = countMap[provider] || {};
         | 
| 60 | 
            -
                    countMap[provider][dateString] = (countMap[provider][dateString] || 0) + 1;
         | 
| 61 | 
            -
                  }
         | 
| 62 | 
            -
                });
         | 
| 63 | 
            -
              });
         | 
| 64 | 
            -
             | 
| 65 | 
            -
              // fill in with 0s for days with no activity
         | 
| 66 | 
            -
              for (let d = new Date(startDate); d <= today; d.setDate(d.getDate() + 1)) {
         | 
| 67 | 
            -
                const dateString = d.toISOString().split('T')[0];
         | 
| 68 | 
            -
                
         | 
| 69 | 
            -
                Object.entries(PROVIDERS_MAP).forEach(([provider]) => {
         | 
| 70 | 
            -
                  const count = countMap[provider]?.[dateString] || 0;
         | 
| 71 | 
            -
                  data[provider].push({ date: dateString, count, level: 0 });
         | 
| 72 | 
            -
                });
         | 
| 73 | 
            -
              }
         | 
| 74 | 
            -
             | 
| 75 | 
            -
              // calculate average counts for each provider
         | 
| 76 | 
            -
              const avgCounts = Object.fromEntries(
         | 
| 77 | 
            -
                Object.entries(data).map(([provider, days]) => [
         | 
| 78 | 
            -
                  provider,
         | 
| 79 | 
            -
                  days.reduce((sum, day) => sum + day.count, 0) / days.length || 0
         | 
| 80 | 
            -
                ])
         | 
| 81 | 
            -
              );
         | 
| 82 | 
            -
             | 
| 83 | 
            -
              // assign levels based on count relative to average
         | 
| 84 | 
            -
              Object.entries(data).forEach(([provider, days]) => {
         | 
| 85 | 
            -
                const avgCount = avgCounts[provider];
         | 
| 86 | 
            -
                days.forEach(day => {
         | 
| 87 | 
            -
                  day.level = 
         | 
| 88 | 
            -
                    day.count === 0 ? 0 :
         | 
| 89 | 
            -
                    day.count <= avgCount * 0.5 ? 1 :
         | 
| 90 | 
            -
                    day.count <= avgCount ? 2 :
         | 
| 91 | 
            -
                    day.count <= avgCount * 1.5 ? 3 : 4;
         | 
| 92 | 
            -
                });
         | 
| 93 | 
            -
              });
         | 
| 94 | 
            -
             | 
| 95 | 
            -
              return data;
         | 
| 96 | 
            -
            }
         | 
| 97 | 
            -
             | 
| 98 | 
             
            export async function getStaticProps() {
         | 
| 99 | 
             
              try {
         | 
| 100 | 
             
                const allAuthors = Object.values(PROVIDERS_MAP).flatMap(({ authors }) => authors);
         | 
| @@ -111,12 +37,13 @@ export async function getStaticProps() { | |
| 111 | 
             
                  })
         | 
| 112 | 
             
                );
         | 
| 113 |  | 
| 114 | 
            -
                const flatModelData = allModelData.flat();
         | 
| 115 | 
            -
                const calendarData = generateCalendarData(flatModelData);
         | 
| 116 |  | 
| 117 | 
             
                return {
         | 
| 118 | 
             
                  props: {
         | 
| 119 | 
             
                    calendarData,
         | 
|  | |
| 120 | 
             
                  },
         | 
| 121 | 
             
                  revalidate: 3600, // regenerate every hour
         | 
| 122 | 
             
                };
         | 
| @@ -125,17 +52,14 @@ export async function getStaticProps() { | |
| 125 | 
             
                return {
         | 
| 126 | 
             
                  props: {
         | 
| 127 | 
             
                    calendarData: {},
         | 
|  | |
| 128 | 
             
                  },
         | 
| 129 | 
             
                  revalidate: 60, // retry after 1 minute if there was an error
         | 
| 130 | 
             
                };
         | 
| 131 | 
             
              }
         | 
| 132 | 
             
            }
         | 
| 133 |  | 
| 134 | 
            -
             | 
| 135 | 
            -
              calendarData: CalendarData;
         | 
| 136 | 
            -
            }
         | 
| 137 | 
            -
             | 
| 138 | 
            -
            const OpenSourceHeatmap: React.FC<OpenSourceHeatmapProps> = ({ calendarData }) => {
         | 
| 139 | 
             
              const [isLoading, setIsLoading] = useState(true);
         | 
| 140 |  | 
| 141 | 
             
              useEffect(() => {
         | 
| @@ -164,7 +88,7 @@ const OpenSourceHeatmap: React.FC<OpenSourceHeatmapProps> = ({ calendarData }) = | |
| 164 | 
             
                    <p className="text-center">Loading...</p>
         | 
| 165 | 
             
                  ) : (
         | 
| 166 | 
             
                    <div className="space-y-8">
         | 
| 167 | 
            -
                      {Object.entries( | 
| 168 | 
             
                        .sort(([keyA], [keyB]) => 
         | 
| 169 | 
             
                          calendarData[keyB].reduce((sum, day) => sum + day.count, 0) -
         | 
| 170 | 
             
                          calendarData[keyA].reduce((sum, day) => sum + day.count, 0)
         | 
|  | |
| 1 | 
             
            import React, { useState, useEffect } from 'react';
         | 
| 2 | 
             
            import ActivityCalendar from "react-activity-calendar";
         | 
| 3 | 
             
            import { Tooltip } from '@mui/material';
         | 
| 4 | 
            +
            import { generateCalendarData } from "../utils/calendar";
         | 
| 5 | 
            +
            import { OpenSourceHeatmapProps, ProviderInfo, ModelData, CalendarData } from "../types/heatmap";
         | 
|  | |
|  | |
|  | |
| 6 |  | 
| 7 | 
             
            const PROVIDERS_MAP: Record<string, ProviderInfo> = {
         | 
| 8 | 
             
              "Mistral AI": { color: "#ff7000", authors: ["mistralai"] },
         | 
|  | |
| 21 | 
             
              "Stability AI": { color: "#A020F0", authors: ["stabilityai"] },
         | 
| 22 | 
             
            };
         | 
| 23 |  | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 24 | 
             
            export async function getStaticProps() {
         | 
| 25 | 
             
              try {
         | 
| 26 | 
             
                const allAuthors = Object.values(PROVIDERS_MAP).flatMap(({ authors }) => authors);
         | 
|  | |
| 37 | 
             
                  })
         | 
| 38 | 
             
                );
         | 
| 39 |  | 
| 40 | 
            +
                const flatModelData: ModelData[] = allModelData.flat();
         | 
| 41 | 
            +
                const calendarData = generateCalendarData(flatModelData, PROVIDERS_MAP);
         | 
| 42 |  | 
| 43 | 
             
                return {
         | 
| 44 | 
             
                  props: {
         | 
| 45 | 
             
                    calendarData,
         | 
| 46 | 
            +
                    providers: PROVIDERS_MAP,
         | 
| 47 | 
             
                  },
         | 
| 48 | 
             
                  revalidate: 3600, // regenerate every hour
         | 
| 49 | 
             
                };
         | 
|  | |
| 52 | 
             
                return {
         | 
| 53 | 
             
                  props: {
         | 
| 54 | 
             
                    calendarData: {},
         | 
| 55 | 
            +
                    providers: PROVIDERS_MAP,
         | 
| 56 | 
             
                  },
         | 
| 57 | 
             
                  revalidate: 60, // retry after 1 minute if there was an error
         | 
| 58 | 
             
                };
         | 
| 59 | 
             
              }
         | 
| 60 | 
             
            }
         | 
| 61 |  | 
| 62 | 
            +
            const OpenSourceHeatmap: React.FC<OpenSourceHeatmapProps> = ({ calendarData, providers }) => {
         | 
|  | |
|  | |
|  | |
|  | |
| 63 | 
             
              const [isLoading, setIsLoading] = useState(true);
         | 
| 64 |  | 
| 65 | 
             
              useEffect(() => {
         | 
|  | |
| 88 | 
             
                    <p className="text-center">Loading...</p>
         | 
| 89 | 
             
                  ) : (
         | 
| 90 | 
             
                    <div className="space-y-8">
         | 
| 91 | 
            +
                      {Object.entries(providers)
         | 
| 92 | 
             
                        .sort(([keyA], [keyB]) => 
         | 
| 93 | 
             
                          calendarData[keyB].reduce((sum, day) => sum + day.count, 0) -
         | 
| 94 | 
             
                          calendarData[keyA].reduce((sum, day) => sum + day.count, 0)
         | 
    	
        src/types/heatmap.ts
    ADDED
    
    | @@ -0,0 +1,26 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            export interface ProviderInfo {
         | 
| 2 | 
            +
              color: string;
         | 
| 3 | 
            +
              authors: string[];
         | 
| 4 | 
            +
            }
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            export interface ModelData {
         | 
| 7 | 
            +
              createdAt: string;
         | 
| 8 | 
            +
              id: string;
         | 
| 9 | 
            +
            }
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            export interface Activity {
         | 
| 12 | 
            +
              date: string;
         | 
| 13 | 
            +
              count: number;
         | 
| 14 | 
            +
              level: number;
         | 
| 15 | 
            +
            }
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            export interface CalendarData {
         | 
| 18 | 
            +
              [key: string]: Activity[];
         | 
| 19 | 
            +
            }
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            export interface OpenSourceHeatmapProps {
         | 
| 22 | 
            +
              calendarData: CalendarData;
         | 
| 23 | 
            +
              author: string;
         | 
| 24 | 
            +
              color: string;
         | 
| 25 | 
            +
              providers: Record<string, ProviderInfo>;
         | 
| 26 | 
            +
            }
         | 
    	
        src/utils/calendar.ts
    ADDED
    
    | @@ -0,0 +1,66 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            import { ModelData, ProviderInfo, CalendarData, Activity } from "../types/heatmap";
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            export const generateCalendarData = (
         | 
| 4 | 
            +
                modelData: ModelData[],
         | 
| 5 | 
            +
                providers: Record<string, ProviderInfo>,
         | 
| 6 | 
            +
              ): CalendarData => {
         | 
| 7 | 
            +
                const data: Record<string, Activity[]> = Object.fromEntries(
         | 
| 8 | 
            +
                  Object.keys(providers).map((provider) => [provider, []]),
         | 
| 9 | 
            +
                );
         | 
| 10 | 
            +
              
         | 
| 11 | 
            +
                const today = new Date();
         | 
| 12 | 
            +
                const startDate = new Date(today);
         | 
| 13 | 
            +
                startDate.setMonth(today.getMonth() - 11);
         | 
| 14 | 
            +
                startDate.setDate(1);
         | 
| 15 | 
            +
              
         | 
| 16 | 
            +
                // create a map to store counts for each provider and date
         | 
| 17 | 
            +
                const countMap: Record<string, Record<string, number>> = {};
         | 
| 18 | 
            +
              
         | 
| 19 | 
            +
                modelData.forEach((item) => {
         | 
| 20 | 
            +
                  const dateString = item.createdAt.split("T")[0];
         | 
| 21 | 
            +
                  Object.entries(providers).forEach(([provider, { authors }]) => {
         | 
| 22 | 
            +
                    if (authors.some((author) => item.id.startsWith(author))) {
         | 
| 23 | 
            +
                      countMap[provider] = countMap[provider] || {};
         | 
| 24 | 
            +
                      countMap[provider][dateString] =
         | 
| 25 | 
            +
                        (countMap[provider][dateString] || 0) + 1;
         | 
| 26 | 
            +
                    }
         | 
| 27 | 
            +
                  });
         | 
| 28 | 
            +
                });
         | 
| 29 | 
            +
              
         | 
| 30 | 
            +
                // fill in with 0s for days with no activity
         | 
| 31 | 
            +
                for (let d = new Date(startDate); d <= today; d.setDate(d.getDate() + 1)) {
         | 
| 32 | 
            +
                  const dateString = d.toISOString().split("T")[0];
         | 
| 33 | 
            +
              
         | 
| 34 | 
            +
                  Object.entries(providers).forEach(([provider]) => {
         | 
| 35 | 
            +
                    const count = countMap[provider]?.[dateString] || 0;
         | 
| 36 | 
            +
                    data[provider].push({ date: dateString, count, level: 0 });
         | 
| 37 | 
            +
                  });
         | 
| 38 | 
            +
                }
         | 
| 39 | 
            +
              
         | 
| 40 | 
            +
                // calculate average counts for each provider
         | 
| 41 | 
            +
                const avgCounts = Object.fromEntries(
         | 
| 42 | 
            +
                  Object.entries(data).map(([provider, days]) => [
         | 
| 43 | 
            +
                    provider,
         | 
| 44 | 
            +
                    days.reduce((sum, day) => sum + day.count, 0) / days.length || 0,
         | 
| 45 | 
            +
                  ]),
         | 
| 46 | 
            +
                );
         | 
| 47 | 
            +
              
         | 
| 48 | 
            +
                // assign levels based on count relative to average
         | 
| 49 | 
            +
                Object.entries(data).forEach(([provider, days]) => {
         | 
| 50 | 
            +
                  const avgCount = avgCounts[provider];
         | 
| 51 | 
            +
                  days.forEach((day) => {
         | 
| 52 | 
            +
                    day.level =
         | 
| 53 | 
            +
                      day.count === 0
         | 
| 54 | 
            +
                        ? 0
         | 
| 55 | 
            +
                        : day.count <= avgCount * 0.5
         | 
| 56 | 
            +
                          ? 1
         | 
| 57 | 
            +
                          : day.count <= avgCount
         | 
| 58 | 
            +
                            ? 2
         | 
| 59 | 
            +
                            : day.count <= avgCount * 1.5
         | 
| 60 | 
            +
                              ? 3
         | 
| 61 | 
            +
                              : 4;
         | 
| 62 | 
            +
                  });
         | 
| 63 | 
            +
                });
         | 
| 64 | 
            +
              
         | 
| 65 | 
            +
                return data;
         | 
| 66 | 
            +
              };
         | 
    	
        tailwind.config.ts
    CHANGED
    
    | @@ -17,4 +17,4 @@ const config: Config = { | |
| 17 | 
             
              },
         | 
| 18 | 
             
              plugins: [],
         | 
| 19 | 
             
            };
         | 
| 20 | 
            -
            export default config;
         | 
|  | |
| 17 | 
             
              },
         | 
| 18 | 
             
              plugins: [],
         | 
| 19 | 
             
            };
         | 
| 20 | 
            +
            export default config;
         | 

