-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/translationProgress #38
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,7 +17,7 @@ services: | |
restart: always | ||
ports: | ||
- '6379:6379' | ||
command: redis-server --save 20 1 --loglevel warning --requirepass eYVX7EwVmmxKPCDmwMtyKVge8oLd2t81 | ||
command: redis-server --save 20 1 --loglevel warning | ||
volumes: | ||
- redis:/data | ||
|
||
Comment on lines
17
to
23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
- @@foreignKey([userId], references: [id], onDelete: Cascade)
- @@foreignKey([pageVersionId], references: [id], onDelete: Cascade)
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
+ pageVersion PageVersion @relation(fields: [pageVersionId], references: [id], onDelete: Cascade) |
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
export function LoadingSpinner() { | ||
return ( | ||
<> | ||
<div className="animate-spin rounded-full h-4 w-4 border-t-2 border-b-2 border-white"></div> | ||
<div className="animate-spin rounded-full h-4 w-4 border-t-2 border-b-2 border-white" /> | ||
</> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,36 +1,36 @@ | ||
import * as React from "react" | ||
import { cva, type VariantProps } from "class-variance-authority" | ||
import { type VariantProps, cva } from "class-variance-authority"; | ||
import type * as React from "react"; | ||
|
||
import { cn } from "~/utils/cn" | ||
import { cn } from "~/utils/cn"; | ||
|
||
const badgeVariants = cva( | ||
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", | ||
{ | ||
variants: { | ||
variant: { | ||
default: | ||
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", | ||
secondary: | ||
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", | ||
destructive: | ||
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", | ||
outline: "text-foreground", | ||
}, | ||
}, | ||
defaultVariants: { | ||
variant: "default", | ||
}, | ||
} | ||
) | ||
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", | ||
{ | ||
variants: { | ||
variant: { | ||
default: | ||
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", | ||
secondary: | ||
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", | ||
destructive: | ||
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", | ||
outline: "text-foreground", | ||
}, | ||
}, | ||
defaultVariants: { | ||
variant: "default", | ||
}, | ||
}, | ||
); | ||
|
||
export interface BadgeProps | ||
extends React.HTMLAttributes<HTMLDivElement>, | ||
VariantProps<typeof badgeVariants> {} | ||
extends React.HTMLAttributes<HTMLDivElement>, | ||
VariantProps<typeof badgeVariants> {} | ||
|
||
function Badge({ className, variant, ...props }: BadgeProps) { | ||
return ( | ||
<div className={cn(badgeVariants({ variant }), className)} {...props} /> | ||
) | ||
return ( | ||
<div className={cn(badgeVariants({ variant }), className)} {...props} /> | ||
); | ||
} | ||
|
||
export { Badge, badgeVariants } | ||
export { Badge, badgeVariants }; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,26 @@ | ||
import * as React from "react" | ||
import * as ProgressPrimitive from "@radix-ui/react-progress" | ||
import * as ProgressPrimitive from "@radix-ui/react-progress"; | ||
import * as React from "react"; | ||
|
||
import { cn } from "~/utils/cn" | ||
import { cn } from "~/utils/cn"; | ||
|
||
const Progress = React.forwardRef< | ||
React.ElementRef<typeof ProgressPrimitive.Root>, | ||
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root> | ||
React.ElementRef<typeof ProgressPrimitive.Root>, | ||
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root> | ||
>(({ className, value, ...props }, ref) => ( | ||
<ProgressPrimitive.Root | ||
ref={ref} | ||
className={cn( | ||
"relative h-2 w-full overflow-hidden rounded-full bg-primary/20", | ||
className | ||
)} | ||
{...props} | ||
> | ||
<ProgressPrimitive.Indicator | ||
className="h-full w-full flex-1 bg-primary transition-all" | ||
style={{ transform: `translateX(-${100 - (value || 0)}%)` }} | ||
/> | ||
</ProgressPrimitive.Root> | ||
)) | ||
Progress.displayName = ProgressPrimitive.Root.displayName | ||
<ProgressPrimitive.Root | ||
ref={ref} | ||
className={cn( | ||
"relative h-2 w-full overflow-hidden rounded-full bg-primary/20", | ||
className, | ||
)} | ||
{...props} | ||
> | ||
<ProgressPrimitive.Indicator | ||
className="h-full w-full flex-1 bg-primary transition-all" | ||
style={{ transform: `translateX(-${100 - (value || 0)}%)` }} | ||
/> | ||
</ProgressPrimitive.Root> | ||
)); | ||
Progress.displayName = ProgressPrimitive.Root.displayName; | ||
|
||
export { Progress } | ||
export { Progress }; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,46 +1,46 @@ | ||
import * as React from "react" | ||
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" | ||
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"; | ||
import * as React from "react"; | ||
|
||
import { cn } from "~/utils/cn" | ||
import { cn } from "~/utils/cn"; | ||
|
||
const ScrollArea = React.forwardRef< | ||
React.ElementRef<typeof ScrollAreaPrimitive.Root>, | ||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root> | ||
React.ElementRef<typeof ScrollAreaPrimitive.Root>, | ||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root> | ||
>(({ className, children, ...props }, ref) => ( | ||
<ScrollAreaPrimitive.Root | ||
ref={ref} | ||
className={cn("relative overflow-hidden", className)} | ||
{...props} | ||
> | ||
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]"> | ||
{children} | ||
</ScrollAreaPrimitive.Viewport> | ||
<ScrollBar /> | ||
<ScrollAreaPrimitive.Corner /> | ||
</ScrollAreaPrimitive.Root> | ||
)) | ||
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName | ||
<ScrollAreaPrimitive.Root | ||
ref={ref} | ||
className={cn("relative overflow-hidden", className)} | ||
{...props} | ||
> | ||
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]"> | ||
{children} | ||
</ScrollAreaPrimitive.Viewport> | ||
<ScrollBar /> | ||
<ScrollAreaPrimitive.Corner /> | ||
</ScrollAreaPrimitive.Root> | ||
)); | ||
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName; | ||
|
||
const ScrollBar = React.forwardRef< | ||
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>, | ||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar> | ||
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>, | ||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar> | ||
>(({ className, orientation = "vertical", ...props }, ref) => ( | ||
<ScrollAreaPrimitive.ScrollAreaScrollbar | ||
ref={ref} | ||
orientation={orientation} | ||
className={cn( | ||
"flex touch-none select-none transition-colors", | ||
orientation === "vertical" && | ||
"h-full w-2.5 border-l border-l-transparent p-[1px]", | ||
orientation === "horizontal" && | ||
"h-2.5 flex-col border-t border-t-transparent p-[1px]", | ||
className | ||
)} | ||
{...props} | ||
> | ||
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" /> | ||
</ScrollAreaPrimitive.ScrollAreaScrollbar> | ||
)) | ||
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName | ||
<ScrollAreaPrimitive.ScrollAreaScrollbar | ||
ref={ref} | ||
orientation={orientation} | ||
className={cn( | ||
"flex touch-none select-none transition-colors", | ||
orientation === "vertical" && | ||
"h-full w-2.5 border-l border-l-transparent p-[1px]", | ||
orientation === "horizontal" && | ||
"h-2.5 flex-col border-t border-t-transparent p-[1px]", | ||
className, | ||
)} | ||
{...props} | ||
> | ||
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" /> | ||
</ScrollAreaPrimitive.ScrollAreaScrollbar> | ||
)); | ||
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName; | ||
|
||
export { ScrollArea, ScrollBar } | ||
export { ScrollArea, ScrollBar }; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import * as TooltipPrimitive from "@radix-ui/react-tooltip"; | ||
import * as React from "react"; | ||
|
||
import { cn } from "~/utils/cn"; | ||
|
||
const TooltipProvider = TooltipPrimitive.Provider; | ||
|
||
const Tooltip = TooltipPrimitive.Root; | ||
|
||
const TooltipTrigger = TooltipPrimitive.Trigger; | ||
|
||
const TooltipContent = React.forwardRef< | ||
React.ElementRef<typeof TooltipPrimitive.Content>, | ||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content> | ||
>(({ className, sideOffset = 4, ...props }, ref) => ( | ||
<TooltipPrimitive.Content | ||
ref={ref} | ||
sideOffset={sideOffset} | ||
className={cn( | ||
"z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", | ||
className, | ||
)} | ||
{...props} | ||
/> | ||
)); | ||
TooltipContent.displayName = TooltipPrimitive.Content.displayName; | ||
|
||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
import { createHash } from "node:crypto"; | ||
import { prisma } from "./prisma"; | ||
import { prisma } from "../utils/prisma"; | ||
|
||
export async function getOrCreatePageVersionId( | ||
url: string, | ||
Comment on lines
1
to
5
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
+ try {
+ const userId = await requireUserId(request);
+ } catch (error) {
+ return redirect("/login");
+ }
Comment on lines
1
to
5
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
- throw new Response("Page version not found", { status: 404 });
+ throw new Response("Not Found", { status: 404 });
Comment on lines
1
to
5
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
フォームデータの取得時に型チェックが不足しています。 - const text = formData.get("text");
- const targetLanguage = formData.get("targetLanguage");
+ const text = formData.get("text")?.toString() || "";
+ const targetLanguage = formData.get("targetLanguage")?.toString() || ""; Changes made to
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { prisma } from "~/utils/prisma"; | ||
|
||
export async function getOrCreatePageVersionTranslationInfo( | ||
pageVersionId: number, | ||
targetLanguage: string, | ||
translationTitle: string, | ||
) { | ||
return await prisma.pageVersionTranslationInfo.upsert({ | ||
where: { | ||
pageVersionId_targetLanguage: { | ||
pageVersionId, | ||
targetLanguage, | ||
}, | ||
}, | ||
update: {}, // 既存のレコードがある場合は更新しない | ||
create: { | ||
pageVersionId, | ||
targetLanguage, | ||
translationTitle, | ||
}, | ||
}); | ||
Comment on lines
+3
to
+21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
import { createHash } from "node:crypto"; | ||
import { prisma } from "./prisma"; | ||
import { prisma } from "../utils/prisma"; | ||
|
||
export async function getOrCreateSourceTextId( | ||
text: string, | ||
Comment on lines
1
to
5
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { prisma } from "~/utils/prisma"; | ||
|
||
export async function getOrCreateUserAITranslationInfo( | ||
userId: number, | ||
pageVersionId: number, | ||
targetLanguage: string, | ||
) { | ||
try { | ||
const userAITranslationInfo = await prisma.userAITranslationInfo.upsert({ | ||
where: { | ||
userId_pageVersionId_targetLanguage: { | ||
userId, | ||
pageVersionId, | ||
targetLanguage, | ||
}, | ||
}, | ||
update: { | ||
aiTranslationStatus: "pending", | ||
aiTranslationProgress: 0, | ||
}, | ||
create: { | ||
userId, | ||
pageVersionId, | ||
targetLanguage, | ||
aiTranslationStatus: "pending", | ||
aiTranslationProgress: 0, | ||
}, | ||
}); | ||
return userAITranslationInfo; | ||
} catch (error) { | ||
console.error("Error in getOrCreateUserAITranslationInfo:", error); | ||
throw error; | ||
} | ||
Comment on lines
+8
to
+33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. エラーハンドリングを改善するために、 - console.error("Error in getOrCreateUserAITranslationInfo:", error);
+ console.error("Error in getOrCreateUserAITranslationInfo:", {
+ message: error.message,
+ stack: error.stack,
+ context: { userId, pageVersionId, targetLanguage }
+ }); |
||
} | ||
|
||
export async function updateUserAITranslationInfo( | ||
userId: number, | ||
pageVersionId: number, | ||
targetLanguage: string, | ||
status: string, | ||
progress: number, | ||
) { | ||
return await prisma.userAITranslationInfo.update({ | ||
where: { | ||
userId_pageVersionId_targetLanguage: { | ||
userId, | ||
pageVersionId, | ||
targetLanguage, | ||
}, | ||
}, | ||
data: { | ||
aiTranslationStatus: status, | ||
aiTranslationProgress: progress, | ||
lastTranslatedAt: new Date(), // 明示的に更新 | ||
}, | ||
}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
import { prisma } from "./prisma"; | ||
import { prisma } from "../utils/prisma"; | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. インポートの順序を整理し、標準ライブラリ、サードパーティライブラリ、ローカルモジュールの順に並べると良いでしょう。 + import { json, LoaderFunction, ActionFunction } from "@remix-run/node";
+ import { useLoaderData, useActionData } from "@remix-run/react";
+ import { z } from "zod";
+ import { db } from "~/utils/db.server";
+ import { requireUserId } from "~/utils/session.server";
+ import { TranslatePage } from "~/components/TranslatePage"; |
||
export async function updateUserReadHistory( | ||
userId: number, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CodeRabbit
エラーメッセージの詳細を追加し、ユーザーにとってわかりやすくするために、エラーハンドリングを改善できます。