translaite/app/routes/t.new.tsx

251 lines
6.8 KiB
TypeScript
Raw Permalink Normal View History

import type { ActionArgs, LoaderArgs } from "@remix-run/node";
2023-08-07 10:52:44 +02:00
import { json, redirect } from "@remix-run/node";
2023-08-22 14:58:46 +02:00
import {
Form,
Link,
useActionData,
useLoaderData,
useNavigation,
} from "@remix-run/react";
2023-08-07 10:52:44 +02:00
import { useEffect, useRef } from "react";
import { createTranslation } from "~/models/translation.server";
import { requireUser } from "~/session.server";
import { Configuration, OpenAIApi } from "openai";
2023-08-07 10:52:44 +02:00
export const action = async ({ request }: ActionArgs) => {
const user = await requireUser(request);
2023-08-07 10:52:44 +02:00
const formData = await request.formData();
const lang = formData.get("lang");
const text = formData.get("text");
2023-08-07 10:52:44 +02:00
if (typeof lang !== "string" || lang.length === 0) {
2023-08-07 10:52:44 +02:00
return json(
{ errors: { text: null, lang: "Lang is required", result: null } },
2023-08-07 10:52:44 +02:00
{ status: 400 }
);
}
if (typeof text !== "string" || text.length === 0) {
2023-08-07 10:52:44 +02:00
return json(
{ errors: { text: "Text is required", lang: null, result: null } },
2023-08-07 10:52:44 +02:00
{ status: 400 }
);
}
if (!user.openAIKey?.length) {
return redirect("/account");
}
try {
const configuration = new Configuration({
apiKey: user.openAIKey,
});
const openai = new OpenAIApi(configuration);
const completion = await openai.createChatCompletion({
model: "gpt-3.5-turbo",
messages: [
{
role: "system",
content: `You will be provided with a sentence, your task is to translate it into ${lang}.`,
},
{ role: "user", content: text },
],
});
const result = completion.data.choices[0].message?.content;
if (typeof result !== "string" || result.length === 0) {
return json(
{
errors: {
text: null,
lang: null,
result: "Error while retrieving translation result",
},
},
{ status: 500 }
);
}
const t = await createTranslation({ lang, text, result, userId: user.id });
return redirect(`/t/${t.id}`);
} catch (e) {
let error = e as any;
if (error.response) {
console.error(error.response.status);
console.error(error.response.data);
return json(
{
errors: {
text: null,
lang: null,
result: `[${error.response.status}] ${
error.response.data || error.message
}`,
},
},
{ status: 500 }
);
} else {
console.error(error.message);
return json(
{
errors: {
text: null,
lang: null,
result: `[${error.name}] ${error.message}`,
},
},
{ status: 500 }
);
}
}
};
export const loader = async ({ params, request }: LoaderArgs) => {
const user = await requireUser(request);
2023-08-07 10:52:44 +02:00
if (!user.openAIKey?.length) {
return redirect("/account");
}
return json({ userHasOpenAIKey: true });
2023-08-07 10:52:44 +02:00
};
export default function NewTranslationPage() {
2023-08-07 10:52:44 +02:00
const actionData = useActionData<typeof action>();
const loaderData = useLoaderData<typeof loader>();
const langRef = useRef<HTMLInputElement>(null);
const textRef = useRef<HTMLTextAreaElement>(null);
2023-08-22 14:58:46 +02:00
const navigation = useNavigation();
2023-08-07 10:52:44 +02:00
useEffect(() => {
if (actionData?.errors?.lang) {
langRef.current?.focus();
} else if (actionData?.errors?.text) {
textRef.current?.focus();
2023-08-07 10:52:44 +02:00
}
}, [actionData]);
return (
<Form
method="post"
style={{
display: "flex",
flexDirection: "column",
gap: 8,
width: "100%",
}}
>
<div>
<label className="flex w-full flex-col gap-1">
<span>Translate to: </span>
2023-08-07 10:52:44 +02:00
<input
ref={langRef}
name="lang"
2023-08-22 14:58:46 +02:00
required
disabled={
loaderData?.userHasOpenAIKey === false ||
navigation.state === "submitting"
}
2023-08-07 10:52:44 +02:00
className="flex-1 rounded-md border-2 border-blue-500 px-3 text-lg leading-loose"
aria-invalid={actionData?.errors?.lang ? true : undefined}
2023-08-07 10:52:44 +02:00
aria-errormessage={
actionData?.errors?.lang ? "lang-error" : undefined
2023-08-07 10:52:44 +02:00
}
/>
</label>
{actionData?.errors?.lang ? (
<div className="pt-1 text-red-700" id="lang-error">
{actionData.errors.lang}
2023-08-07 10:52:44 +02:00
</div>
) : null}
</div>
<div>
<label className="flex w-full flex-col gap-1">
<span>Text: </span>
2023-08-07 10:52:44 +02:00
<textarea
ref={textRef}
name="text"
2023-08-22 14:58:46 +02:00
required
2023-08-07 10:52:44 +02:00
rows={8}
2023-08-22 14:58:46 +02:00
disabled={
loaderData?.userHasOpenAIKey === false ||
navigation.state === "submitting"
}
2023-08-07 10:52:44 +02:00
className="w-full flex-1 rounded-md border-2 border-blue-500 px-3 py-2 text-lg leading-6"
aria-invalid={actionData?.errors?.text ? true : undefined}
2023-08-07 10:52:44 +02:00
aria-errormessage={
actionData?.errors?.text ? "text-error" : undefined
2023-08-07 10:52:44 +02:00
}
/>
</label>
{actionData?.errors?.text ? (
<div className="pt-1 text-red-700" id="text-error">
{actionData.errors.text}
2023-08-07 10:52:44 +02:00
</div>
) : null}
</div>
<div className="text-right">
<button
type="submit"
2023-08-22 14:58:46 +02:00
disabled={
loaderData?.userHasOpenAIKey === false ||
navigation.state === "submitting"
}
2023-08-07 10:52:44 +02:00
className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600 focus:bg-blue-400"
>
2023-08-22 14:58:46 +02:00
{navigation.state === "submitting" && (
<svg
className="mr-2 inline-block h-4 w-4 animate-spin text-white"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
/>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2
5.291A7.962 7.962 0 014 12H0c0 3.042
1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
)}
Translate
2023-08-07 10:52:44 +02:00
</button>
</div>
{loaderData?.userHasOpenAIKey === false && (
<div className="rounded border border-red-200 bg-red-100 p-4 text-red-700">
<p>
You need to add your OpenAI API key to your account before you can
translate text.
</p>
<p>
Go to <Link to="/account">your account</Link> to add your key.
</p>
</div>
)}
{actionData?.errors?.result && (
<div className="rounded border border-red-200 bg-red-100 p-4 text-red-700">
<p>{actionData.errors.result}</p>
</div>
)}
2023-08-07 10:52:44 +02:00
</Form>
);
}