khronoz commited on
Commit
bd6511e
·
1 Parent(s): ea7fe20

Add Backend API Status page and Status icon to Header

Browse files
frontend/app/components/header.tsx CHANGED
@@ -7,6 +7,7 @@ import { usePathname } from 'next/navigation';
7
  import { useTheme } from "next-themes";
8
  import { useEffect, useState, useRef } from "react";
9
  import { useMedia } from 'react-use';
 
10
  import logo from '../../public/smart-retrieval-logo.webp'
11
 
12
  interface NavLinkProps {
@@ -21,7 +22,7 @@ interface MobileMenuProps {
21
  }
22
 
23
  const MobileMenu: React.FC<MobileMenuProps> = ({ isOpen, onClose }) => {
24
- const isLargeScreen = useMedia('(min-width: 1024px)');
25
  const menuRef = useRef<HTMLDivElement | null>(null);
26
 
27
  useEffect(() => {
@@ -130,9 +131,33 @@ const NavLink: React.FC<NavLinkProps> = ({ href, children, onClick }) => {
130
  };
131
 
132
  export default function Header() {
133
- const isLargeScreen = useMedia('(min-width: 1024px)');
134
  const [mounted, setMounted] = useState(false);
135
  const { theme, setTheme } = useTheme();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
 
137
  useEffect(() => {
138
  setMounted(true);
@@ -224,10 +249,26 @@ export default function Header() {
224
  </NavLink>
225
  </div>
226
  <div className="flex items-center ml-auto">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
  {/* Toggle button with icon based on the theme */}
228
  <button
229
  onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
230
- className="flex items-center text-xl transition duration-300 ease-in-out transform hover:scale-125"
231
  title={`Toggle between dark & light mode (Current mode: ${theme})`}>
232
  {theme === 'light' ? (
233
  <span role="img" aria-label="sun emoji">
 
7
  import { useTheme } from "next-themes";
8
  import { useEffect, useState, useRef } from "react";
9
  import { useMedia } from 'react-use';
10
+ import useSWR from 'swr'
11
  import logo from '../../public/smart-retrieval-logo.webp'
12
 
13
  interface NavLinkProps {
 
22
  }
23
 
24
  const MobileMenu: React.FC<MobileMenuProps> = ({ isOpen, onClose }) => {
25
+ const isLargeScreen = useMedia('(min-width: 1024px)', false);
26
  const menuRef = useRef<HTMLDivElement | null>(null);
27
 
28
  useEffect(() => {
 
131
  };
132
 
133
  export default function Header() {
134
+ const isLargeScreen = useMedia('(min-width: 1024px)', false);
135
  const [mounted, setMounted] = useState(false);
136
  const { theme, setTheme } = useTheme();
137
+ // const [apiStatus, setApiStatus] = useState(false);
138
+ // Use SWR for API status fetching
139
+ const healthcheck_api = process.env.NEXT_PUBLIC_HEALTHCHECK_API;
140
+ const { data: apiStatus, error: apiError } = useSWR(healthcheck_api, async (url) => {
141
+ try {
142
+ // Fetch the data
143
+ const response = await fetch(url);
144
+ if (!response.ok) {
145
+ throw new Error(response.statusText || 'Unknown Error');
146
+ }
147
+ const data = await response.json();
148
+ return data;
149
+ } catch (error: any) {
150
+ console.error('Error fetching Backend API Status:', error.message);
151
+ throw error;
152
+ }
153
+ }, {
154
+ revalidateOnFocus: true, // Revalidate when the window gains focus
155
+ revalidateIfStale: true, // Revalidate if the data is stale
156
+ refreshInterval: 60000, // Revalidate every 60 seconds
157
+ });
158
+ if (apiError) {
159
+ console.error('[Header] Error fetching Backend API Status:', apiError.message);
160
+ }
161
 
162
  useEffect(() => {
163
  setMounted(true);
 
249
  </NavLink>
250
  </div>
251
  <div className="flex items-center ml-auto">
252
+ {/* Status Page Button/Indicator */}
253
+ <span className='flex items-center mr-1'>API:</span>
254
+ <NavLink href='/status'>
255
+ <div className="flex items-center mr-2 text-l transition duration-300 ease-in-out transform hover:scale-125">
256
+ {apiError ? (
257
+ <span role="img" aria-label="red circle">
258
+ 🔴
259
+ </span>
260
+ ) : (
261
+ <span role="img" aria-label="green circle">
262
+ 🟢
263
+ </span>
264
+ )}
265
+ </div>
266
+ </NavLink>
267
+ <span className="lg:text-lg font-nunito">|</span>
268
  {/* Toggle button with icon based on the theme */}
269
  <button
270
  onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
271
+ className="flex items-center ml-2 text-xl transition duration-300 ease-in-out transform hover:scale-125"
272
  title={`Toggle between dark & light mode (Current mode: ${theme})`}>
273
  {theme === 'light' ? (
274
  <span role="img" aria-label="sun emoji">
frontend/app/status/page.tsx ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import useSWR from 'swr';
4
+ import { NextPage } from 'next';
5
+ import { Button } from "@nextui-org/react";
6
+ import { IconSpinner } from '@/app/components/ui/icons';
7
+ import Header from "@/app/components/header";
8
+ import Main from "@/app/components/ui/main-container";
9
+
10
+ // Define the API endpoint
11
+ const healthcheck_api = process.env.NEXT_PUBLIC_HEALTHCHECK_API;
12
+
13
+ const StatusPage: NextPage = () => {
14
+
15
+ // Define a function to clear the cache
16
+ const clearCache = () => mutate(
17
+ () => true,
18
+ undefined,
19
+ )
20
+ // Use SWR hook to fetch data with caching and revalidation
21
+ const { data, error, isValidating, mutate } = useSWR(healthcheck_api, async (url) => {
22
+ try {
23
+ // Fetch the data
24
+ const response = await fetch(url);
25
+ if (!response.ok) {
26
+ throw new Error(response.statusText || 'Unknown Error');
27
+ }
28
+ const data = await response.json();
29
+ return data;
30
+ } catch (error: any) {
31
+ console.error('Error fetching Backend API Status:', error.message);
32
+ throw error;
33
+ }
34
+ }, {
35
+ revalidateOnFocus: true, // Revalidate when the window gains focus
36
+ revalidateIfStale: true, // Revalidate if the data is stale
37
+ refreshInterval: 60000, // Revalidate every 60 seconds
38
+ });
39
+ if (error) {
40
+ console.error('[status] Error fetching Backend API Status:', error.message);
41
+ }
42
+
43
+ const apiStatus = error ? '❌' : '✅';
44
+ const apiResponse = error ? '❌' : JSON.stringify(data, null, 2);
45
+
46
+ const checkApiStatus = async () => {
47
+ try {
48
+ // Invalidate the cache and trigger a revalidation
49
+ mutate();
50
+ } catch (error: any) {
51
+ console.error('Error fetching Backend API Status:', error.message);
52
+ }
53
+ };
54
+
55
+ return (
56
+ <Main>
57
+ <Header />
58
+ <div className="rounded-xl shadow-xl p-4 mb-8 max-w-5xl w-full">
59
+ <div className="max-w-2xl space-y-2 p-4">
60
+ <h1 className="text-xl font-bold">Backend API Status</h1>
61
+ <p>
62
+ <span className="font-bold">Status: </span>
63
+ <span>{isValidating ? (
64
+ <IconSpinner className="inline ml-2 animate-spin" />
65
+ ) : apiStatus}</span>
66
+ </p>
67
+ <p><span className="font-bold">Response Data: </span>{isValidating ? (
68
+ <IconSpinner className="inline ml-2 animate-spin" />
69
+ ) : apiResponse}</p>
70
+ <Button
71
+ onClick={checkApiStatus}
72
+ disabled={isValidating} // Disable the button when isValidating is true
73
+ className="flex text-center items-center text-l disabled:bg-orange-400 bg-blue-500 text-white px-6 py-3 rounded-md font-bold transition duration-300 ease-in-out transform hover:scale-105"
74
+ >
75
+ {isValidating ? (
76
+ <IconSpinner className="mr-2 animate-spin" />
77
+ ) : null}
78
+ Refresh Status
79
+ </Button>
80
+ </div>
81
+ </div>
82
+ </Main>
83
+ );
84
+ };
85
+
86
+ // StatusPage.getInitialProps = async () => {
87
+ // try {
88
+ // // Ensure healthcheck_api is defined before making the fetch request
89
+ // const healthcheck_api = process.env.NEXT_PUBLIC_HEALTHCHECK_API;
90
+
91
+ // if (!healthcheck_api) {
92
+ // throw new Error('NEXT_PUBLIC_HEALTHCHECK_API is not defined');
93
+ // }
94
+
95
+ // const response = await fetch(healthcheck_api);
96
+
97
+ // if (!response.ok) {
98
+ // throw new Error(response.statusText || 'Unknown Error');
99
+ // }
100
+
101
+ // const data = await response.json();
102
+ // return { initialStatus: data.status };
103
+ // } catch (error: any) {
104
+ // console.error('Error fetching Backend API Status:', error.message);
105
+ // throw error;
106
+ // }
107
+ // };
108
+
109
+
110
+ export default StatusPage;
frontend/package-lock.json CHANGED
@@ -28,7 +28,9 @@
28
  "remark-code-import": "^1.2.0",
29
  "remark-gfm": "^3.0.1",
30
  "remark-math": "^5.1.1",
 
31
  "supports-color": "^8.1.1",
 
32
  "tailwind-merge": "^2"
33
  },
34
  "devDependencies": {
@@ -114,6 +116,15 @@
114
  "node": ">=6.9.0"
115
  }
116
  },
 
 
 
 
 
 
 
 
 
117
  "node_modules/@emotion/is-prop-valid": {
118
  "version": "0.8.8",
119
  "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz",
@@ -175,6 +186,437 @@
175
  "tslib": "^2.4.0"
176
  }
177
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
  "node_modules/@internationalized/date": {
179
  "version": "3.5.0",
180
  "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.5.0.tgz",
@@ -3118,6 +3560,17 @@
3118
  }
3119
  }
3120
  },
 
 
 
 
 
 
 
 
 
 
 
3121
  "node_modules/any-promise": {
3122
  "version": "1.3.0",
3123
  "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
@@ -3726,6 +4179,14 @@
3726
  "node": ">=6"
3727
  }
3728
  },
 
 
 
 
 
 
 
 
3729
  "node_modules/detect-node-es": {
3730
  "version": "1.1.0",
3731
  "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
@@ -3926,9 +4387,9 @@
3926
  }
3927
  },
3928
  "node_modules/follow-redirects": {
3929
- "version": "1.15.3",
3930
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz",
3931
- "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==",
3932
  "funding": [
3933
  {
3934
  "type": "individual",
@@ -6891,6 +7352,20 @@
6891
  "compute-scroll-into-view": "^3.0.2"
6892
  }
6893
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6894
  "node_modules/seroval": {
6895
  "version": "0.12.4",
6896
  "resolved": "https://registry.npmjs.org/seroval/-/seroval-0.12.4.tgz",
@@ -6913,6 +7388,45 @@
6913
  "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
6914
  "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
6915
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6916
  "node_modules/simple-swizzle": {
6917
  "version": "0.2.2",
6918
  "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
@@ -7213,10 +7727,11 @@
7213
  }
7214
  },
7215
  "node_modules/swr": {
7216
- "version": "2.2.0",
7217
- "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.0.tgz",
7218
- "integrity": "sha512-AjqHOv2lAhkuUdIiBu9xbuettzAzWXmCEcLONNKJRba87WAefz8Ca9d6ds/SzrPc235n1IxWYdhJ2zF3MNUaoQ==",
7219
  "dependencies": {
 
7220
  "use-sync-external-store": "^1.2.0"
7221
  },
7222
  "peerDependencies": {
 
28
  "remark-code-import": "^1.2.0",
29
  "remark-gfm": "^3.0.1",
30
  "remark-math": "^5.1.1",
31
+ "sharp": "^0.33.2",
32
  "supports-color": "^8.1.1",
33
+ "swr": "^2.2.4",
34
  "tailwind-merge": "^2"
35
  },
36
  "devDependencies": {
 
116
  "node": ">=6.9.0"
117
  }
118
  },
119
+ "node_modules/@emnapi/runtime": {
120
+ "version": "0.45.0",
121
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-0.45.0.tgz",
122
+ "integrity": "sha512-Txumi3td7J4A/xTTwlssKieHKTGl3j4A1tglBx72auZ49YK7ePY6XZricgIg9mnZT4xPfA+UPCUdnhRuEFDL+w==",
123
+ "optional": true,
124
+ "dependencies": {
125
+ "tslib": "^2.4.0"
126
+ }
127
+ },
128
  "node_modules/@emotion/is-prop-valid": {
129
  "version": "0.8.8",
130
  "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz",
 
186
  "tslib": "^2.4.0"
187
  }
188
  },
189
+ "node_modules/@img/sharp-darwin-arm64": {
190
+ "version": "0.33.2",
191
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.2.tgz",
192
+ "integrity": "sha512-itHBs1rPmsmGF9p4qRe++CzCgd+kFYktnsoR1sbIAfsRMrJZau0Tt1AH9KVnufc2/tU02Gf6Ibujx+15qRE03w==",
193
+ "cpu": [
194
+ "arm64"
195
+ ],
196
+ "optional": true,
197
+ "os": [
198
+ "darwin"
199
+ ],
200
+ "engines": {
201
+ "glibc": ">=2.26",
202
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0",
203
+ "npm": ">=9.6.5",
204
+ "pnpm": ">=7.1.0",
205
+ "yarn": ">=3.2.0"
206
+ },
207
+ "funding": {
208
+ "url": "https://opencollective.com/libvips"
209
+ },
210
+ "optionalDependencies": {
211
+ "@img/sharp-libvips-darwin-arm64": "1.0.1"
212
+ }
213
+ },
214
+ "node_modules/@img/sharp-darwin-x64": {
215
+ "version": "0.33.2",
216
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.2.tgz",
217
+ "integrity": "sha512-/rK/69Rrp9x5kaWBjVN07KixZanRr+W1OiyKdXcbjQD6KbW+obaTeBBtLUAtbBsnlTTmWthw99xqoOS7SsySDg==",
218
+ "cpu": [
219
+ "x64"
220
+ ],
221
+ "optional": true,
222
+ "os": [
223
+ "darwin"
224
+ ],
225
+ "engines": {
226
+ "glibc": ">=2.26",
227
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0",
228
+ "npm": ">=9.6.5",
229
+ "pnpm": ">=7.1.0",
230
+ "yarn": ">=3.2.0"
231
+ },
232
+ "funding": {
233
+ "url": "https://opencollective.com/libvips"
234
+ },
235
+ "optionalDependencies": {
236
+ "@img/sharp-libvips-darwin-x64": "1.0.1"
237
+ }
238
+ },
239
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
240
+ "version": "1.0.1",
241
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.1.tgz",
242
+ "integrity": "sha512-kQyrSNd6lmBV7O0BUiyu/OEw9yeNGFbQhbxswS1i6rMDwBBSX+e+rPzu3S+MwAiGU3HdLze3PanQ4Xkfemgzcw==",
243
+ "cpu": [
244
+ "arm64"
245
+ ],
246
+ "optional": true,
247
+ "os": [
248
+ "darwin"
249
+ ],
250
+ "engines": {
251
+ "macos": ">=11",
252
+ "npm": ">=9.6.5",
253
+ "pnpm": ">=7.1.0",
254
+ "yarn": ">=3.2.0"
255
+ },
256
+ "funding": {
257
+ "url": "https://opencollective.com/libvips"
258
+ }
259
+ },
260
+ "node_modules/@img/sharp-libvips-darwin-x64": {
261
+ "version": "1.0.1",
262
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.1.tgz",
263
+ "integrity": "sha512-eVU/JYLPVjhhrd8Tk6gosl5pVlvsqiFlt50wotCvdkFGf+mDNBJxMh+bvav+Wt3EBnNZWq8Sp2I7XfSjm8siog==",
264
+ "cpu": [
265
+ "x64"
266
+ ],
267
+ "optional": true,
268
+ "os": [
269
+ "darwin"
270
+ ],
271
+ "engines": {
272
+ "macos": ">=10.13",
273
+ "npm": ">=9.6.5",
274
+ "pnpm": ">=7.1.0",
275
+ "yarn": ">=3.2.0"
276
+ },
277
+ "funding": {
278
+ "url": "https://opencollective.com/libvips"
279
+ }
280
+ },
281
+ "node_modules/@img/sharp-libvips-linux-arm": {
282
+ "version": "1.0.1",
283
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.1.tgz",
284
+ "integrity": "sha512-FtdMvR4R99FTsD53IA3LxYGghQ82t3yt0ZQ93WMZ2xV3dqrb0E8zq4VHaTOuLEAuA83oDawHV3fd+BsAPadHIQ==",
285
+ "cpu": [
286
+ "arm"
287
+ ],
288
+ "optional": true,
289
+ "os": [
290
+ "linux"
291
+ ],
292
+ "engines": {
293
+ "glibc": ">=2.28",
294
+ "npm": ">=9.6.5",
295
+ "pnpm": ">=7.1.0",
296
+ "yarn": ">=3.2.0"
297
+ },
298
+ "funding": {
299
+ "url": "https://opencollective.com/libvips"
300
+ }
301
+ },
302
+ "node_modules/@img/sharp-libvips-linux-arm64": {
303
+ "version": "1.0.1",
304
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.1.tgz",
305
+ "integrity": "sha512-bnGG+MJjdX70mAQcSLxgeJco11G+MxTz+ebxlz8Y3dxyeb3Nkl7LgLI0mXupoO+u1wRNx/iRj5yHtzA4sde1yA==",
306
+ "cpu": [
307
+ "arm64"
308
+ ],
309
+ "optional": true,
310
+ "os": [
311
+ "linux"
312
+ ],
313
+ "engines": {
314
+ "glibc": ">=2.26",
315
+ "npm": ">=9.6.5",
316
+ "pnpm": ">=7.1.0",
317
+ "yarn": ">=3.2.0"
318
+ },
319
+ "funding": {
320
+ "url": "https://opencollective.com/libvips"
321
+ }
322
+ },
323
+ "node_modules/@img/sharp-libvips-linux-s390x": {
324
+ "version": "1.0.1",
325
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.1.tgz",
326
+ "integrity": "sha512-3+rzfAR1YpMOeA2zZNp+aYEzGNWK4zF3+sdMxuCS3ey9HhDbJ66w6hDSHDMoap32DueFwhhs3vwooAB2MaK4XQ==",
327
+ "cpu": [
328
+ "s390x"
329
+ ],
330
+ "optional": true,
331
+ "os": [
332
+ "linux"
333
+ ],
334
+ "engines": {
335
+ "glibc": ">=2.28",
336
+ "npm": ">=9.6.5",
337
+ "pnpm": ">=7.1.0",
338
+ "yarn": ">=3.2.0"
339
+ },
340
+ "funding": {
341
+ "url": "https://opencollective.com/libvips"
342
+ }
343
+ },
344
+ "node_modules/@img/sharp-libvips-linux-x64": {
345
+ "version": "1.0.1",
346
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.1.tgz",
347
+ "integrity": "sha512-3NR1mxFsaSgMMzz1bAnnKbSAI+lHXVTqAHgc1bgzjHuXjo4hlscpUxc0vFSAPKI3yuzdzcZOkq7nDPrP2F8Jgw==",
348
+ "cpu": [
349
+ "x64"
350
+ ],
351
+ "optional": true,
352
+ "os": [
353
+ "linux"
354
+ ],
355
+ "engines": {
356
+ "glibc": ">=2.26",
357
+ "npm": ">=9.6.5",
358
+ "pnpm": ">=7.1.0",
359
+ "yarn": ">=3.2.0"
360
+ },
361
+ "funding": {
362
+ "url": "https://opencollective.com/libvips"
363
+ }
364
+ },
365
+ "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
366
+ "version": "1.0.1",
367
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.1.tgz",
368
+ "integrity": "sha512-5aBRcjHDG/T6jwC3Edl3lP8nl9U2Yo8+oTl5drd1dh9Z1EBfzUKAJFUDTDisDjUwc7N4AjnPGfCA3jl3hY8uDg==",
369
+ "cpu": [
370
+ "arm64"
371
+ ],
372
+ "optional": true,
373
+ "os": [
374
+ "linux"
375
+ ],
376
+ "engines": {
377
+ "musl": ">=1.2.2",
378
+ "npm": ">=9.6.5",
379
+ "pnpm": ">=7.1.0",
380
+ "yarn": ">=3.2.0"
381
+ },
382
+ "funding": {
383
+ "url": "https://opencollective.com/libvips"
384
+ }
385
+ },
386
+ "node_modules/@img/sharp-libvips-linuxmusl-x64": {
387
+ "version": "1.0.1",
388
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.1.tgz",
389
+ "integrity": "sha512-dcT7inI9DBFK6ovfeWRe3hG30h51cBAP5JXlZfx6pzc/Mnf9HFCQDLtYf4MCBjxaaTfjCCjkBxcy3XzOAo5txw==",
390
+ "cpu": [
391
+ "x64"
392
+ ],
393
+ "optional": true,
394
+ "os": [
395
+ "linux"
396
+ ],
397
+ "engines": {
398
+ "musl": ">=1.2.2",
399
+ "npm": ">=9.6.5",
400
+ "pnpm": ">=7.1.0",
401
+ "yarn": ">=3.2.0"
402
+ },
403
+ "funding": {
404
+ "url": "https://opencollective.com/libvips"
405
+ }
406
+ },
407
+ "node_modules/@img/sharp-linux-arm": {
408
+ "version": "0.33.2",
409
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.2.tgz",
410
+ "integrity": "sha512-Fndk/4Zq3vAc4G/qyfXASbS3HBZbKrlnKZLEJzPLrXoJuipFNNwTes71+Ki1hwYW5lch26niRYoZFAtZVf3EGA==",
411
+ "cpu": [
412
+ "arm"
413
+ ],
414
+ "optional": true,
415
+ "os": [
416
+ "linux"
417
+ ],
418
+ "engines": {
419
+ "glibc": ">=2.28",
420
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0",
421
+ "npm": ">=9.6.5",
422
+ "pnpm": ">=7.1.0",
423
+ "yarn": ">=3.2.0"
424
+ },
425
+ "funding": {
426
+ "url": "https://opencollective.com/libvips"
427
+ },
428
+ "optionalDependencies": {
429
+ "@img/sharp-libvips-linux-arm": "1.0.1"
430
+ }
431
+ },
432
+ "node_modules/@img/sharp-linux-arm64": {
433
+ "version": "0.33.2",
434
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.2.tgz",
435
+ "integrity": "sha512-pz0NNo882vVfqJ0yNInuG9YH71smP4gRSdeL09ukC2YLE6ZyZePAlWKEHgAzJGTiOh8Qkaov6mMIMlEhmLdKew==",
436
+ "cpu": [
437
+ "arm64"
438
+ ],
439
+ "optional": true,
440
+ "os": [
441
+ "linux"
442
+ ],
443
+ "engines": {
444
+ "glibc": ">=2.26",
445
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0",
446
+ "npm": ">=9.6.5",
447
+ "pnpm": ">=7.1.0",
448
+ "yarn": ">=3.2.0"
449
+ },
450
+ "funding": {
451
+ "url": "https://opencollective.com/libvips"
452
+ },
453
+ "optionalDependencies": {
454
+ "@img/sharp-libvips-linux-arm64": "1.0.1"
455
+ }
456
+ },
457
+ "node_modules/@img/sharp-linux-s390x": {
458
+ "version": "0.33.2",
459
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.2.tgz",
460
+ "integrity": "sha512-MBoInDXDppMfhSzbMmOQtGfloVAflS2rP1qPcUIiITMi36Mm5YR7r0ASND99razjQUpHTzjrU1flO76hKvP5RA==",
461
+ "cpu": [
462
+ "s390x"
463
+ ],
464
+ "optional": true,
465
+ "os": [
466
+ "linux"
467
+ ],
468
+ "engines": {
469
+ "glibc": ">=2.28",
470
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0",
471
+ "npm": ">=9.6.5",
472
+ "pnpm": ">=7.1.0",
473
+ "yarn": ">=3.2.0"
474
+ },
475
+ "funding": {
476
+ "url": "https://opencollective.com/libvips"
477
+ },
478
+ "optionalDependencies": {
479
+ "@img/sharp-libvips-linux-s390x": "1.0.1"
480
+ }
481
+ },
482
+ "node_modules/@img/sharp-linux-x64": {
483
+ "version": "0.33.2",
484
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.2.tgz",
485
+ "integrity": "sha512-xUT82H5IbXewKkeF5aiooajoO1tQV4PnKfS/OZtb5DDdxS/FCI/uXTVZ35GQ97RZXsycojz/AJ0asoz6p2/H/A==",
486
+ "cpu": [
487
+ "x64"
488
+ ],
489
+ "optional": true,
490
+ "os": [
491
+ "linux"
492
+ ],
493
+ "engines": {
494
+ "glibc": ">=2.26",
495
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0",
496
+ "npm": ">=9.6.5",
497
+ "pnpm": ">=7.1.0",
498
+ "yarn": ">=3.2.0"
499
+ },
500
+ "funding": {
501
+ "url": "https://opencollective.com/libvips"
502
+ },
503
+ "optionalDependencies": {
504
+ "@img/sharp-libvips-linux-x64": "1.0.1"
505
+ }
506
+ },
507
+ "node_modules/@img/sharp-linuxmusl-arm64": {
508
+ "version": "0.33.2",
509
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.2.tgz",
510
+ "integrity": "sha512-F+0z8JCu/UnMzg8IYW1TMeiViIWBVg7IWP6nE0p5S5EPQxlLd76c8jYemG21X99UzFwgkRo5yz2DS+zbrnxZeA==",
511
+ "cpu": [
512
+ "arm64"
513
+ ],
514
+ "optional": true,
515
+ "os": [
516
+ "linux"
517
+ ],
518
+ "engines": {
519
+ "musl": ">=1.2.2",
520
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0",
521
+ "npm": ">=9.6.5",
522
+ "pnpm": ">=7.1.0",
523
+ "yarn": ">=3.2.0"
524
+ },
525
+ "funding": {
526
+ "url": "https://opencollective.com/libvips"
527
+ },
528
+ "optionalDependencies": {
529
+ "@img/sharp-libvips-linuxmusl-arm64": "1.0.1"
530
+ }
531
+ },
532
+ "node_modules/@img/sharp-linuxmusl-x64": {
533
+ "version": "0.33.2",
534
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.2.tgz",
535
+ "integrity": "sha512-+ZLE3SQmSL+Fn1gmSaM8uFusW5Y3J9VOf+wMGNnTtJUMUxFhv+P4UPaYEYT8tqnyYVaOVGgMN/zsOxn9pSsO2A==",
536
+ "cpu": [
537
+ "x64"
538
+ ],
539
+ "optional": true,
540
+ "os": [
541
+ "linux"
542
+ ],
543
+ "engines": {
544
+ "musl": ">=1.2.2",
545
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0",
546
+ "npm": ">=9.6.5",
547
+ "pnpm": ">=7.1.0",
548
+ "yarn": ">=3.2.0"
549
+ },
550
+ "funding": {
551
+ "url": "https://opencollective.com/libvips"
552
+ },
553
+ "optionalDependencies": {
554
+ "@img/sharp-libvips-linuxmusl-x64": "1.0.1"
555
+ }
556
+ },
557
+ "node_modules/@img/sharp-wasm32": {
558
+ "version": "0.33.2",
559
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.2.tgz",
560
+ "integrity": "sha512-fLbTaESVKuQcpm8ffgBD7jLb/CQLcATju/jxtTXR1XCLwbOQt+OL5zPHSDMmp2JZIeq82e18yE0Vv7zh6+6BfQ==",
561
+ "cpu": [
562
+ "wasm32"
563
+ ],
564
+ "optional": true,
565
+ "dependencies": {
566
+ "@emnapi/runtime": "^0.45.0"
567
+ },
568
+ "engines": {
569
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0",
570
+ "npm": ">=9.6.5",
571
+ "pnpm": ">=7.1.0",
572
+ "yarn": ">=3.2.0"
573
+ },
574
+ "funding": {
575
+ "url": "https://opencollective.com/libvips"
576
+ }
577
+ },
578
+ "node_modules/@img/sharp-win32-ia32": {
579
+ "version": "0.33.2",
580
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.2.tgz",
581
+ "integrity": "sha512-okBpql96hIGuZ4lN3+nsAjGeggxKm7hIRu9zyec0lnfB8E7Z6p95BuRZzDDXZOl2e8UmR4RhYt631i7mfmKU8g==",
582
+ "cpu": [
583
+ "ia32"
584
+ ],
585
+ "optional": true,
586
+ "os": [
587
+ "win32"
588
+ ],
589
+ "engines": {
590
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0",
591
+ "npm": ">=9.6.5",
592
+ "pnpm": ">=7.1.0",
593
+ "yarn": ">=3.2.0"
594
+ },
595
+ "funding": {
596
+ "url": "https://opencollective.com/libvips"
597
+ }
598
+ },
599
+ "node_modules/@img/sharp-win32-x64": {
600
+ "version": "0.33.2",
601
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.2.tgz",
602
+ "integrity": "sha512-E4magOks77DK47FwHUIGH0RYWSgRBfGdK56kIHSVeB9uIS4pPFr4N2kIVsXdQQo4LzOsENKV5KAhRlRL7eMAdg==",
603
+ "cpu": [
604
+ "x64"
605
+ ],
606
+ "optional": true,
607
+ "os": [
608
+ "win32"
609
+ ],
610
+ "engines": {
611
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0",
612
+ "npm": ">=9.6.5",
613
+ "pnpm": ">=7.1.0",
614
+ "yarn": ">=3.2.0"
615
+ },
616
+ "funding": {
617
+ "url": "https://opencollective.com/libvips"
618
+ }
619
+ },
620
  "node_modules/@internationalized/date": {
621
  "version": "3.5.0",
622
  "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.5.0.tgz",
 
3560
  }
3561
  }
3562
  },
3563
+ "node_modules/ai/node_modules/swr": {
3564
+ "version": "2.2.0",
3565
+ "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.0.tgz",
3566
+ "integrity": "sha512-AjqHOv2lAhkuUdIiBu9xbuettzAzWXmCEcLONNKJRba87WAefz8Ca9d6ds/SzrPc235n1IxWYdhJ2zF3MNUaoQ==",
3567
+ "dependencies": {
3568
+ "use-sync-external-store": "^1.2.0"
3569
+ },
3570
+ "peerDependencies": {
3571
+ "react": "^16.11.0 || ^17.0.0 || ^18.0.0"
3572
+ }
3573
+ },
3574
  "node_modules/any-promise": {
3575
  "version": "1.3.0",
3576
  "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
 
4179
  "node": ">=6"
4180
  }
4181
  },
4182
+ "node_modules/detect-libc": {
4183
+ "version": "2.0.2",
4184
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz",
4185
+ "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==",
4186
+ "engines": {
4187
+ "node": ">=8"
4188
+ }
4189
+ },
4190
  "node_modules/detect-node-es": {
4191
  "version": "1.1.0",
4192
  "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
 
4387
  }
4388
  },
4389
  "node_modules/follow-redirects": {
4390
+ "version": "1.15.4",
4391
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
4392
+ "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
4393
  "funding": [
4394
  {
4395
  "type": "individual",
 
7352
  "compute-scroll-into-view": "^3.0.2"
7353
  }
7354
  },
7355
+ "node_modules/semver": {
7356
+ "version": "7.5.4",
7357
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
7358
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
7359
+ "dependencies": {
7360
+ "lru-cache": "^6.0.0"
7361
+ },
7362
+ "bin": {
7363
+ "semver": "bin/semver.js"
7364
+ },
7365
+ "engines": {
7366
+ "node": ">=10"
7367
+ }
7368
+ },
7369
  "node_modules/seroval": {
7370
  "version": "0.12.4",
7371
  "resolved": "https://registry.npmjs.org/seroval/-/seroval-0.12.4.tgz",
 
7388
  "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
7389
  "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
7390
  },
7391
+ "node_modules/sharp": {
7392
+ "version": "0.33.2",
7393
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.2.tgz",
7394
+ "integrity": "sha512-WlYOPyyPDiiM07j/UO+E720ju6gtNtHjEGg5vovUk1Lgxyjm2LFO+37Nt/UI3MMh2l6hxTWQWi7qk3cXJTutcQ==",
7395
+ "hasInstallScript": true,
7396
+ "dependencies": {
7397
+ "color": "^4.2.3",
7398
+ "detect-libc": "^2.0.2",
7399
+ "semver": "^7.5.4"
7400
+ },
7401
+ "engines": {
7402
+ "libvips": ">=8.15.1",
7403
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
7404
+ },
7405
+ "funding": {
7406
+ "url": "https://opencollective.com/libvips"
7407
+ },
7408
+ "optionalDependencies": {
7409
+ "@img/sharp-darwin-arm64": "0.33.2",
7410
+ "@img/sharp-darwin-x64": "0.33.2",
7411
+ "@img/sharp-libvips-darwin-arm64": "1.0.1",
7412
+ "@img/sharp-libvips-darwin-x64": "1.0.1",
7413
+ "@img/sharp-libvips-linux-arm": "1.0.1",
7414
+ "@img/sharp-libvips-linux-arm64": "1.0.1",
7415
+ "@img/sharp-libvips-linux-s390x": "1.0.1",
7416
+ "@img/sharp-libvips-linux-x64": "1.0.1",
7417
+ "@img/sharp-libvips-linuxmusl-arm64": "1.0.1",
7418
+ "@img/sharp-libvips-linuxmusl-x64": "1.0.1",
7419
+ "@img/sharp-linux-arm": "0.33.2",
7420
+ "@img/sharp-linux-arm64": "0.33.2",
7421
+ "@img/sharp-linux-s390x": "0.33.2",
7422
+ "@img/sharp-linux-x64": "0.33.2",
7423
+ "@img/sharp-linuxmusl-arm64": "0.33.2",
7424
+ "@img/sharp-linuxmusl-x64": "0.33.2",
7425
+ "@img/sharp-wasm32": "0.33.2",
7426
+ "@img/sharp-win32-ia32": "0.33.2",
7427
+ "@img/sharp-win32-x64": "0.33.2"
7428
+ }
7429
+ },
7430
  "node_modules/simple-swizzle": {
7431
  "version": "0.2.2",
7432
  "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
 
7727
  }
7728
  },
7729
  "node_modules/swr": {
7730
+ "version": "2.2.4",
7731
+ "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.4.tgz",
7732
+ "integrity": "sha512-njiZ/4RiIhoOlAaLYDqwz5qH/KZXVilRLvomrx83HjzCWTfa+InyfAjv05PSFxnmLzZkNO9ZfvgoqzAaEI4sGQ==",
7733
  "dependencies": {
7734
+ "client-only": "^0.0.1",
7735
  "use-sync-external-store": "^1.2.0"
7736
  },
7737
  "peerDependencies": {
frontend/package.json CHANGED
@@ -28,7 +28,9 @@
28
  "remark-code-import": "^1.2.0",
29
  "remark-gfm": "^3.0.1",
30
  "remark-math": "^5.1.1",
 
31
  "supports-color": "^8.1.1",
 
32
  "tailwind-merge": "^2"
33
  },
34
  "devDependencies": {
 
28
  "remark-code-import": "^1.2.0",
29
  "remark-gfm": "^3.0.1",
30
  "remark-math": "^5.1.1",
31
+ "sharp": "^0.33.2",
32
  "supports-color": "^8.1.1",
33
+ "swr": "^2.2.4",
34
  "tailwind-merge": "^2"
35
  },
36
  "devDependencies": {