From fb9ba558a827dbb60994bed09ee899d6df0d12ed Mon Sep 17 00:00:00 2001 From: tomolld Date: Mon, 12 Aug 2024 16:32:10 +0900 Subject: [PATCH] =?UTF-8?q?=E5=A4=A7=E3=81=8D=E3=81=8F=E4=BD=9C=E3=82=8A?= =?UTF-8?q?=E5=A4=89=E3=81=88=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/app/components/TargetLanguageSelect.tsx | 63 ----- web/app/components/ui/command.tsx | 153 ++++++++++++ web/app/components/ui/popover.tsx | 31 +++ .../translate/functions/mutations.server.ts | 12 +- .../translate/lib/translate.server.ts | 24 +- .../translate/translate-user-queue.ts | 2 +- web/app/features/translate/types.ts | 6 +- web/app/root.tsx | 27 ++- ...{queries.server.ts => mutations.server.ts} | 15 +- web/app/routes/$userName+/edit/route.tsx | 111 +++++---- .../$userName+/functions/queries.server.ts | 1 + web/app/routes/$userName+/index.tsx | 55 ++--- .../components/TargetLanguageSelector.tsx | 95 +++++--- .../components/UserAITranslationStatus.tsx | 106 +++++++++ .../page+/$slug+/constants/languages.ts | 191 +++++++++++++++ .../$slug+/edit/{index.tsx => _edit.tsx} | 8 +- .../$slug+/edit/functions/mutations.server.ts | 33 --- .../$slug+/functions/mutations.server.ts | 25 +- .../page+/$slug+/functions/queries.server.ts | 11 + .../routes/$userName+/page+/$slug+/index.tsx | 53 +++-- web/app/routes/_index/index.tsx | 140 +++++------ web/app/routes/_index/route.tsx | 114 --------- web/app/routes/api.target-language.tsx | 7 +- web/app/routes/privacy.tsx | 175 +++++++------- web/app/routes/resources+/footer.tsx | 10 +- .../resources+/gemini-api-key-dialog.tsx | 24 +- web/app/routes/search/route.tsx | 3 +- web/app/routes/terms/route.tsx | 217 ++++++++++-------- .../components/TranslationInputForm.tsx | 55 +---- .../components/UserAITranslationStatus.tsx | 154 ------------- .../translator/functions/mutations.server.ts | 26 +-- web/app/routes/translator/route.tsx | 60 ++--- web/app/routes/translator/types.ts | 2 +- .../welcome/functions/mutations.server.ts | 2 +- web/app/routes/welcome/route.tsx | 13 +- web/app/utils/target-language.server.ts | 9 +- web/package.json | 2 + .../migrations/20240812055942_/migration.sql | 21 ++ .../migrations/20240812063423_/migration.sql | 9 + web/prisma/schema.prisma | 9 +- 40 files changed, 1135 insertions(+), 939 deletions(-) delete mode 100644 web/app/components/TargetLanguageSelect.tsx create mode 100644 web/app/components/ui/command.tsx create mode 100644 web/app/components/ui/popover.tsx rename web/app/routes/$userName+/edit/functions/{queries.server.ts => mutations.server.ts} (51%) create mode 100644 web/app/routes/$userName+/page+/$slug+/components/UserAITranslationStatus.tsx create mode 100644 web/app/routes/$userName+/page+/$slug+/constants/languages.ts rename web/app/routes/$userName+/page+/$slug+/edit/{index.tsx => _edit.tsx} (95%) delete mode 100644 web/app/routes/_index/route.tsx delete mode 100644 web/app/routes/translator/components/UserAITranslationStatus.tsx create mode 100644 web/prisma/migrations/20240812055942_/migration.sql create mode 100644 web/prisma/migrations/20240812063423_/migration.sql diff --git a/web/app/components/TargetLanguageSelect.tsx b/web/app/components/TargetLanguageSelect.tsx deleted file mode 100644 index 27842b6e..00000000 --- a/web/app/components/TargetLanguageSelect.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { useFetcher } from "@remix-run/react"; -import { useCallback, useEffect, useState } from "react"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "~/components/ui/select"; - -type TargetLanguage = { - code: string; - name: string; -}; - -const targetLanguages: TargetLanguage[] = [ - { code: "ja", name: "日本語" }, - { code: "en", name: "English" }, - { code: "zh", name: "中文" }, - { code: "ko", name: "한국어" }, - { code: "es", name: "Español" }, - { code: "fr", name: "Français" }, -]; - -export function TargetLanguageSelect() { - const fetcher = useFetcher<{ targetLanguage: string }>(); - const [currentLanguage, setCurrentLanguage] = useState( - fetcher.data?.targetLanguage ?? "ja", - ); - - const loadLanguage = useCallback(() => { - if (fetcher.state === "idle" && !fetcher.data) { - fetcher.load("/api/target-language"); - } - }, [fetcher]); - - useEffect(() => { - loadLanguage(); - }, [loadLanguage]); - - const handleLanguageChange = (value: string) => { - setCurrentLanguage(value); - fetcher.submit( - { targetLanguage: value }, - { method: "post", action: "/api/target-language" }, - ); - }; - - return ( - - ); -} diff --git a/web/app/components/ui/command.tsx b/web/app/components/ui/command.tsx new file mode 100644 index 00000000..8b038be9 --- /dev/null +++ b/web/app/components/ui/command.tsx @@ -0,0 +1,153 @@ +import type { DialogProps } from "@radix-ui/react-dialog"; +import { MagnifyingGlassIcon } from "@radix-ui/react-icons"; +import { Command as CommandPrimitive } from "cmdk"; +import * as React from "react"; + +import { Dialog, DialogContent } from "~/components/ui/dialog"; +import { cn } from "~/utils/cn"; + +const Command = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +Command.displayName = CommandPrimitive.displayName; + +interface CommandDialogProps extends DialogProps {} + +const CommandDialog = ({ children, ...props }: CommandDialogProps) => { + return ( + + + + {children} + + + + ); +}; + +const CommandInput = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( +
+ + +
+)); + +CommandInput.displayName = CommandPrimitive.Input.displayName; + +const CommandList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandList.displayName = CommandPrimitive.List.displayName; + +const CommandEmpty = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>((props, ref) => ( + +)); + +CommandEmpty.displayName = CommandPrimitive.Empty.displayName; + +const CommandGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandGroup.displayName = CommandPrimitive.Group.displayName; + +const CommandSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +CommandSeparator.displayName = CommandPrimitive.Separator.displayName; + +const CommandItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandItem.displayName = CommandPrimitive.Item.displayName; + +const CommandShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ); +}; +CommandShortcut.displayName = "CommandShortcut"; + +export { + Command, + CommandDialog, + CommandInput, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, + CommandShortcut, + CommandSeparator, +}; diff --git a/web/app/components/ui/popover.tsx b/web/app/components/ui/popover.tsx new file mode 100644 index 00000000..638d5194 --- /dev/null +++ b/web/app/components/ui/popover.tsx @@ -0,0 +1,31 @@ +import * as PopoverPrimitive from "@radix-ui/react-popover"; +import * as React from "react"; + +import { cn } from "~/utils/cn"; + +const Popover = PopoverPrimitive.Root; + +const PopoverTrigger = PopoverPrimitive.Trigger; + +const PopoverAnchor = PopoverPrimitive.Anchor; + +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + + + +)); +PopoverContent.displayName = PopoverPrimitive.Content.displayName; + +export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }; diff --git a/web/app/features/translate/functions/mutations.server.ts b/web/app/features/translate/functions/mutations.server.ts index ec72b372..a68eb811 100644 --- a/web/app/features/translate/functions/mutations.server.ts +++ b/web/app/features/translate/functions/mutations.server.ts @@ -38,24 +38,18 @@ export async function getOrCreatePageTranslationInfo( } export async function updateUserAITranslationInfo( - userId: number, - slug: string, - targetLanguage: string, + userAITranslationInfoId: number, status: string, progress: number, ) { return await prisma.userAITranslationInfo.update({ where: { - userId_slug_targetLanguage: { - userId, - slug, - targetLanguage, - }, + id: userAITranslationInfoId, }, data: { aiTranslationStatus: status, aiTranslationProgress: progress, - lastTranslatedAt: new Date(), + createdAt: new Date(), }, }); } diff --git a/web/app/features/translate/lib/translate.server.ts b/web/app/features/translate/lib/translate.server.ts index b804914e..bd2d3339 100644 --- a/web/app/features/translate/lib/translate.server.ts +++ b/web/app/features/translate/lib/translate.server.ts @@ -10,14 +10,12 @@ import { extractTranslations } from "../utils/extractTranslations.server"; import { splitNumberedElements } from "../utils/splitNumberedElements.server"; export async function translate(params: TranslateJobParams) { - await updateUserAITranslationInfo( - params.userId, - params.slug, - params.targetLanguage, - "in_progress", - 0, - ); try { + await updateUserAITranslationInfo( + params.userAITranslationInfoId, + "in_progress", + 0, + ); const chunks = splitNumberedElements(params.numberedElements); const totalChunks = chunks.length; for (let i = 0; i < chunks.length; i++) { @@ -33,26 +31,20 @@ export async function translate(params: TranslateJobParams) { ); const progress = ((i + 1) / totalChunks) * 100; await updateUserAITranslationInfo( - params.userId, - params.slug, - params.targetLanguage, + params.userAITranslationInfoId, "in_progress", progress, ); } await updateUserAITranslationInfo( - params.userId, - params.slug, - params.targetLanguage, + params.userAITranslationInfoId, "completed", 100, ); } catch (error) { console.error("Background translation job failed:", error); await updateUserAITranslationInfo( - params.userId, - params.slug, - params.targetLanguage, + params.userAITranslationInfoId, "failed", 0, ); diff --git a/web/app/features/translate/translate-user-queue.ts b/web/app/features/translate/translate-user-queue.ts index 0ec1e3b2..368f2f39 100644 --- a/web/app/features/translate/translate-user-queue.ts +++ b/web/app/features/translate/translate-user-queue.ts @@ -2,7 +2,7 @@ import { Queue } from "~/utils/queue.server"; import { translate } from "./lib/translate.server"; import type { TranslateJobParams } from "./types"; -const QUEUE_VERSION = 4; +const QUEUE_VERSION = 2; export const getTranslateUserQueue = (userId: number) => { return Queue( diff --git a/web/app/features/translate/types.ts b/web/app/features/translate/types.ts index b1095775..d47625e5 100644 --- a/web/app/features/translate/types.ts +++ b/web/app/features/translate/types.ts @@ -4,13 +4,13 @@ export type NumberedElement = { }; export interface TranslateJobParams { + userId: number; + pageId: number; + userAITranslationInfoId: number; geminiApiKey: string; aiModel: string; - userId: number; targetLanguage: string; - pageId: number; title: string; numberedContent: string; numberedElements: NumberedElement[]; - slug: string; } diff --git a/web/app/root.tsx b/web/app/root.tsx index 7f071c26..843fb323 100644 --- a/web/app/root.tsx +++ b/web/app/root.tsx @@ -42,20 +42,29 @@ export function Layout({ children }: { children: React.ReactNode }) { ); } -function CommonLayout({ children }: { children: React.ReactNode }) { +function CommonLayout({ + children, + showHeaderFooter = true, +}: { children: React.ReactNode; showHeaderFooter?: boolean }) { const { safeUser } = useTypedLoaderData(); return ( <> -
-
{children}
-