构建 Next.js 应用程序
在本教程中,我们将构建一个简单的 Next.js 应用程序,使用 Transformers.js 进行情感分析!由于 Transformers.js 可以在浏览器或 Node.js 中运行,因此您可以选择是在 客户端 还是 服务器端 进行推理(我们将向您展示两种方法)。在任一情况下,我们都将使用新的 App Router 范式进行开发。最终产品将如下所示
有用链接
先决条件
客户端推理
步骤 1:初始化项目
首先使用 create-next-app
创建一个新的 Next.js 应用程序
npx create-next-app@latest
安装后,您将看到各种提示。对于本演示,我们将选择下面显示的以粗体显示的提示
√ What is your project named? ... next √ Would you like to use TypeScript? ... No / Yes √ Would you like to use ESLint? ... No / Yes √ Would you like to use Tailwind CSS? ... No / Yes √ Would you like to use `src/` directory? ... No / Yes √ Would you like to use App Router? (recommended) ... No / Yes √ Would you like to customize the default import alias? ... No / Yes
步骤 2:安装和配置 Transformers.js
您可以使用以下命令从 NPM 安装 Transformers.js
npm i @huggingface/transformers
我们还需要更新 next.config.js
文件,以便在为浏览器捆绑时忽略特定于节点的模块
/** @type {import('next').NextConfig} */
const nextConfig = {
// (Optional) Export as a static site
// See https://nextjs.net.cn/docs/pages/building-your-application/deploying/static-exports#configuration
output: 'export', // Feel free to modify/remove this option
// Override the default webpack configuration
webpack: (config) => {
// See https://webpack.js.cn/configuration/resolve/#resolvealias
config.resolve.alias = {
...config.resolve.alias,
"sharp$": false,
"onnxruntime-node$": false,
}
return config;
},
}
module.exports = nextConfig
接下来,我们将创建一个新的 Web Worker 脚本,我们将在此处放置所有与 ML 相关的代码。这样做是为了确保在模型加载和执行推理时不会阻塞主线程。对于此应用程序,我们将使用 Xenova/distilbert-base-uncased-finetuned-sst-2-english
,这是一个在 斯坦福情感树库 数据集上微调的约 6700 万个参数的模型。将以下代码添加到 ./src/app/worker.js
import { pipeline, env } from "@huggingface/transformers";
// Skip local model check
env.allowLocalModels = false;
// Use the Singleton pattern to enable lazy construction of the pipeline.
class PipelineSingleton {
static task = 'text-classification';
static model = 'Xenova/distilbert-base-uncased-finetuned-sst-2-english';
static instance = null;
static async getInstance(progress_callback = null) {
if (this.instance === null) {
this.instance = pipeline(this.task, this.model, { progress_callback });
}
return this.instance;
}
}
// Listen for messages from the main thread
self.addEventListener('message', async (event) => {
// Retrieve the classification pipeline. When called for the first time,
// this will load the pipeline and save it for future use.
let classifier = await PipelineSingleton.getInstance(x => {
// We also add a progress callback to the pipeline so that we can
// track model loading.
self.postMessage(x);
});
// Actually perform the classification
let output = await classifier(event.data.text);
// Send the output back to the main thread
self.postMessage({
status: 'complete',
output: output,
});
});
步骤 3:设计用户界面
我们现在将修改默认的 ./src/app/page.js
文件,以便它连接到我们的 worker 线程。由于我们将只执行浏览器内推理,因此我们可以使用 'use client'
指令 选择加入客户端组件。
'use client'
import { useState, useEffect, useRef, useCallback } from 'react'
export default function Home() {
/* TODO: Add state variables */
// Create a reference to the worker object.
const worker = useRef(null);
// We use the `useEffect` hook to set up the worker as soon as the `App` component is mounted.
useEffect(() => {
if (!worker.current) {
// 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: See below */};
// 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);
});
const classify = useCallback((text) => {
if (worker.current) {
worker.current.postMessage({ text });
}
}, []);
return ( /* TODO: See below */ )
}
在 Home
组件的开头初始化以下状态变量
// Keep track of the classification result and the model loading status.
const [result, setResult] = useState(null);
const [ready, setReady] = useState(null);
并填写 onMessageReceived
函数,以便当 worker 线程发送消息时更新这些变量
const onMessageReceived = (e) => {
switch (e.data.status) {
case 'initiate':
setReady(false);
break;
case 'ready':
setReady(true);
break;
case 'complete':
setResult(e.data.output[0])
break;
}
};
最后,我们可以向 Home
组件添加一个简单的 UI,它包含一个输入文本框和一个预格式化文本元素,用于显示分类结果
<main className="flex min-h-screen flex-col items-center justify-center p-12">
<h1 className="text-5xl font-bold mb-2 text-center">Transformers.js</h1>
<h2 className="text-2xl mb-4 text-center">Next.js template</h2>
<input
className="w-full max-w-xs p-2 border border-gray-300 rounded mb-4"
type="text"
placeholder="Enter text here"
onInput={e => {
classify(e.target.value);
}}
/>
{ready !== null && (
<pre className="bg-gray-100 p-2 rounded">
{ (!ready || !result) ? 'Loading...' : JSON.stringify(result, null, 2) }
</pre>
)}
</main>
您现在可以使用以下命令运行您的应用程序
npm run dev
访问终端显示的 URL(例如,http://localhost:3000/)即可查看您的应用程序!
(可选) 第 4 步:构建和部署
要构建您的应用程序,只需运行
npm run build
这将打包您的应用程序并将静态文件输出到 out
文件夹。
在这个演示中,我们将把我们的应用程序部署为一个静态的 Hugging Face Space,但您也可以将其部署到任何您喜欢的地方!如果您还没有,可以创建一个免费的 Hugging Face 帐户 here。
- 访问 https://huggingface.co/new-space 并填写表单。请记住选择“静态”作为空间类型。
- 点击页面底部的“创建空间”按钮。
- 转到“文件”→“添加文件”→“上传文件”。将
out
文件夹中的文件拖放到上传框中,然后点击“上传”。上传完毕后,向下滚动到按钮并点击“将更改提交到 main”。
就是这样!您的应用程序现在应该在 https://huggingface.co/spaces/<your-username>/<your-space-name>
上上线了!
服务器端推理
虽然有很多不同的方法来执行服务器端推理,但最简单的方法(我们将在本教程中讨论)是使用新的 路由处理程序 功能。
步骤 1:初始化项目
首先使用 create-next-app
创建一个新的 Next.js 应用程序
npx create-next-app@latest
安装后,您将看到各种提示。对于本演示,我们将选择下面显示的以粗体显示的提示
√ What is your project named? ... next √ Would you like to use TypeScript? ... No / Yes √ Would you like to use ESLint? ... No / Yes √ Would you like to use Tailwind CSS? ... No / Yes √ Would you like to use `src/` directory? ... No / Yes √ Would you like to use App Router? (recommended) ... No / Yes √ Would you like to customize the default import alias? ... No / Yes
步骤 2:安装和配置 Transformers.js
您可以使用以下命令从 NPM 安装 Transformers.js
npm i @huggingface/transformers
我们还需要更新 next.config.js
文件,以阻止 Webpack 打包某些软件包
/** @type {import('next').NextConfig} */
const nextConfig = {
// (Optional) Export as a standalone site
// See https://nextjs.net.cn/docs/pages/api-reference/next-config-js/output#automatically-copying-traced-files
output: 'standalone', // Feel free to modify/remove this option
// Indicate that these packages should not be bundled by webpack
experimental: {
serverComponentsExternalPackages: ['sharp', 'onnxruntime-node'],
},
};
module.exports = nextConfig
接下来,让我们设置我们的路由处理程序。我们可以通过在新的 ./src/app/classify/
目录中创建两个文件来实现。
pipeline.js
- 用于处理我们管道构建。import { pipeline } from "@huggingface/transformers"; // Use the Singleton pattern to enable lazy construction of the pipeline. // NOTE: We wrap the class in a function to prevent code duplication (see below). const P = () => class PipelineSingleton { static task = 'text-classification'; static model = 'Xenova/distilbert-base-uncased-finetuned-sst-2-english'; static instance = null; static async getInstance(progress_callback = null) { if (this.instance === null) { this.instance = pipeline(this.task, this.model, { progress_callback }); } return this.instance; } } let PipelineSingleton; if (process.env.NODE_ENV !== 'production') { // When running in development mode, attach the pipeline to the // global object so that it's preserved between hot reloads. // For more information, see https://vercel.com/guides/nextjs-prisma-postgres if (!global.PipelineSingleton) { global.PipelineSingleton = P(); } PipelineSingleton = global.PipelineSingleton; } else { PipelineSingleton = P(); } export default PipelineSingleton;
route.js
- 用于处理对/classify
路由的请求。import { NextResponse } from 'next/server' import PipelineSingleton from './pipeline.js'; export async function GET(request) { const text = request.nextUrl.searchParams.get('text'); if (!text) { return NextResponse.json({ error: 'Missing text parameter', }, { status: 400 }); } // Get the classification pipeline. When called for the first time, // this will load the pipeline and cache it for future use. const classifier = await PipelineSingleton.getInstance(); // Actually perform the classification const result = await classifier(text); return NextResponse.json(result); }
步骤 3:设计用户界面
现在,我们将修改默认的 ./src/app/page.js
文件,以便向我们新创建的路由处理程序发出请求。
'use client'
import { useState } from 'react'
export default function Home() {
// Keep track of the classification result and the model loading status.
const [result, setResult] = useState(null);
const [ready, setReady] = useState(null);
const classify = async (text) => {
if (!text) return;
if (ready === null) setReady(false);
// Make a request to the /classify route on the server.
const result = await fetch(`/classify?text=${encodeURIComponent(text)}`);
// If this is the first time we've made a request, set the ready flag.
if (!ready) setReady(true);
const json = await result.json();
setResult(json);
};
return (
<main className="flex min-h-screen flex-col items-center justify-center p-12">
<h1 className="text-5xl font-bold mb-2 text-center">Transformers.js</h1>
<h2 className="text-2xl mb-4 text-center">Next.js template (server-side)</h2>
<input
type="text"
className="w-full max-w-xs p-2 border border-gray-300 rounded mb-4"
placeholder="Enter text here"
onInput={e => {
classify(e.target.value);
}}
/>
{ready !== null && (
<pre className="bg-gray-100 p-2 rounded">
{
(!ready || !result) ? 'Loading...' : JSON.stringify(result, null, 2)}
</pre>
)}
</main>
)
}
您现在可以使用以下命令运行您的应用程序
npm run dev
访问终端显示的 URL(例如,http://localhost:3000/)即可查看您的应用程序!
(可选) 第 4 步:构建和部署
在这个演示中,我们将构建并部署我们的应用程序到 Hugging Face Spaces。如果您还没有,可以创建一个免费的 Hugging Face 帐户 here。
- 在您的项目的根文件夹中创建一个新的
Dockerfile
。您可以使用我们的 示例 Dockerfile 作为模板。 - 访问 https://huggingface.co/new-space 并填写表单。请记住选择“Docker”作为空间类型(您可以选择“空白”Docker 模板)。
- 点击页面底部的“创建空间”按钮。
- 转到“文件”→“添加文件”→“上传文件”。将项目文件夹中的文件(如果存在,则排除
node_modules
和.next
)拖放到上传框中,然后点击“上传”。上传完毕后,向下滚动到按钮并点击“将更改提交到 main”。 - 将以下行添加到您的
README.md
的顶部--- title: Next Server Example App emoji: 🔥 colorFrom: yellow colorTo: red sdk: docker pinned: false app_port: 3000 ---
就是这样!您的应用程序现在应该在 https://huggingface.co/spaces/<your-username>/<your-space-name>
上上线了!