nico-martin HF Staff commited on
Commit
82a3cd7
·
1 Parent(s): 6e10499

added model facts

Browse files
src/App.tsx CHANGED
@@ -11,22 +11,7 @@ function App() {
11
  const init = async () => {
12
  setIsInitializing(true);
13
  const t = Translator.getInstance();
14
- const loaded = new Map<string, number>();
15
- let newProgress = 0;
16
- await t.init((e) => {
17
- if (e.status === "progress") {
18
- loaded.set(e.file, e.loaded);
19
- const allLoaded = Array.from(loaded.values()).reduce(
20
- (acc: number, curr: number) => acc + curr,
21
- 0
22
- );
23
- const percentLoaded = Math.round((100 / Translator.size) * allLoaded);
24
- if (newProgress !== percentLoaded) {
25
- newProgress = percentLoaded;
26
- setProgress(newProgress);
27
- }
28
- }
29
- });
30
  setTranslator(t);
31
  setIsInitializing(false);
32
  };
 
11
  const init = async () => {
12
  setIsInitializing(true);
13
  const t = Translator.getInstance();
14
+ await t.init(setProgress);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  setTranslator(t);
16
  setIsInitializing(false);
17
  };
src/Initialize.tsx CHANGED
@@ -5,6 +5,7 @@ import Translator from "./ai/Translator.ts";
5
  import Card from "./theme/misc/Card.tsx";
6
  import Introduction from "./components/Introduction.tsx";
7
  import ModelLoader from "./components/ModelLoader.tsx";
 
8
 
9
  interface InitializeProps {
10
  progress: number;
@@ -19,13 +20,20 @@ export default function Initialize({
19
  isInitializing = false,
20
  className = "",
21
  }: InitializeProps) {
 
 
 
 
 
 
 
 
 
 
 
 
22
  return (
23
- <div
24
- className={cn(
25
- "max-w-2xl w-full mx-auto px-8 animate-fade-in-up",
26
- className
27
- )}
28
- >
29
  <Card className="flex flex-col items-center gap-6">
30
  {isInitializing ? (
31
  <ModelLoader className="w-full" progress={progress} />
@@ -34,7 +42,7 @@ export default function Initialize({
34
  <Introduction />
35
  <Button
36
  variant="primary"
37
- onClick={onInitialize}
38
  disabled={isInitializing}
39
  className="text-lg! w-full"
40
  >
 
5
  import Card from "./theme/misc/Card.tsx";
6
  import Introduction from "./components/Introduction.tsx";
7
  import ModelLoader from "./components/ModelLoader.tsx";
8
+ import { flushSync } from "react-dom";
9
 
10
  interface InitializeProps {
11
  progress: number;
 
20
  isInitializing = false,
21
  className = "",
22
  }: InitializeProps) {
23
+ const handleInitialize = async () => {
24
+ if (document.startViewTransition) {
25
+ document.startViewTransition(() => {
26
+ flushSync(() => {
27
+ onInitialize();
28
+ });
29
+ });
30
+ } else {
31
+ await onInitialize();
32
+ }
33
+ };
34
+
35
  return (
36
+ <div className={cn("max-w-2xl w-full mx-auto px-8", className)}>
 
 
 
 
 
37
  <Card className="flex flex-col items-center gap-6">
38
  {isInitializing ? (
39
  <ModelLoader className="w-full" progress={progress} />
 
42
  <Introduction />
43
  <Button
44
  variant="primary"
45
+ onClick={handleInitialize}
46
  disabled={isInitializing}
47
  className="text-lg! w-full"
48
  >
src/ai/Translator.ts CHANGED
@@ -1,5 +1,4 @@
1
  import { pipeline, DataType } from "@huggingface/transformers";
2
- import { ProgressCallback } from "../types/transformers.ts";
3
 
4
  class Translator {
5
  private static instance: Translator | null = null;
@@ -18,11 +17,28 @@ class Translator {
18
  return Translator.instance;
19
  }
20
 
21
- public async init(onProgress?: ProgressCallback) {
22
  if (this.pipeline) return;
23
 
 
 
 
24
  this.pipeline = await pipeline("text-generation", Translator.modelId, {
25
- progress_callback: onProgress,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  device: "webgpu",
27
  dtype: Translator.dtype,
28
  });
@@ -37,8 +53,6 @@ class Translator {
37
  throw new Error("Translator not initialized. Call init() first.");
38
  }
39
 
40
- console.log(`Translating from ${sourceLang} to ${targetLang}`, text);
41
-
42
  const messages = [
43
  {
44
  role: "user",
 
1
  import { pipeline, DataType } from "@huggingface/transformers";
 
2
 
3
  class Translator {
4
  private static instance: Translator | null = null;
 
17
  return Translator.instance;
18
  }
19
 
20
+ public async init(onProgress?: (progress: number) => void) {
21
  if (this.pipeline) return;
22
 
23
+ const loaded = new Map<string, number>();
24
+ let newProgress = 0;
25
+
26
  this.pipeline = await pipeline("text-generation", Translator.modelId, {
27
+ progress_callback: (e) => {
28
+ if (e.status === "progress") {
29
+ loaded.set(e.file, e.loaded);
30
+ const allLoaded = Array.from(loaded.values()).reduce(
31
+ (acc: number, curr: number) => acc + curr,
32
+ 0
33
+ );
34
+ const percentLoaded =
35
+ Math.round((100 / Translator.size) * allLoaded * 100) / 100;
36
+ if (newProgress !== percentLoaded) {
37
+ newProgress = percentLoaded;
38
+ onProgress(newProgress);
39
+ }
40
+ }
41
+ },
42
  device: "webgpu",
43
  dtype: Translator.dtype,
44
  });
 
53
  throw new Error("Translator not initialized. Call init() first.");
54
  }
55
 
 
 
56
  const messages = [
57
  {
58
  role: "user",
src/components/ModelLoader.tsx CHANGED
@@ -1,33 +1,79 @@
 
1
  import cn from "../utils/classnames.ts";
 
2
 
3
  export default function ModelLoader({
4
  progress,
5
  className = "",
 
6
  }: {
7
  progress: number;
8
  className?: string;
 
9
  }) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  return (
11
- <div className={cn("space-y-2", className)}>
12
- <div className="flex justify-between items-center">
13
- <span className="text-sm font-medium text-muted-foreground">
14
- Loading model...
15
- </span>
16
- <span className="text-sm font-medium text-primary animate-pulse-soft">
17
- {progress}%
18
- </span>
19
- </div>
20
- <div className="w-full bg-muted rounded-full h-2 overflow-hidden">
21
- <div
22
- className="bg-primary h-full transition-all duration-300 ease-out relative overflow-hidden"
23
- style={{ width: `${progress}%` }}
24
- >
25
- <div className="absolute inset-0 bg-linear-to-r from-transparent via-white/20 to-transparent animate-[shimmer_2s_infinite]" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  </div>
 
 
 
27
  </div>
28
- <p className="text-xs text-muted-foreground text-center">
29
- This may take a few moments. Please don't close the page.
30
- </p>
31
  </div>
32
  );
33
  }
 
1
+ import { CSSProperties, useState, useEffect } from "react";
2
  import cn from "../utils/classnames.ts";
3
+ import { MODEL_FACTS } from "../constants.ts";
4
 
5
  export default function ModelLoader({
6
  progress,
7
  className = "",
8
+ style,
9
  }: {
10
  progress: number;
11
  className?: string;
12
+ style?: CSSProperties;
13
  }) {
14
+ const [shuffledFacts, setShuffledFacts] = useState<readonly string[]>([]);
15
+ const [currentFactIndex, setCurrentFactIndex] = useState(0);
16
+ const [isTransitioning, setIsTransitioning] = useState(false);
17
+
18
+ // Shuffle facts on mount
19
+ useEffect(() => {
20
+ const shuffled = [...MODEL_FACTS].sort(() => Math.random() - 0.5);
21
+ setShuffledFacts(shuffled);
22
+ }, []);
23
+
24
+ // Rotate facts every 5 seconds
25
+ useEffect(() => {
26
+ if (shuffledFacts.length === 0) return;
27
+
28
+ const interval = setInterval(() => {
29
+ setIsTransitioning(true);
30
+
31
+ setTimeout(() => {
32
+ setCurrentFactIndex((prev) => (prev + 1) % shuffledFacts.length);
33
+ setIsTransitioning(false);
34
+ }, 400); // Half of transition duration for crossfade
35
+ }, 10_000);
36
+
37
+ return () => clearInterval(interval);
38
+ }, [shuffledFacts]);
39
+
40
  return (
41
+ <div className={cn("space-y-4", className)} style={style}>
42
+ {shuffledFacts.length > 0 && (
43
+ <div className="min-h-12 flex items-center justify-center">
44
+ <p
45
+ className={cn(
46
+ "text-lg text-center transition-opacity duration-400",
47
+ isTransitioning ? "opacity-0" : "opacity-100"
48
+ )}
49
+ >
50
+ {shuffledFacts[currentFactIndex]}
51
+ </p>
52
+ </div>
53
+ )}
54
+
55
+ {/* Progress */}
56
+ <div className="space-y-2 mt-8">
57
+ <div className="flex justify-between items-center">
58
+ <span className="text-sm font-medium text-muted-foreground">
59
+ Loading model...
60
+ </span>
61
+ <span className="text-sm text-primary animate-pulse-soft font-mono font-bold">
62
+ {progress.toFixed(2)}%
63
+ </span>
64
+ </div>
65
+ <div className="w-full bg-muted rounded-full h-2 overflow-hidden">
66
+ <div
67
+ className="bg-primary h-full transition-all duration-300 ease-out relative overflow-hidden"
68
+ style={{ width: `${progress}%` }}
69
+ >
70
+ <div className="absolute inset-0 bg-linear-to-r from-transparent via-white/20 to-transparent animate-[shimmer_2s_infinite]" />
71
+ </div>
72
  </div>
73
+ <p className="text-xs text-muted-foreground text-center">
74
+ This may take a few moments. Please don't close the page.
75
+ </p>
76
  </div>
 
 
 
77
  </div>
78
  );
79
  }
src/constants.ts CHANGED
@@ -70,3 +70,15 @@ export type LanguageCode = (typeof LANGUAGES_WITH_AUTO)[number]["code"];
70
  export const getLanguageName = (code: LanguageCode): string => {
71
  return LANGUAGES.find((lang) => lang.code === code)?.name || code;
72
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  export const getLanguageName = (code: LanguageCode): string => {
71
  return LANGUAGES.find((lang) => lang.code === code)?.name || code;
72
  };
73
+
74
+ export const MODEL_FACTS = [
75
+ "TranslateGemma 4B runs entirely in your browser. Your text never leaves your device.",
76
+ "Google's 4B model rivals the translation quality of models 3x its size, thanks to knowledge distilled from Gemini.",
77
+ "TranslateGemma's quality was refined using an ensemble of AI judges that score translations for accuracy and fluency.",
78
+ "Evaluated on 55 languages, but trained on nearly 500 language pairs, including many languages AI usually ignores.",
79
+ "Bengali has 300 million speakers but limited AI training data. TranslateGemma helps bridge this gap.",
80
+ "Built on Gemma 3, the same foundation Google uses across its AI products but fine-tuned specifically for translation.",
81
+ "The 4B model was designed for phones and edge devices. Google optimized it to run where cloud access isn't guaranteed.",
82
+ "Frozen embeddings during training helped TranslateGemma handle scripts and languages not in the training data.",
83
+ "TranslateGemma is fully open. Researchers can fine-tune it for their own languages, domains, or specialized terminology.",
84
+ ] as const;
src/index.css CHANGED
@@ -86,16 +86,6 @@ body {
86
  }
87
  }
88
 
89
- @keyframes float {
90
- 0%,
91
- 100% {
92
- transform: translateY(0px);
93
- }
94
- 50% {
95
- transform: translateY(-5px);
96
- }
97
- }
98
-
99
  @keyframes pulse-soft {
100
  0%,
101
  100% {
@@ -123,53 +113,6 @@ body {
123
  animation: fadeIn 0.6s ease-out forwards;
124
  }
125
 
126
- .animate-float {
127
- animation: float 3s ease-in-out infinite;
128
- }
129
-
130
  .animate-pulse-soft {
131
  animation: pulse-soft 2s ease-in-out infinite;
132
  }
133
-
134
- /* View Transitions */
135
- ::view-transition-old(initialize-content) {
136
- animation-duration: 0.2s;
137
- animation-timing-function: ease-out;
138
- animation-name: fade-out;
139
- mix-blend-mode: normal;
140
- overflow: hidden;
141
- }
142
-
143
- ::view-transition-new(initialize-content) {
144
- animation-duration: 0.6s;
145
- animation-timing-function: ease-in;
146
- animation-name: fade-in-after-scale;
147
- mix-blend-mode: normal;
148
- overflow: hidden;
149
- }
150
-
151
- ::view-transition-group(initialize-content) {
152
- animation-duration: 0.4s;
153
- animation-timing-function: ease-in-out;
154
- }
155
-
156
- @keyframes fade-out {
157
- from {
158
- opacity: 1;
159
- }
160
- to {
161
- opacity: 0;
162
- }
163
- }
164
-
165
- @keyframes fade-in-after-scale {
166
- 0% {
167
- opacity: 0;
168
- }
169
- 66% {
170
- opacity: 0;
171
- }
172
- 100% {
173
- opacity: 1;
174
- }
175
- }
 
86
  }
87
  }
88
 
 
 
 
 
 
 
 
 
 
 
89
  @keyframes pulse-soft {
90
  0%,
91
  100% {
 
113
  animation: fadeIn 0.6s ease-out forwards;
114
  }
115
 
 
 
 
 
116
  .animate-pulse-soft {
117
  animation: pulse-soft 2s ease-in-out infinite;
118
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/theme/misc/Card.tsx CHANGED
@@ -1,18 +1,20 @@
1
- import { ReactNode } from "react";
2
  import cn from "../../utils/classnames.ts";
3
 
4
  interface CardProps {
5
  className?: string;
6
  children?: ReactNode;
 
7
  }
8
 
9
- export default function Card({ className = "", children }: CardProps) {
10
  return (
11
  <div
12
  className={cn(
13
  "bg-white rounded-lg shadow-md p-8 border border-border",
14
  className
15
  )}
 
16
  >
17
  {children}
18
  </div>
 
1
+ import { ReactNode, CSSProperties } from "react";
2
  import cn from "../../utils/classnames.ts";
3
 
4
  interface CardProps {
5
  className?: string;
6
  children?: ReactNode;
7
+ style?: CSSProperties;
8
  }
9
 
10
+ export default function Card({ className = "", children, style }: CardProps) {
11
  return (
12
  <div
13
  className={cn(
14
  "bg-white rounded-lg shadow-md p-8 border border-border",
15
  className
16
  )}
17
+ style={style}
18
  >
19
  {children}
20
  </div>