Skip to content

Commit

Permalink
Run transformers.js on the server-side
Browse files Browse the repository at this point in the history
  • Loading branch information
neet committed Jan 18, 2025
1 parent 5082f29 commit d2582d3
Show file tree
Hide file tree
Showing 13 changed files with 103 additions and 432 deletions.
39 changes: 25 additions & 14 deletions next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import mdxPlugin from "@next/mdx";
import createNextIntlPlugin from "next-intl/plugin";
import path from "path";
// import path from "path";

const __dirname = import.meta.dirname;
// const __dirname = import.meta.dirname;

const withMDX = mdxPlugin();
const withNextIntl = createNextIntlPlugin();
Expand All @@ -11,19 +11,30 @@ const withNextIntl = createNextIntlPlugin();
const nextConfig = {
pageExtensions: ["ts", "tsx", "js", "jsx", "md", "mdx"],

// https://github.com/huggingface/transformers.js/issues/1026
webpack: (config) => {
config.resolve.alias["@huggingface/transformers"] = path.resolve(
__dirname,
"node_modules/@huggingface/transformers",
);
config.resolve.alias = {
...config.resolve.alias,
sharp$: false,
"onnxruntime-node$": false,
};
return config;
// Indicate that these packages should not be bundled by webpack
experimental: {
serverComponentsExternalPackages: [
// "sharp",
"onnxruntime-node",
],
},

// // https://nextjs.org/docs/app/api-reference/next-config-js/serverExternalPackages
// serverExternalPackages: ["@huggingface/transformers"],

// // https://github.com/huggingface/transformers.js/issues/1026
// webpack: (config) => {
// config.resolve.alias["@huggingface/transformers"] = path.resolve(
// __dirname,
// "node_modules/@huggingface/transformers",
// );
// config.resolve.alias = {
// ...config.resolve.alias,
// sharp$: false,
// "onnxruntime-node$": false,
// };
// return config;
// },
};

export default withMDX(withNextIntl(nextConfig));
1 change: 0 additions & 1 deletion src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export * from "./translate";
export * from "./romanize";
86 changes: 0 additions & 86 deletions src/api/translate.ts

This file was deleted.

114 changes: 48 additions & 66 deletions src/app/[locale]/_server.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
import { pipeline } from "@huggingface/transformers";
import { to_kana } from "ainu-utils";

import * as api from "@/api";
import { Transcription } from "@/models/transcription";
import { isKana, normalize } from "@/utils";
import { createInputSequence } from "@/utils/task_prefix";

const MAX_LENGTH = 200;

const pipe = await pipeline(
"text2text-generation",
"aynumosir/mt5-base-ainu-onnx",
{
revision: "b6cc98f634063743e4d911e21047b67b2c04fff7",
// progress_callback: (progress) => console.log(progress),
},
);

export type TranslationParams = {
direction?: string;
source: string;
target: string;
dialect?: string;
pronoun?: string;
};
Expand All @@ -32,7 +43,7 @@ export async function translate(
text: string,
params: TranslationParams,
): Promise<Result> {
const { direction, dialect, pronoun } = params;
const { source, target, dialect, pronoun } = params;

if (typeof text !== "string" || text.length === 0) {
return {
Expand All @@ -41,7 +52,7 @@ export async function translate(
};
}

if (typeof direction !== "string") {
if (typeof source !== "string" || typeof target !== "string") {
return {
type: "error",
message: "翻訳方向が不正です。",
Expand Down Expand Up @@ -70,28 +81,34 @@ export async function translate(
}

try {
const translationSource = await normalize(text, direction);
const translationSource = await normalize(text, source);

const output = await pipe(
createInputSequence(translationSource, {
source,
target,
dialect,
pronoun,
}),
);

const translation = await api.translate(translationSource, {
direction,
dialect,
pronoun,
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const translation = (output[0] as any).generated_text as string;

const result: Result = {
type: "ok",
translation,
transcriptions: {},
};

if (direction === "ja2ain") {
if (source === "ja") {
result.transcriptions.translation = {
type: "kana",
text: to_kana(translation),
};
}

if (direction === "ain2ja") {
if (source === "ain") {
if (isKana(text)) {
result.transcriptions.text = {
type: "latin",
Expand All @@ -107,59 +124,24 @@ export async function translate(

return result;
} catch (error) {
console.log(error);
if (
(error instanceof Error &&
error.cause instanceof Response &&
error.cause.status === 503) ||
(error instanceof DOMException && error.name === "TimeoutError")
) {
return {
type: "error",
message:
"サーバーを起動していますので、1〜2分ほど待ってから再度お試しください。\n費用を削減するため、15分以上アクセスが無かった場合には自動的に停止しています。",
};
} else {
console.error(error);
return {
type: "error",
message: "エラーが発生しました。しばらく待ってから再度お試しください。",
};
}
}
}

export async function fetchAlternativeTranslations(
text: string,
result: Result,
direction: string,
dialect: string,
pronoun: string,
): Promise<string[]> {
if (result.type === "error") {
return [];
}

const genMax = 20;
const max = 3;

text =
result.transcriptions.text && result.transcriptions.text.type === "latin"
? result.transcriptions.text.text
: text;

try {
const translations = await api.translate(text, {
direction,
dialect,
pronoun,
numReturnSequences: genMax,
});

return translations
.filter((translation) => translation !== result.translation)
.splice(0, max);
} catch {
return [];
// console.log(error);
// if (
// (error instanceof Error &&
// error.cause instanceof Response &&
// error.cause.status === 503) ||
// (error instanceof DOMException && error.name === "TimeoutError")
// ) {
// return {
// type: "error",
// message:
// "サーバーを起動していますので、1〜2分ほど待ってから再度お試しください。\n費用を削減するため、15分以上アクセスが無かった場合には自動的に停止しています。",
// };
// } else {
console.error(error);
return {
type: "error",
message: "エラーが発生しました。しばらく待ってから再度お試しください。",
};
// }
}
}
36 changes: 17 additions & 19 deletions src/app/[locale]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { searchClient } from "@/api/search";
import { Composer } from "@/components/Composer";
import { SearchEntry } from "@/models/entry";

import { fetchAlternativeTranslations, Result, translate } from "./_server";
// import { fetchAlternativeTranslations } from "./_server";
import { Result, translate } from "./_server";

type HomeProps = {
params: { locale: string };
Expand Down Expand Up @@ -40,39 +41,35 @@ export default async function Home(props: HomeProps) {
setRequestLocale(params.locale);

const text = searchParams?.text;
const direction =
searchParams?.source && searchParams?.target
? `${searchParams.source}2${searchParams.target}`
: "ja2ain";
const source = searchParams?.source ?? "ja";
const target = searchParams?.target ?? "ain";
const dialect = searchParams?.dialect ?? "沙流";
const pronoun = searchParams?.pronoun ?? "first";

let result: Result | undefined;
if (text) {
result = await translate(text, {
direction,
source,
target,
dialect,
pronoun,
});
}

let alternativeTranslationsPromise: Promise<string[]> | undefined;
if (text && result && result.type === "ok") {
alternativeTranslationsPromise = fetchAlternativeTranslations(
text,
result,
direction,
dialect,
pronoun,
);
}
// if (text && result && result.type === "ok") {
// alternativeTranslationsPromise = fetchAlternativeTranslations(
// text,
// result,
// direction,
// dialect,
// pronoun,
// );
// }

let exampleSentences: Promise<SearchResponse<SearchEntry>> | undefined;
if (text && result?.type === "ok") {
const words = tokenize(
direction === "ain2ja" ? text : result.translation,
false,
);
const words = tokenize(source === "ain" ? text : result.translation, false);
if (words.length < 4) {
exampleSentences = searchClient.searchSingleIndex<SearchEntry>({
indexName: "entries",
Expand Down Expand Up @@ -102,6 +99,7 @@ export default async function Home(props: HomeProps) {
dialect,
pronoun,
}}
translation={result?.type === "ok" ? result.translation : undefined}
textTranscription={
result?.type === "ok" ? result.transcriptions.text : undefined
}
Expand Down
Loading

0 comments on commit d2582d3

Please sign in to comment.