File size: 6,557 Bytes
3e887f8
574b4e5
3e887f8
 
09ab9c0
362fadb
 
 
574b4e5
362fadb
 
 
 
 
 
888a56b
362fadb
 
 
 
 
 
 
 
 
 
 
 
97f2719
 
09ab9c0
362fadb
97f2719
 
 
 
574b4e5
362fadb
97f2719
574b4e5
3e887f8
362fadb
 
 
 
 
 
 
 
 
 
 
 
 
 
888a56b
362fadb
 
 
97f2719
 
 
 
362fadb
 
 
 
 
97f2719
362fadb
 
 
 
97f2719
 
 
 
 
 
 
362fadb
97f2719
362fadb
 
574b4e5
97f2719
362fadb
 
97f2719
362fadb
 
 
 
 
 
97f2719
 
 
 
 
 
 
 
 
 
 
362fadb
574b4e5
 
97f2719
 
 
 
574b4e5
09ab9c0
9054fbb
 
3e887f8
97f2719
1465ed6
3e887f8
 
 
 
 
 
 
 
 
 
 
574b4e5
3e887f8
362fadb
 
 
3e887f8
 
 
 
 
 
 
2ce75b3
574b4e5
 
362fadb
 
574b4e5
362fadb
 
 
97f2719
362fadb
 
574b4e5
 
97f2719
3e887f8
9be25dc
97f2719
 
 
 
 
 
 
 
 
 
 
 
 
3e887f8
 
9be25dc
 
 
97f2719
574b4e5
97f2719
 
 
 
574b4e5
9be25dc
 
 
 
9054fbb
09ab9c0
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
import { useState } from "react";
import chartXkcd from "chart.xkcd";

const projectTypes = ["model", "dataset", "space"];

function transformLikesData(likesData) {
  // Step 1
  likesData.sort((a, b) => new Date(a.likedAt) - new Date(b.likedAt));

  // Step 2
  const cumulativeLikes = {};
  let cumulativeCount = 0;

  // Step 3
  likesData.forEach(like => {
    const date = like.likedAt
    cumulativeCount++;
    cumulativeLikes[date] = cumulativeCount;
  });

  // Step 4
  const transformedData = Object.keys(cumulativeLikes).map(date => ({
    x: date,
    y: cumulativeLikes[date].toString()
  }));

  return transformedData;
}

const datasets = [];
function App() {
  const [projectType, setProjectType] = useState("models");
  const [projectName, setProjectName] = useState("");
  const [hasGraph, setHasGraph] = useState(false);
  const [isLoading, setIsLoading] = useState(false);
  // const [datasets, setDatasets] = useState([]);

  const onSubmit = async () => {
    setIsLoading(true)
    const svg = document.querySelector('.line-chart')

    const res = await fetch(`https://huggingface.co/api/${projectType}/${projectName}/likers?expand[]=likeAt`)

    /**
     * Format:
     * [{"user": "timqian", "likedAt": "2021-07-01T00:00:00.000Z"}, {"user": "yy", "likedAt": "2021-07-02T00:00:00.000Z"}]
     */
    const likers = await res.json()


    let likeHistory = transformLikesData(likers)

    if (likeHistory.length > 40) {
      // sample 20 points
      const sampledLikeHistory = []
      const step = Math.floor(likeHistory.length / 20)
      for (let i = 0; i < likeHistory.length; i += step) {
        sampledLikeHistory.push(likeHistory[i])
      }
      // Add the last point if it's not included
      if (sampledLikeHistory[sampledLikeHistory.length - 1].x !== likeHistory[likeHistory.length - 1].x) {
        sampledLikeHistory.push(likeHistory[likeHistory.length - 1])
      }
      likeHistory = sampledLikeHistory
    }

    // if likeHistory is empty, show error message
    if (likeHistory.length === 0) {
      setIsLoading(false)
      alert("No like history found")
      return
    }

    datasets.push({
      label: projectName,
      data: likeHistory,
    })

    // draw chart in next tick
    // setTimeout(() => {
    new chartXkcd.XY(svg, {
      title: 'Like History',
      xLabel: 'Time',
      yLabel: 'Likes',
      data: {
        datasets,
      },
      options: {
        // unxkcdify: true,
        xTickCount: 3,
        yTickCount: 4,
        legendPosition: chartXkcd.config.positionType.upLeft,
        showLine: true,
        timeFormat: 'MM/DD/YYYY',
        dotSize: 0.5,
        dataColors: [
          "#FBBF24", // Warm Yellow
          "#60A5FA", // Light Blue
          "#14B8A6", // Teal
          "#A78BFA", // Soft Purple
          "#FF8C00", // Orange
          "#64748B", // Slate Gray
          "#FB7185", // Coral Pink
          "#6EE7B7", // Mint Green
          "#2563EB", // Deep Blue
          "#374151"  // Charcoal
        ]
      },
    });
    setHasGraph(true)
    setIsLoading(false)
    setProjectName("")
    // }, 0.2)
  }
  return (
    <div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-16">
      <div className="mx-auto max-w-3xl">
        <h1 className="text-sm font-light right-0 text-right text-gray-600">
          View the like history of a project on <span className="font-semibold">huggingface</span> <span className="text-lg">🤗</span>
        </h1>
        <div>
          <div className="relative mt-2 rounded-md shadow-sm">
            <div className="absolute inset-y-0 left-0 flex items-center">
              <label htmlFor="country" className="sr-only">
                Country
              </label>
              <select
                id="country"
                name="country"
                autoComplete="country"
                className="h-full rounded-md border-0 bg-transparent py-0 pl-3 pr-7 text-gray-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm"
                onChange={(e) => setProjectType(e.target.value)}
              >
                <option value="models">Model</option>
                <option value="datasets">Dataset</option>
                <option value="spaces">Space</option>
              </select>
            </div>
            <input
              type="text"
              name="phone-number"
              id="phone-number"
              className="block w-full rounded-md border-0 py-1.5 pl-24 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
              placeholder="openai/whisper-large"
              value={projectName}
              onChange={(e) => setProjectName(e.target.value)}
              onFocus={(e) => e.target.select()}
              onKeyDown={async (e) => {
                if (e.key === "Enter") {
                  try {
                    await onSubmit();
                  } catch (err) {
                    setIsLoading(false);
                    alert(`No like history found for ${projectName}, please check the name and try again`);
                  }
                }
              }}
              disabled={isLoading}
            />

            {
              isLoading &&
              <div className="absolute inset-y-0 right-0 flex items-center">
                <svg className="animate-spin h-5 w-5 mr-3 text-gray-400" viewBox="0 0 24 24">
                  <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
                  <path
                    className="opacity-75"
                    fill="currentColor"
                    d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z">
                  </path>
                </svg>
              </div>
            }
          </div>
        </div>



        <div className="mt-16 relative min-w-sm">
          <svg className="line-chart"></svg>
          {
            hasGraph &&
            <span className="text-slate-500 absolute bottom-0 right-8" style={{ fontFamily: "xkcd" }}>🤗 like-history.ai</span>
          }
        </div>

        <a className={`${!hasGraph ? "mt-64" : "mt-12"} flex gap-x-2 text-slate-600 justify-end items-center text-xl`} href="https://chromewebstore.google.com/detail/like-history/ockfibaidgopelphgdgcnfijdnhnmpek">
          <img className="w-6 inline" src="/extension.svg" /> Install the chrome extension
        </a>
      </div>
    </div>
  );
}

export default App;