Társila Samille commited on
Commit
211e55a
·
unverified ·
1 Parent(s): 17e4582

Updated template

Browse files
.eslintrc.cjs ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ module.exports = {
2
+ env: { browser: true, es2020: true, 'node': true },
3
+ extends: [
4
+ 'eslint:recommended',
5
+ 'plugin:react/recommended',
6
+ 'plugin:react/jsx-runtime',
7
+ 'plugin:react-hooks/recommended',
8
+ ],
9
+ parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
10
+ settings: { react: { version: '18.2' } },
11
+ plugins: ['react-refresh'],
12
+ rules: {
13
+ 'react-refresh/only-export-components': 'warn',
14
+ 'react/prop-types': 'off',
15
+ },
16
+ }
.gitignore ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ *.suo
21
+ *.ntvs*
22
+ *.njsproj
23
+ *.sln
24
+ *.sw?
README.md CHANGED
@@ -6,7 +6,7 @@ colorTo: yellow
6
  sdk: static
7
  pinned: false
8
  models:
9
- - Xenova/detr-resnet-50
10
  ---
11
 
12
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
6
  sdk: static
7
  pinned: false
8
  models:
9
+ - tarsssss/eng-jagoy-t5-001
10
  ---
11
 
12
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -3,27 +3,13 @@
3
 
4
  <head>
5
  <meta charset="UTF-8" />
6
- <link rel="stylesheet" href="style.css" />
7
-
8
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
9
- <title>Transformers.js - Object Detection</title>
10
  </head>
11
 
12
  <body>
13
- <h1>Object Detection w/ 🤗 Transformers.js</h1>
14
- <label id="container" for="upload">
15
- <svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
16
- <path fill="#000"
17
- d="M3.5 24.3a3 3 0 0 1-1.9-.8c-.5-.5-.8-1.2-.8-1.9V2.9c0-.7.3-1.3.8-1.9.6-.5 1.2-.7 2-.7h18.6c.7 0 1.3.2 1.9.7.5.6.7 1.2.7 2v18.6c0 .7-.2 1.4-.7 1.9a3 3 0 0 1-2 .8H3.6Zm0-2.7h18.7V2.9H3.5v18.7Zm2.7-2.7h13.3c.3 0 .5 0 .6-.3v-.7l-3.7-5a.6.6 0 0 0-.6-.2c-.2 0-.4 0-.5.3l-3.5 4.6-2.4-3.3a.6.6 0 0 0-.6-.3c-.2 0-.4.1-.5.3l-2.7 3.6c-.1.2-.2.4 0 .7.1.2.3.3.6.3Z">
18
- </path>
19
- </svg>
20
- Click to upload image
21
- <label id="example">(or try example)</label>
22
- </label>
23
- <label id="status">Loading model...</label>
24
- <input id="upload" type="file" accept="image/*" />
25
-
26
- <script src="index.js" type="module"></script>
27
  </body>
28
 
29
  </html>
 
3
 
4
  <head>
5
  <meta charset="UTF-8" />
 
 
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Transformers.js - Sample react application</title>
8
  </head>
9
 
10
  <body>
11
+ <div id="root"></div>
12
+ <script type="module" src="/src/main.jsx"></script>
 
 
 
 
 
 
 
 
 
 
 
 
13
  </body>
14
 
15
  </html>
index.js DELETED
@@ -1,79 +0,0 @@
1
- import { pipeline, env } from 'https://cdn.jsdelivr.net/npm/@xenova/transformers@2.10.1';
2
-
3
- // Since we will download the model from the Hugging Face Hub, we can skip the local model check
4
- env.allowLocalModels = false;
5
-
6
- // Reference the elements that we will need
7
- const status = document.getElementById('status');
8
- const fileUpload = document.getElementById('upload');
9
- const imageContainer = document.getElementById('container');
10
- const example = document.getElementById('example');
11
-
12
- const EXAMPLE_URL = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/city-streets.jpg';
13
-
14
- // Create a new object detection pipeline
15
- status.textContent = 'Loading model...';
16
- const detector = await pipeline('object-detection', 'Xenova/detr-resnet-50');
17
- status.textContent = 'Ready';
18
-
19
- example.addEventListener('click', (e) => {
20
- e.preventDefault();
21
- detect(EXAMPLE_URL);
22
- });
23
-
24
- fileUpload.addEventListener('change', function (e) {
25
- const file = e.target.files[0];
26
- if (!file) {
27
- return;
28
- }
29
-
30
- const reader = new FileReader();
31
-
32
- // Set up a callback when the file is loaded
33
- reader.onload = e2 => detect(e2.target.result);
34
-
35
- reader.readAsDataURL(file);
36
- });
37
-
38
-
39
- // Detect objects in the image
40
- async function detect(img) {
41
- imageContainer.innerHTML = '';
42
- imageContainer.style.backgroundImage = `url(${img})`;
43
-
44
- status.textContent = 'Analysing...';
45
- const output = await detector(img, {
46
- threshold: 0.5,
47
- percentage: true,
48
- });
49
- status.textContent = '';
50
- output.forEach(renderBox);
51
- }
52
-
53
- // Render a bounding box and label on the image
54
- function renderBox({ box, label }) {
55
- const { xmax, xmin, ymax, ymin } = box;
56
-
57
- // Generate a random color for the box
58
- const color = '#' + Math.floor(Math.random() * 0xFFFFFF).toString(16).padStart(6, 0);
59
-
60
- // Draw the box
61
- const boxElement = document.createElement('div');
62
- boxElement.className = 'bounding-box';
63
- Object.assign(boxElement.style, {
64
- borderColor: color,
65
- left: 100 * xmin + '%',
66
- top: 100 * ymin + '%',
67
- width: 100 * (xmax - xmin) + '%',
68
- height: 100 * (ymax - ymin) + '%',
69
- })
70
-
71
- // Draw label
72
- const labelElement = document.createElement('span');
73
- labelElement.textContent = label;
74
- labelElement.className = 'bounding-box-label';
75
- labelElement.style.backgroundColor = color;
76
-
77
- boxElement.appendChild(labelElement);
78
- imageContainer.appendChild(boxElement);
79
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
package.json ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "react-translator",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "lint": "eslint src --ext js,jsx --report-unused-disable-directives --max-warnings 0",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "@xenova/transformers": "^2.0.0",
14
+ "react": "^18.2.0",
15
+ "react-dom": "^18.2.0"
16
+ },
17
+ "devDependencies": {
18
+ "@types/react": "^18.0.28",
19
+ "@types/react-dom": "^18.0.11",
20
+ "@vitejs/plugin-react": "^4.0.0",
21
+ "eslint": "^8.38.0",
22
+ "eslint-plugin-react": "^7.32.2",
23
+ "eslint-plugin-react-hooks": "^4.6.0",
24
+ "eslint-plugin-react-refresh": "^0.3.4",
25
+ "vite": "^4.3.2"
26
+ }
27
+ }
src/App.css ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #root {
2
+ max-width: 1280px;
3
+ margin: 0 auto;
4
+ padding: 2rem;
5
+ text-align: center;
6
+ }
7
+
8
+ .language-container {
9
+ display: flex;
10
+ gap: 20px;
11
+ }
12
+
13
+ .textbox-container {
14
+ display: flex;
15
+ justify-content: center;
16
+ gap: 20px;
17
+ width: 800px;
18
+ }
19
+
20
+ .textbox-container>textarea, .language-selector {
21
+ width: 50%;
22
+ }
23
+
24
+ .language-selector>select {
25
+ width: 150px;
26
+ }
27
+
28
+ .progress-container {
29
+ position: relative;
30
+ font-size: 14px;
31
+ color: white;
32
+ background-color: #e9ecef;
33
+ border: solid 1px;
34
+ border-radius: 8px;
35
+ text-align: left;
36
+ overflow: hidden;
37
+ }
38
+
39
+ .progress-bar {
40
+ padding: 0 4px;
41
+ z-index: 0;
42
+ top: 0;
43
+ width: 1%;
44
+ height: 100%;
45
+ overflow: hidden;
46
+ background-color: #007bff;
47
+ white-space: nowrap;
48
+ }
49
+
50
+ .progress-text {
51
+ z-index: 2;
52
+ }
53
+
54
+ .selector-container {
55
+ display: flex;
56
+ gap: 20px;
57
+ }
58
+
59
+ .progress-bars-container {
60
+ padding: 8px;
61
+ height: 140px;
62
+ }
63
+
64
+ .container {
65
+ margin: 25px;
66
+ display: flex;
67
+ flex-direction: column;
68
+ gap: 10px;
69
+ }
src/App.jsx ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useEffect, useRef, useState } from 'react'
2
+ import LanguageSelector from './components/LanguageSelector';
3
+ import Progress from './components/Progress';
4
+
5
+ import './App.css'
6
+
7
+ function App() {
8
+
9
+ // Model loading
10
+ const [ready, setReady] = useState(null);
11
+ const [disabled, setDisabled] = useState(false);
12
+ const [progressItems, setProgressItems] = useState([]);
13
+
14
+ // Inputs and outputs
15
+ const [input, setInput] = useState('I love walking my dog.');
16
+ const [sourceLanguage, setSourceLanguage] = useState('eng_Latn');
17
+ const [targetLanguage, setTargetLanguage] = useState('fra_Latn');
18
+ const [output, setOutput] = useState('');
19
+
20
+ // Create a reference to the worker object.
21
+ const worker = useRef(null);
22
+
23
+ // We use the `useEffect` hook to setup the worker as soon as the `App` component is mounted.
24
+ useEffect(() => {
25
+ if (!worker.current) {
26
+ // Create the worker if it does not yet exist.
27
+ worker.current = new Worker(new URL('./worker.js', import.meta.url), {
28
+ type: 'module'
29
+ });
30
+ }
31
+
32
+ // Create a callback function for messages from the worker thread.
33
+ const onMessageReceived = (e) => {
34
+ switch (e.data.status) {
35
+ case 'initiate':
36
+ // Model file start load: add a new progress item to the list.
37
+ setReady(false);
38
+ setProgressItems(prev => [...prev, e.data]);
39
+ break;
40
+
41
+ case 'progress':
42
+ // Model file progress: update one of the progress items.
43
+ setProgressItems(
44
+ prev => prev.map(item => {
45
+ if (item.file === e.data.file) {
46
+ return { ...item, progress: e.data.progress }
47
+ }
48
+ return item;
49
+ })
50
+ );
51
+ break;
52
+
53
+ case 'done':
54
+ // Model file loaded: remove the progress item from the list.
55
+ setProgressItems(
56
+ prev => prev.filter(item => item.file !== e.data.file)
57
+ );
58
+ break;
59
+
60
+ case 'ready':
61
+ // Pipeline ready: the worker is ready to accept messages.
62
+ setReady(true);
63
+ break;
64
+
65
+ case 'update':
66
+ // Generation update: update the output text.
67
+ setOutput(e.data.output);
68
+ break;
69
+
70
+ case 'complete':
71
+ // Generation complete: re-enable the "Translate" button
72
+ setDisabled(false);
73
+ break;
74
+ }
75
+ };
76
+
77
+ // Attach the callback function as an event listener.
78
+ worker.current.addEventListener('message', onMessageReceived);
79
+
80
+ // Define a cleanup function for when the component is unmounted.
81
+ return () => worker.current.removeEventListener('message', onMessageReceived);
82
+ });
83
+
84
+ const translate = () => {
85
+ setDisabled(true);
86
+ worker.current.postMessage({
87
+ text: input,
88
+ src_lang: sourceLanguage,
89
+ tgt_lang: targetLanguage,
90
+ });
91
+ }
92
+
93
+ return (
94
+ <>
95
+ <h1>Transformers.js</h1>
96
+ <h2>ML-powered multilingual translation in React!</h2>
97
+
98
+ <div className='container'>
99
+ <div className='language-container'>
100
+ <LanguageSelector type={"Source"} defaultLanguage={"eng_Latn"} onChange={x => setSourceLanguage(x.target.value)} />
101
+ <LanguageSelector type={"Target"} defaultLanguage={"fra_Latn"} onChange={x => setTargetLanguage(x.target.value)} />
102
+ </div>
103
+
104
+ <div className='textbox-container'>
105
+ <textarea value={input} rows={3} onChange={e => setInput(e.target.value)}></textarea>
106
+ <textarea value={output} rows={3} readOnly></textarea>
107
+ </div>
108
+ </div>
109
+
110
+ <button disabled={disabled} onClick={translate}>Translate</button>
111
+
112
+ <div className='progress-bars-container'>
113
+ {ready === false && (
114
+ <label>Loading models... (only run once)</label>
115
+ )}
116
+ {progressItems.map(data => (
117
+ <div key={data.file}>
118
+ <Progress text={data.file} percentage={data.progress} />
119
+ </div>
120
+ ))}
121
+ </div>
122
+ </>
123
+ )
124
+ }
125
+
126
+ export default App
src/components/LanguageSelector.jsx ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ // The full list of languages in FLORES-200 is available here:
3
+ // https://github.com/facebookresearch/flores/blob/main/flores200/README.md#languages-in-flores-200
4
+
5
+ const LANGUAGES = {
6
+ English: "en",
7
+ French: "fr",
8
+ German: "de",
9
+ Spanish: "es",
10
+ Italian: "it",
11
+ Portuguese: "pt",
12
+ Dutch: "nl",
13
+ Russian: "ru",
14
+ Chinese: "zh",
15
+ Japanese: "ja",
16
+ Korean: "ko",
17
+ Arabic: "ar",
18
+ Hindi: "hi",
19
+ Bengali: "bn",
20
+ Punjabi: "pa",
21
+ Javanese: "jv",
22
+ Vietnamese: "vi",
23
+ "Bidaio Jagoi": "bj",
24
+ Indonesian: "id",
25
+ Malay: "ms",
26
+ };
27
+
28
+ export default function LanguageSelector({ type, onChange, defaultLanguage }) {
29
+ return (
30
+ <div className='language-selector'>
31
+ <label>{type}: </label>
32
+ <select onChange={onChange} defaultValue={defaultLanguage}>
33
+ {Object.entries(LANGUAGES).map(([key, value]) => {
34
+ return <option key={key} value={value}>{key}</option>
35
+ })}
36
+ </select>
37
+ </div>
38
+ )
39
+ }
src/components/Progress.jsx ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+
2
+ export default function Progress({ text, percentage }) {
3
+ percentage = percentage ?? 0;
4
+ return (
5
+ <div className="progress-container">
6
+ <div className='progress-bar' style={{ 'width': `${percentage}%` }}>{text} ({`${percentage.toFixed(2)}%`})</div>
7
+ </div>
8
+ );
9
+ }
src/index.css ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3
+ line-height: 1.5;
4
+ font-weight: 400;
5
+ color: #213547;
6
+ background-color: #ffffff;
7
+
8
+ font-synthesis: none;
9
+ text-rendering: optimizeLegibility;
10
+ -webkit-font-smoothing: antialiased;
11
+ -moz-osx-font-smoothing: grayscale;
12
+ -webkit-text-size-adjust: 100%;
13
+ }
14
+
15
+ body {
16
+ margin: 0;
17
+ display: flex;
18
+ place-items: center;
19
+ min-width: 320px;
20
+ min-height: 100vh;
21
+ }
22
+
23
+ h1 {
24
+ font-size: 3.2em;
25
+ line-height: 1;
26
+ }
27
+
28
+ h1,
29
+ h2 {
30
+ margin: 8px;
31
+ }
32
+
33
+ select {
34
+ padding: 0.3em;
35
+ cursor: pointer;
36
+ }
37
+
38
+ textarea {
39
+ padding: 0.6em;
40
+ }
41
+
42
+ button {
43
+ padding: 0.6em 1.2em;
44
+ cursor: pointer;
45
+ font-weight: 500;
46
+ }
47
+
48
+ button[disabled] {
49
+ cursor: not-allowed;
50
+ }
51
+
52
+ select,
53
+ textarea,
54
+ button {
55
+ border-radius: 8px;
56
+ border: 1px solid transparent;
57
+ font-size: 1em;
58
+ font-family: inherit;
59
+ background-color: #f9f9f9;
60
+ transition: border-color 0.25s;
61
+ }
62
+
63
+ select:hover,
64
+ textarea:hover,
65
+ button:not([disabled]):hover {
66
+ border-color: #646cff;
67
+ }
68
+
69
+ select:focus,
70
+ select:focus-visible,
71
+ textarea:focus,
72
+ textarea:focus-visible,
73
+ button:focus,
74
+ button:focus-visible {
75
+ outline: 4px auto -webkit-focus-ring-color;
76
+ }
src/main.jsx ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react'
2
+ import ReactDOM from 'react-dom/client'
3
+ import App from './App.jsx'
4
+ import './index.css'
5
+
6
+ ReactDOM.createRoot(document.getElementById('root')).render(
7
+ <React.StrictMode>
8
+ <App />
9
+ </React.StrictMode>,
10
+ )
src/worker.js ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import { pipeline } from '@xenova/transformers';
3
+
4
+ /**
5
+ * This class uses the Singleton pattern to ensure that only one instance of the
6
+ * pipeline is loaded. This is because loading the pipeline is an expensive
7
+ * operation and we don't want to do it every time we want to translate a sentence.
8
+ */
9
+ class MyTranslationPipeline {
10
+ static task = "translation";
11
+ static model = "tarsssss/eng-jagoy-t5-001";
12
+ static instance = null;
13
+
14
+ static async getInstance(progress_callback = null) {
15
+ if (this.instance === null) {
16
+ this.instance = pipeline(this.task, this.model, { progress_callback });
17
+ }
18
+
19
+ return this.instance;
20
+ }
21
+ }
22
+
23
+ // Listen for messages from the main thread
24
+ self.addEventListener('message', async (event) => {
25
+ // Retrieve the translation pipeline. When called for the first time,
26
+ // this will load the pipeline and save it for future use.
27
+ let translator = await MyTranslationPipeline.getInstance(x => {
28
+ // We also add a progress callback to the pipeline so that we can
29
+ // track model loading.
30
+ self.postMessage(x);
31
+ });
32
+
33
+ // Actually perform the translation
34
+ let output = await translator(event.data.text, {
35
+ tgt_lang: event.data.tgt_lang,
36
+ src_lang: event.data.src_lang,
37
+
38
+ // Allows for partial output
39
+ callback_function: x => {
40
+ self.postMessage({
41
+ status: 'update',
42
+ output: translator.tokenizer.decode(x[0].output_token_ids, { skip_special_tokens: true })
43
+ });
44
+ }
45
+ });
46
+
47
+ // Send the output back to the main thread
48
+ self.postMessage({
49
+ status: 'complete',
50
+ output: output,
51
+ });
52
+ });
style.css DELETED
@@ -1,76 +0,0 @@
1
- * {
2
- box-sizing: border-box;
3
- padding: 0;
4
- margin: 0;
5
- font-family: sans-serif;
6
- }
7
-
8
- html,
9
- body {
10
- height: 100%;
11
- }
12
-
13
- body {
14
- padding: 32px;
15
- }
16
-
17
- body,
18
- #container {
19
- display: flex;
20
- flex-direction: column;
21
- justify-content: center;
22
- align-items: center;
23
- }
24
-
25
- #container {
26
- position: relative;
27
- gap: 0.4rem;
28
-
29
- width: 640px;
30
- height: 640px;
31
- max-width: 100%;
32
- max-height: 100%;
33
-
34
- border: 2px dashed #D1D5DB;
35
- border-radius: 0.75rem;
36
- overflow: hidden;
37
- cursor: pointer;
38
- margin: 1rem;
39
-
40
- background-size: 100% 100%;
41
- background-position: center;
42
- background-repeat: no-repeat;
43
- font-size: 18px;
44
- }
45
-
46
- #upload {
47
- display: none;
48
- }
49
-
50
- svg {
51
- pointer-events: none;
52
- }
53
-
54
- #example {
55
- font-size: 14px;
56
- text-decoration: underline;
57
- cursor: pointer;
58
- }
59
-
60
- #example:hover {
61
- color: #2563EB;
62
- }
63
-
64
- .bounding-box {
65
- position: absolute;
66
- box-sizing: border-box;
67
- border: solid 2px;
68
- }
69
-
70
- .bounding-box-label {
71
- color: white;
72
- position: absolute;
73
- font-size: 12px;
74
- margin: -16px 0 0 -2px;
75
- padding: 1px;
76
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vite.config.js ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+
4
+ // https://vitejs.dev/config/
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ })