Skip to content
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

Develop #27

Merged
merged 9 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,24 @@
# Authjs
AUTH_SECRET=""

# Google Analytics
NEXT_PUBLIC_GA_ID=""

# NextAuth
NEXTAUTH_URL=""

# Database
POSTGRES_DATABASE="verceldb"
POSTGRES_HOST=""
POSTGRES_PASSWORD=""
POSTGRES_PRISMA_URL=""
POSTGRES_URL=""
POSTGRES_URL_NON_POOLING=""
POSTGRES_URL_NO_SSL=""
POSTGRES_USER=""

# AI
AI=""
GPT_KEY=""
GEMINI_KEY=""
GPT_URL=""
1,035 changes: 1,013 additions & 22 deletions README.md

Large diffs are not rendered by default.

16 changes: 12 additions & 4 deletions app/actions/generate-word-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,19 @@ import { isDev } from "@/utils/is-dev";
import { WORD_EXAMPLE } from "@/utils/user-dict";
import { fetchChatCompletion } from "../../scripts/open-ai";

const promptTemplate = (word: string) => {
return `
参考该 JSON 信息:
${JSON.stringify(WORD_EXAMPLE)}
进行一下操作:
[1] 现有单词【${word}】,若单词不是韩文请先根据该单词转换成韩文,去掉所有标点。
[2] 如果该韩文单词没有对应的汉字词 或 不是汉字 或 该汉字词不常见,则跳过操作[3],进行操作[4]。
[3] 如果该韩文单词有对应的汉字词,则把汉字词(使用【】包裹)也作为单词释义并放在各中语言的单词释义的第一位,如”단풍“的汉字词为”丹楓“则【丹楓】也作为单词释义之一放在首位。
[4] 以该韩语单词作为 JSON 中的 name 字段,trans为单词释义、example为单词例句(可以使用活用后的单词)、exTrans为例句释义,生成 JSON 字符串,其他字段也应补充完整,仅返回对应的 JSON 字符串不要添加任何其他内容。`;
};

export const generateWordAction = async (word: string) => {
const prompt = `参考该 JSON 信息
${JSON.stringify(WORD_EXAMPLE)}
现有单词【${word}】,若单词不是韩文请先根据该单词转换成韩文,单词不需要任何标点,然后以该韩语单词作为 JSON 中的 name 字段,trans为单词释义、example为单词例句(可以使用活用后的单词)、exTrans为例句释义,生成 JSON 字符串,其他字段也应补充完整,仅返回对应的 JSON 字符串不要添加任何其他内容。
`;
const prompt = promptTemplate(word);
isDev && console.log("[generateWordAction][prompt]:", prompt);
const result = await fetchChatCompletion([
{
Expand Down
4 changes: 4 additions & 0 deletions app/assets/svg/setting.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 8 additions & 1 deletion app/components/dict-nav/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import NextIcon from "@/assets/svg/next.svg";
import PrevIcon from "@/assets/svg/prev.svg";

import { HideText } from "@/components/hide-text";
import type { Dict, DictItem, Tran } from "@/types/dict";
import { getTranslation } from "@/utils/convert-input";
import { notoKR } from "@/utils/fonts";
Expand All @@ -12,11 +13,13 @@ const DictNav = ({
curWordIndex,
onPrev,
onNext,
hideMeaning,
}: {
dict: Dict;
curWordIndex: number;
onPrev: () => void;
onNext: () => void;
hideMeaning: boolean;
}) => {
const prev = curWordIndex - 1 >= 0 ? dict[curWordIndex - 1] : null;
const next = curWordIndex + 1 < dict.length ? dict[curWordIndex + 1] : null;
Expand Down Expand Up @@ -53,7 +56,11 @@ const DictNav = ({
title={getTranslation(item, locale)}
className="text-xs text-gray-500 max-w-40 overflow-hidden text-ellipsis text-nowrap"
>
{getTranslation(item, locale)}
{
<HideText hide={hideMeaning}>
{getTranslation(item, locale)}
</HideText>
}
</p>
</div>
</div>
Expand Down
20 changes: 20 additions & 0 deletions app/components/hide-text.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import clsx from "clsx";
import type { PropsWithChildren } from "react";

const HideText = ({
hide = false,
children,
}: PropsWithChildren<{ hide?: boolean }>) => {
return (
<span
className={clsx(
"inline-block relative cursor-pointer hover:before:opacity-0 before:transition-opacity",
hide && "after-backdrop-shadow",
)}
>
{children}
</span>
);
};

export { HideText };
93 changes: 68 additions & 25 deletions app/components/home-drawer/dict-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
import { generateWordAction } from "@/actions/generate-word-action";
import DownloadIcon from "@/assets/svg/download.svg";
import FileImportIcon from "@/assets/svg/file-import.svg";
import SettingIcon from "@/assets/svg/setting.svg";
import ShuffleIcon from "@/assets/svg/shuffle.svg";
import { createToast } from "@/hooks/use-toast";
import type { HomeSetting } from "@/types";
import { type DictItem, Dicts } from "@/types/dict";
import { addUserDict, downLoadDict, importDict } from "@/utils/user-dict";
import { useTranslations } from "next-intl";
Expand All @@ -12,10 +14,15 @@ import { useRouter, useSearchParams } from "next/navigation";
const DictMenu = ({
onShuffle,
onUserDictUpdate,
onSettingChange,
setting,
}: {
onShuffle?: () => void;
onUserDictUpdate?: () => void;
onSettingChange?: (val: Partial<HomeSetting>) => void;
setting: HomeSetting;
}) => {
const tHome = useTranslations("Home");
const searchParams = useSearchParams();
const router = useRouter();
const tIndex = useTranslations("Dict");
Expand All @@ -27,24 +34,24 @@ const DictMenu = ({
};

const createWord = async () => {
// TODO: intl
const word = prompt("Enter new word", "좋아요");
const word = prompt(tHome("createWord"), tHome("exampleWord"));
if (word) {
const removeInfoToast = createToast({
type: "info",
delay: 60 * 1000 * 5,
message: (
<span>
<span className="loading loading-spinner loading-sm" />{" "}
Generating...
</span>
<div className="flex items-center">
<span className="loading loading-spinner loading-sm mr-2" />
{tHome("generating")}
</div>
),
});

try {
const result = await Promise.all(
word
.split(",")
.map((w) => w.trim())
.map(
async (w) =>
JSON.parse((await generateWordAction(w)) || "{}") as DictItem,
Expand All @@ -54,11 +61,11 @@ const DictMenu = ({
onUserDictUpdate?.();
createToast({
type: "success",
message: <span>Generated Success!</span>,
message: <span>{tHome("generated")}</span>,
});
} catch (error) {
console.error("[createWord]:\n", error);
createToast({ type: "error", message: "Generated Failed!" });
createToast({ type: "error", message: tHome("generateError") });
} finally {
removeInfoToast();
}
Expand All @@ -71,34 +78,70 @@ const DictMenu = ({

return (
<div className="sticky top-2 z-10 bg-base-200 rounded-xl mb-3 shadow-md flex justify-between items-center p-1">
<div className="pl-4 flex items-center">
<div className="pl-3 flex items-center *:mx-1 *:inline-block *:cursor-pointer *:select-none">
<ShuffleIcon
width={20}
height={20}
viewBox="0 0 24 24"
className="cursor-pointer inline-block"
onClick={onShuffle}
/>
<div className="dropdown dropdown-hover">
<SettingIcon width={20} height={20} viewBox="0 0 24 24" />
<div className="dropdown-content !cursor-auto bg-base-100 rounded-box z-[1] w-44 p-4 shadow flex flex-col gap-3">
<div className="flex justify-between items-center gap-2">
<label htmlFor="muteAudio" className="cursor-pointer flex-auto">
{tHome("enableAudio")}
</label>
<input
id="muteAudio"
type="checkbox"
className="toggle toggle-sm"
checked={setting.enableAudio}
onChange={(e) =>
onSettingChange?.({ enableAudio: e.target.checked })
}
/>
</div>
<div className="flex justify-between items-center gap-2">
<label htmlFor="autoVoice" className="cursor-pointer flex-auto">
{tHome("autoVoice")}
</label>
<input
id="autoVoice"
type="checkbox"
className="toggle toggle-sm"
checked={setting.autoVoice}
onChange={(e) =>
onSettingChange?.({ autoVoice: e.target.checked })
}
/>
</div>
<div className="flex justify-between items-center gap-2">
<label htmlFor="hideMeaning" className="cursor-pointer flex-auto">
{tHome("showMeaning")}
</label>
<input
id="hideMeaning"
type="checkbox"
className="toggle toggle-sm"
checked={setting.showMeaning}
onChange={(e) =>
onSettingChange?.({
showMeaning: e.target.checked,
})
}
/>
</div>
</div>
</div>
{isUserDict && (
<>
<span
onClick={createWord}
className="inline-block px-2 text-xl cursor-pointer"
>
<span onClick={createWord} className="text-xl">
+
</span>
<DownloadIcon
width={20}
height={20}
onClick={downLoadDict}
className="cursor-pointer inline-block mx-1"
/>
<FileImportIcon
width={20}
height={20}
onClick={handleImport}
className="cursor-pointer inline-block mx-1"
/>
<DownloadIcon width={20} height={20} onClick={downLoadDict} />
<FileImportIcon width={20} height={20} onClick={handleImport} />
</>
)}
</div>
Expand Down
9 changes: 8 additions & 1 deletion app/components/home-drawer/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import CloseIcon from "@/assets/svg/close.svg";
import { ClientOnly } from "@/components/client-only";
import type { HomeSetting } from "@/types";
import { type Dict, Dicts } from "@/types/dict";
import { getTranslation } from "@/utils/convert-input";
import { isServer } from "@/utils/is-server";
Expand All @@ -19,13 +20,17 @@ const HomeDrawer = ({
onShuffle,
onUserDictUpdate,
drawerRef,
setting,
onSettingChange,
}: {
dict: Dict;
curWordIndex: number;
onClick: (index: number) => void;
drawerRef: React.RefObject<{ open: () => void }>;
onShuffle: () => void;
onUserDictUpdate: () => void;
setting: HomeSetting;
onSettingChange: (val: Partial<HomeSetting>) => void;
}) => {
const locale = useLocale();
const searchParams = useSearchParams();
Expand Down Expand Up @@ -73,8 +78,10 @@ const HomeDrawer = ({
aria-label="close sidebar"
className="drawer-overlay"
/>
<ul className="menu bg-base-100 text-base-content min-h-full w-80 p-4">
<ul className="menu bg-base-100 text-base-content min-h-full w-5/6 sm:w-80 p-4">
<DictMenu
setting={setting}
onSettingChange={onSettingChange}
onShuffle={onShuffle}
onUserDictUpdate={onUserDictUpdate}
/>
Expand Down
Loading