Skip to content

Commit

Permalink
feat(project): add chat gpt translation generation
Browse files Browse the repository at this point in the history
  • Loading branch information
davidecarpini committed Mar 22, 2024
1 parent c193b8a commit e2bf3a7
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 17 deletions.
106 changes: 106 additions & 0 deletions app/api/chatGpt/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { NextRequest } from "next/server"
import { I18n, I18nInfo, I18nLang, ProjectSettings } from "@/store/useI18nState"
import { OpenAIHelper } from "@/utils/OpenAiUtils"
import { getServerSession } from "next-auth/next"
import * as z from "zod"

import { authOptions } from "@/lib/auth"
import { db } from "@/lib/db"
import { handleCatchApi } from "@/lib/exceptions"
import i18n from "@/lib/i18n"
import { ErrorResponse } from "@/lib/response"

const generateTranslationSchema = z.object({
projectId: z.string(),
keyword: z.string(),
})

export async function GET(req: NextRequest) {
try {
const session = await getServerSession(authOptions)

if (!session) {
return new Response("Unauthorized", { status: 403 })
}

const searchParams = req.nextUrl.searchParams

const { keyword, projectId } = generateTranslationSchema.parse({
projectId: searchParams.get("projectId"),
keyword: searchParams.get("keyword"),
})

const project = await db.project.findFirst({
where: {
id: projectId,
},
})

if (!project) {
return ErrorResponse({
error: i18n.t("The project does not exist or is not published"),
})
}

const languages = project.languages as I18nLang[]

if (!languages) {
return ErrorResponse({
error: i18n.t("The project does not have any languages"),
})
}

const languagesPropt = languages
.map((language) => language.short)
.join(", ")

const { context } = (project.info as I18nInfo[])?.find(
(ele) => ele.key === keyword
) || {
key: keyword,
context: "",
}

const settings = project.settings as ProjectSettings

try {
const prompt = `
I'm working on internationalizing my application. I'd like to translate the text "${keyword}" ${
context ? `, used in this context: "${context}"` : ""
}. Could you write the translations in [${languagesPropt}]?
[[Translations should be informal]] <-- ${settings.formality}
[[in the tone of a tech website]] <-- ${settings.description}
[[The target audience is both male and female]] <-- ${settings.audience}
with an age range between ${settings.ageStart} and ${settings.ageEnd} years old.
respond using an unique JSON object without any comments or any other descriptions, like so:
{
"en": "",
"it": "",
"es": ""
}
where:
language-id for english = en
language-id for italian = it
language-id for spanish = es
`

const openaiHelper = new OpenAIHelper()
const response = await openaiHelper.askChatGPT({
prompt,
})
const jsonString = openaiHelper.getResponseJSONString(response)
const result = openaiHelper.parseChatGPTJSONString<I18n>(jsonString)
if (result) {
return new Response(JSON.stringify(result))
}
throw new Error("Failed to get response from Chat GPT.")
} catch (e) {
throw new Error(e)
}
} catch (error) {
return handleCatchApi(error)
}
}
1 change: 1 addition & 0 deletions components/app/project/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ export function Editor(props: EditorProps) {
editKey={editKey}
checkIfKeyAlreadyExists={checkIfKeyAlreadyExists}
isSaving={isSaving}
project={project}

Check failure on line 120 in components/app/project/index.tsx

View workflow job for this annotation

GitHub Actions / build

Type 'Pick<Project, "info" | "id" | "title" | "languages" | "settings" | "published">' is missing the following properties from type 'Project': createdAt, updatedAt, userId
/>
</div>
{isProjectSettingsOpened && (
Expand Down
43 changes: 31 additions & 12 deletions components/app/project/table/detail-slide-over.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { ChangeEvent, useCallback, useState } from "react"
import { Project } from "@prisma/client"

import i18n from "@/lib/i18n"
import { Button } from "@/components/ui/button"
import SlideOver, { SlideOverRow } from "@/components/slide-over"

Expand All @@ -13,6 +15,7 @@ type Props = {
editContext: (key: string, context: string) => void
editKey: (key: string, newKey: string) => void
checkIfKeyAlreadyExists: (key: string) => boolean
project: Project
}

const DetailSlideOver = (props: Props) => {
Expand All @@ -24,6 +27,7 @@ const DetailSlideOver = (props: Props) => {
editContext,
editKey,
checkIfKeyAlreadyExists,
project,
} = props

const [key, setKey] = useState(keyword.key)
Expand Down Expand Up @@ -59,37 +63,52 @@ const DetailSlideOver = (props: Props) => {
editKey(keyword.key, key)
}, [editKey, key, keyword.key])

const askAI = useCallback(async () => {
const response = await fetch(
`/api/chatGpt?projectId=${project?.id}&keyword=${keyword.key}`
).then((res) => res.json())
Object.keys(response).forEach((language) => {
editTranslation(language, keyword.key, response[language])
})
}, [editTranslation, keyword.key, project?.id])

return (
<SlideOver title={keyword.key} onClose={onClose} isSaving={isSaving}>
<div className="relative p-4 flex-1 sm:px-6">
<SlideOverRow title="Keyword">
<div className="mt-1">
<label className="inline-block text-xs font-light text-gray-700 mt-2.5 dark:text-gray-200">
Pay attention not to insert an existing keyword, or you will
overwrite that keyword
{i18n.t(
"Pay attention not to insert an existing keyword, or you will overwrite that keyword"
)}
</label>
<textarea
rows={3}
className="t-textarea mt-2"
placeholder="Welcome"
placeholder={i18n.t("Context")}
value={key}
onChange={handleChangeKey}
></textarea>
<Button
className="mt-2"
onClick={saveKey}
variant={isWarning ? "warning" : "default"}
>
{isWarning ? "Overwrite the keyword" : "Change"}
</Button>
<div className="flex gap-2">
<Button
className="mt-2"
onClick={saveKey}
variant={isWarning ? "warning" : "default"}
>
{isWarning ? "Overwrite the keyword" : "Change"}
</Button>
<Button className="mt-2" onClick={askAI} variant={"default"}>
{i18n.t("Generate")}
</Button>
</div>
</div>
</SlideOverRow>
<SlideOverRow title="Description">
<SlideOverRow title="Context">
<div className="mt-1">
<textarea
rows={3}
className="t-textarea"
placeholder="Leave the keyword description here..."
placeholder="Leave the keyword context here..."
value={keyword.info?.context}
onChange={handleChangeContext}
></textarea>
Expand Down
4 changes: 4 additions & 0 deletions components/app/project/table/table.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client"

import { useCallback, useMemo, useState } from "react"
import { Project } from "@prisma/client"

import i18n from "@/lib/i18n"

Expand All @@ -18,6 +19,7 @@ type Props = {
editContext: (key: string, context: string) => void
editKey: (key: string, newKey: string) => void
checkIfKeyAlreadyExists: (key: string) => boolean
project: Project
}

const Table = (props: Props) => {
Expand All @@ -30,6 +32,7 @@ const Table = (props: Props) => {
editContext,
editKey,
checkIfKeyAlreadyExists,
project,
} = props

const [keySelected, selectKey] = useState<string | undefined>(undefined)
Expand Down Expand Up @@ -126,6 +129,7 @@ const Table = (props: Props) => {
</div>
{keywordSelected && (
<DetailSlideOver
project={project}
onClose={closeDetailRow}
keyword={keywordSelected}
editTranslation={editTranslation}
Expand Down
8 changes: 6 additions & 2 deletions lib/i18n/languages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,9 @@
"Upload .json file": "Upload .json file",
"Use a glossary to keep project translations consistent": "Use a glossary to keep project translations consistent",
"User wrong": "User wrong",
"Write the word you want to be preferred over other similar words in the languages you prefer": "Write the word you want to be preferred over other similar words in the languages you prefer"
}
"Write the word you want to be preferred over other similar words in the languages you prefer": "Write the word you want to be preferred over other similar words in the languages you prefer",
"Pay attention not to insert an existing keyword, or you will overwrite that keyword": "Pay attention not to insert an existing keyword, or you will overwrite that keyword",
"Generate": "Generate",
"The project does not exist or is not published": "The project does not exist or is not published",
"The project does not have any languages": "The project does not have any languages"
}
2 changes: 1 addition & 1 deletion store/useI18nState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ export const useI18nState = create<I18nState>()(
i18n: {
...state.i18n,
languages: state.i18n.languages.map((_language) => {
if (_language.lang !== language) {
if (_language.lang !== language && _language.short !== language) {
return _language
}

Expand Down
4 changes: 2 additions & 2 deletions utils/OpenAiUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class OpenAIHelper {
maxTokens?: number
}) =>
this.openai.chat.completions.create({
model: "gpt-3.5-turbo",
model: "gpt-4-vision-preview",
max_tokens: maxTokens,
messages: [
{
Expand Down Expand Up @@ -59,7 +59,7 @@ export class OpenAIHelper {
maxTokens?: number
}) =>
this.openai.chat.completions.create({
model: "gpt-4-vision-preview",
model: "gpt-3.5-turbo",
max_tokens: maxTokens,
messages: [
{
Expand Down

0 comments on commit e2bf3a7

Please sign in to comment.