Transformers.js 文件
構建一個 React 應用
並獲得增強的文件體驗
開始使用
構建一個 React 應用
在本教程中,我們將使用 Transformers.js 構建一個簡單的 React 應用程式,實現多語言翻譯!最終產品將如下所示
實用連結
先決條件
第 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 種語言之間的多語言翻譯。在我們開始之前,有 2 件事需要注意
- 機器學習推理的計算量可能很大,因此最好在與主(UI)執行緒分開的執行緒中載入和執行模型。
- 由於模型很大(>1 GB),我們不希望在使用者點選“翻譯”按鈕之前下載它。
我們可以透過使用 Web Worker 和一些 React Hooks 來實現這兩個目標。
在
src
目錄中建立一個名為worker.js
的檔案。此指令碼將為我們完成所有繁重的工作,包括載入和執行翻譯管道。為確保模型只加載一次,我們將建立MyTranslationPipeline
類,該類使用 單例模式 在首次呼叫getInstance
時延遲建立管道的單個例項,並在所有後續呼叫中使用此管道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
:此元件將允許使用者選擇輸入和輸出語言。在此處檢視完整的語言列表這裡。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
元素用於輸入和輸出文字,以及一個 button
來觸發翻譯。修改 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
函式,當用戶點選 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
鉤子中
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,但您可以將其部署到任何您喜歡的地方!如果您還沒有,可以在這裡建立一個免費的 Hugging Face 帳戶。
- 訪問 https://huggingface.co/new-space 並填寫表格。請記住選擇“Static”作為空間型別。
- 轉到“檔案”→“新增檔案”→“上傳檔案”。將
dist
資料夾中的index.html
檔案和public/
資料夾拖到上傳框中,然後點選“上傳”。上傳完成後,向下滾動到按鈕並點選“Commit changes to main”。
大功告成!您的應用程式現在應該在 https://huggingface.co/spaces/<您的使用者名稱>/<您的空間名稱>
上線了!