Transformers.js 文档
构建 React 应用程序
并获得增强的文档体验
开始使用
构建 React 应用程序
在本教程中,我们将构建一个简单的 React 应用程序,该程序使用 Transformers.js 执行多语言翻译!最终产品看起来像这样
实用链接
前提条件
步骤 1:初始化项目
在本教程中,我们将使用 Vite 来初始化我们的项目。Vite 是一个构建工具,使我们能够以最少的配置快速设置 React 应用程序。在您的终端中运行以下命令
npm create vite@latest react-translator -- --template react
如果提示安装 create-vite
,请键入 y 并按 Enter 键。
接下来,进入项目目录并安装必要的开发依赖项
cd react-translator
npm install
为了测试我们的应用程序是否正常工作,我们可以运行以下命令
npm run dev
访问终端中显示的 URL(例如,https://#:5173/)应显示默认的“React + Vite”着陆页。您可以通过在终端中按 Ctrl + C 来停止开发服务器。
步骤 2:安装和配置 Transformers.js
现在我们进入有趣的部分:将机器学习添加到我们的应用程序!首先,使用以下命令从 NPM 安装 Transformers.js
npm install @huggingface/transformers
对于此应用程序,我们将使用 Xenova/nllb-200-distilled-600M 模型,该模型可以在 200 种语言之间执行多语言翻译。在我们开始之前,有两件事我们需要注意
- ML 推理的计算量可能很大,因此最好在与主(UI)线程分离的线程中加载和运行模型。
- 由于模型相当大(> 1 GB),我们不希望在用户单击“翻译”按钮之前下载它。
我们可以通过使用 Web Worker 和一些 React hooks 来实现这两个目标。
在
src
目录中创建一个名为worker.js
的文件。此脚本将为我们完成所有繁重的工作,包括加载和运行翻译 pipeline。为了确保模型仅加载一次,我们将创建MyTranslationPipeline
类,该类使用 singleton 模式 在首次调用getInstance
时延迟创建 pipeline 的单个实例,并将此 pipeline 用于所有后续调用import { pipeline, TextStreamer } from '@huggingface/transformers'; class MyTranslationPipeline { static task = 'translation'; static model = 'Xenova/nllb-200-distilled-600M'; static instance = null; static async getInstance(progress_callback = null) { this.instance ??= pipeline(this.task, this.model, { progress_callback }); return this.instance; } }
修改
src
目录中的App.jsx
。此文件在初始化我们的 React 项目时会自动创建,并将包含一些样板代码。在App
函数内部,让我们创建 web worker 并使用useRef
hook 存储对它的引用// Remember to import the relevant hooks import { useEffect, useRef, useState } from 'react' import './App.css' function App() { // Create a reference to the worker object. const worker = useRef(null); // We use the `useEffect` hook to setup the worker as soon as the `App` component is mounted. useEffect(() => { // Create the worker if it does not yet exist. worker.current ??= new Worker(new URL('./worker.js', import.meta.url), { type: 'module' }); // Create a callback function for messages from the worker thread. const onMessageReceived = (e) => { // TODO: Will fill in later }; // Attach the callback function as an event listener. worker.current.addEventListener('message', onMessageReceived); // Define a cleanup function for when the component is unmounted. return () => worker.current.removeEventListener('message', onMessageReceived); }); return ( // TODO: Rest of our app goes here... ) } export default App
步骤 3:设计用户界面
我们建议再次使用 npm run dev
启动开发服务器(如果尚未运行),以便您可以实时查看您的更改。
首先,让我们定义我们的组件。在 src
目录中创建一个名为 components
的文件夹,并创建以下文件
LanguageSelector.jsx
:此组件将允许用户选择输入和输出语言。在此处查看完整语言列表:here。const LANGUAGES = { "Acehnese (Arabic script)": "ace_Arab", "Acehnese (Latin script)": "ace_Latn", "Afrikaans": "afr_Latn", ... "Zulu": "zul_Latn", } export default function LanguageSelector({ type, onChange, defaultLanguage }) { return ( <div className='language-selector'> <label>{type}: </label> <select onChange={onChange} defaultValue={defaultLanguage}> {Object.entries(LANGUAGES).map(([key, value]) => { return <option key={key} value={value}>{key}</option> })} </select> </div> ) }
Progress.jsx
:此组件将显示下载每个模型文件的进度。export default function Progress({ text, percentage }) { percentage = percentage ?? 0; return ( <div className="progress-container"> <div className='progress-bar' style={{ 'width': `${percentage}%` }}> {text} ({`${percentage.toFixed(2)}%`}) </div> </div> ); }
我们现在可以通过将这些导入添加到文件顶部,在 App.jsx
中使用这些组件
import LanguageSelector from './components/LanguageSelector';
import Progress from './components/Progress';
让我们还添加一些状态变量来跟踪应用程序中的一些事项,例如模型加载、语言、输入文本和输出文本。将以下代码添加到 src/App.jsx
中 App
函数的开头
function App() {
// Model loading
const [ready, setReady] = useState(null);
const [disabled, setDisabled] = useState(false);
const [progressItems, setProgressItems] = useState([]);
// Inputs and outputs
const [input, setInput] = useState('I love walking my dog.');
const [sourceLanguage, setSourceLanguage] = useState('eng_Latn');
const [targetLanguage, setTargetLanguage] = useState('fra_Latn');
const [output, setOutput] = useState('');
// rest of the code...
}
接下来,我们可以将我们的自定义组件添加到主 App
组件。我们还将添加两个 textarea
元素用于输入和输出文本,以及一个按钮来触发翻译。修改 return
语句使其看起来像这样
return (
<>
<h1>Transformers.js</h1>
<h2>ML-powered multilingual translation in React!</h2>
<div className='container'>
<div className='language-container'>
<LanguageSelector type={"Source"} defaultLanguage={"eng_Latn"} onChange={x => setSourceLanguage(x.target.value)} />
<LanguageSelector type={"Target"} defaultLanguage={"fra_Latn"} onChange={x => setTargetLanguage(x.target.value)} />
</div>
<div className='textbox-container'>
<textarea value={input} rows={3} onChange={e => setInput(e.target.value)}></textarea>
<textarea value={output} rows={3} readOnly></textarea>
</div>
</div>
<button disabled={disabled} onClick={translate}>Translate</button>
<div className='progress-bars-container'>
{ready === false && (
<label>Loading models... (only run once)</label>
)}
{progressItems.map(data => (
<div key={data.file}>
<Progress text={data.file} percentage={data.progress} />
</div>
))}
</div>
</>
)
现在不用担心 translate
函数。我们将在下一节中定义它。
最后,我们可以添加一些 CSS 使我们的应用程序看起来更好看。修改 src
目录中的以下文件
index.css
:查看代码
:root { font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; line-height: 1.5; font-weight: 400; color: #213547; background-color: #ffffff; font-synthesis: none; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; -webkit-text-size-adjust: 100%; } body { margin: 0; display: flex; place-items: center; min-width: 320px; min-height: 100vh; } h1 { font-size: 3.2em; line-height: 1; } h1, h2 { margin: 8px; } select { padding: 0.3em; cursor: pointer; } textarea { padding: 0.6em; } button { padding: 0.6em 1.2em; cursor: pointer; font-weight: 500; } button[disabled] { cursor: not-allowed; } select, textarea, button { border-radius: 8px; border: 1px solid transparent; font-size: 1em; font-family: inherit; background-color: #f9f9f9; transition: border-color 0.25s; } select:hover, textarea:hover, button:not([disabled]):hover { border-color: #646cff; } select:focus, select:focus-visible, textarea:focus, textarea:focus-visible, button:focus, button:focus-visible { outline: 4px auto -webkit-focus-ring-color; }
App.css
查看代码
#root { max-width: 1280px; margin: 0 auto; padding: 2rem; text-align: center; } .language-container { display: flex; gap: 20px; } .textbox-container { display: flex; justify-content: center; gap: 20px; width: 800px; } .textbox-container>textarea, .language-selector { width: 50%; } .language-selector>select { width: 150px; } .progress-container { position: relative; font-size: 14px; color: white; background-color: #e9ecef; border: solid 1px; border-radius: 8px; text-align: left; overflow: hidden; } .progress-bar { padding: 0 4px; z-index: 0; top: 0; width: 1%; overflow: hidden; background-color: #007bff; white-space: nowrap; } .progress-text { z-index: 2; } .selector-container { display: flex; gap: 20px; } .progress-bars-container { padding: 8px; height: 140px; } .container { margin: 25px; display: flex; flex-direction: column; gap: 10px; }
步骤 4:将所有内容连接在一起
现在我们已经设置了基本的用户界面,我们终于可以将所有内容连接在一起。
首先,让我们定义 translate
函数,该函数将在用户单击“翻译”按钮时被调用。这会将消息(包含输入文本、源语言和目标语言)发送到 worker 线程进行处理。我们还将禁用该按钮,以便用户不会多次单击它。在 App
函数中的 return
语句之前添加以下代码
const translate = () => {
setDisabled(true);
setOutput('');
worker.current.postMessage({
text: input,
src_lang: sourceLanguage,
tgt_lang: targetLanguage,
});
}
现在,让我们在 src/worker.js
中添加一个事件侦听器,以侦听来自主线程的消息。我们将使用 self.postMessage
将消息(例如,用于模型加载进度和文本流式传输)发送回主线程。
// Listen for messages from the main thread
self.addEventListener('message', async (event) => {
// Retrieve the translation pipeline. When called for the first time,
// this will load the pipeline and save it for future use.
const translator = await MyTranslationPipeline.getInstance(x => {
// We also add a progress callback to the pipeline so that we can
// track model loading.
self.postMessage(x);
});
// Capture partial output as it streams from the pipeline
const streamer = new TextStreamer(translator.tokenizer, {
skip_prompt: true,
skip_special_tokens: true,
callback_function: function (text) {
self.postMessage({
status: 'update',
output: text
});
}
});
// Actually perform the translation
const output = await translator(event.data.text, {
tgt_lang: event.data.tgt_lang,
src_lang: event.data.src_lang,
// Allows for partial output to be captured
streamer,
});
// Send the output back to the main thread
self.postMessage({
status: 'complete',
output,
});
});
最后,让我们填写 src/App.jsx
中的 onMessageReceived
函数,该函数将响应来自 worker 线程的消息来更新应用程序状态。将以下代码添加到我们之前定义的 useEffect
hook 中
const onMessageReceived = (e) => {
switch (e.data.status) {
case 'initiate':
// Model file start load: add a new progress item to the list.
setReady(false);
setProgressItems(prev => [...prev, e.data]);
break;
case 'progress':
// Model file progress: update one of the progress items.
setProgressItems(
prev => prev.map(item => {
if (item.file === e.data.file) {
return { ...item, progress: e.data.progress }
}
return item;
})
);
break;
case 'done':
// Model file loaded: remove the progress item from the list.
setProgressItems(
prev => prev.filter(item => item.file !== e.data.file)
);
break;
case 'ready':
// Pipeline ready: the worker is ready to accept messages.
setReady(true);
break;
case 'update':
// Generation update: update the output text.
setOutput(o => o + e.data.output);
break;
case 'complete':
// Generation complete: re-enable the "Translate" button
setDisabled(false);
break;
}
};
您现在可以使用 npm run dev
运行应用程序,并在浏览器中直接执行多语言翻译!
(可选)步骤 5:构建和部署
要构建您的应用程序,只需运行 npm run build
。这将捆绑您的应用程序并将静态文件输出到 dist
文件夹。
对于此演示,我们将把我们的应用程序部署为静态 Hugging Face Space,但您可以将其部署到任何您喜欢的地方!如果您还没有,可以在 here 创建一个免费的 Hugging Face 帐户。
- 访问 https://huggingface.co/new-space 并填写表格。请记住选择“Static”作为 space 类型。
- 转到“Files”→“Add file”→“Upload files”。将
dist
文件夹中的index.html
文件和public/
文件夹拖到上传框中,然后单击“Upload”。上传完成后,向下滚动到按钮并单击“Commit changes to main”。
就是这样! 您的应用程序现在应该在 https://huggingface.co/spaces/<your-username>/<your-space-name>
上线了!