...
This commit is contained in:
@@ -2,16 +2,15 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import Container from "@/components/ui/Container";
|
import Container from "@/components/ui/Container";
|
||||||
import { lookUp } from "@/lib/server/bigmodel/dictionaryActions";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { authClient } from "@/lib/auth-client";
|
import { authClient } from "@/lib/auth-client";
|
||||||
import { Folder } from "../../../../generated/prisma/browser";
|
import { Folder } from "../../../../generated/prisma/browser";
|
||||||
import { getFoldersByUserId } from "@/lib/server/services/folderService";
|
import { getFoldersByUserId } from "@/lib/server/services/folderService";
|
||||||
import { DictLookUpResponse, isDictErrorResponse } from "./types";
|
import { DictLookUpResponse } from "./types";
|
||||||
import { SearchForm } from "./SearchForm";
|
import { SearchForm } from "./SearchForm";
|
||||||
import { SearchResult } from "./SearchResult";
|
import { SearchResult } from "./SearchResult";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { POPULAR_LANGUAGES } from "./constants";
|
import { POPULAR_LANGUAGES } from "./constants";
|
||||||
|
import { performDictionaryLookup } from "./utils";
|
||||||
|
|
||||||
export default function Dictionary() {
|
export default function Dictionary() {
|
||||||
const t = useTranslations("dictionary");
|
const t = useTranslations("dictionary");
|
||||||
@@ -52,28 +51,22 @@ export default function Dictionary() {
|
|||||||
setHasSearched(true);
|
setHasSearched(true);
|
||||||
setSearchResult(null);
|
setSearchResult(null);
|
||||||
|
|
||||||
try {
|
const result = await performDictionaryLookup(
|
||||||
// 使用查询语言和释义语言的 nativeName
|
{
|
||||||
const result = await lookUp({
|
|
||||||
text: searchQuery,
|
text: searchQuery,
|
||||||
definitionLang: getNativeName(definitionLang),
|
queryLang: getNativeName(queryLang),
|
||||||
queryLang: getNativeName(queryLang)
|
definitionLang: getNativeName(definitionLang)
|
||||||
})
|
},
|
||||||
|
t
|
||||||
|
);
|
||||||
|
|
||||||
// 检查是否为错误响应
|
if (result.success && result.data) {
|
||||||
if (isDictErrorResponse(result)) {
|
setSearchResult(result.data);
|
||||||
toast.error(result.error);
|
|
||||||
setSearchResult(null);
|
|
||||||
} else {
|
} else {
|
||||||
setSearchResult(result);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("词典查询失败:", error);
|
|
||||||
toast.error(t("lookupFailed"));
|
|
||||||
setSearchResult(null);
|
setSearchResult(null);
|
||||||
} finally {
|
|
||||||
setIsSearching(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setIsSearching(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -111,7 +104,7 @@ export default function Dictionary() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isSearching && searchResult && !isDictErrorResponse(searchResult) && (
|
{!isSearching && searchResult && (
|
||||||
<SearchResult
|
<SearchResult
|
||||||
searchResult={searchResult}
|
searchResult={searchResult}
|
||||||
searchQuery={searchQuery}
|
searchQuery={searchQuery}
|
||||||
|
|||||||
@@ -3,17 +3,15 @@ import { toast } from "sonner";
|
|||||||
import { authClient } from "@/lib/auth-client";
|
import { authClient } from "@/lib/auth-client";
|
||||||
import { Folder } from "../../../../generated/prisma/browser";
|
import { Folder } from "../../../../generated/prisma/browser";
|
||||||
import { createPair } from "@/lib/server/services/pairService";
|
import { createPair } from "@/lib/server/services/pairService";
|
||||||
import { lookUp } from "@/lib/server/bigmodel/dictionaryActions";
|
|
||||||
import {
|
import {
|
||||||
DictWordResponse,
|
DictWordResponse,
|
||||||
DictPhraseResponse,
|
DictPhraseResponse,
|
||||||
isDictWordResponse,
|
isDictWordResponse,
|
||||||
DictWordEntry,
|
DictWordEntry,
|
||||||
isDictErrorResponse,
|
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { DictionaryEntry } from "./DictionaryEntry";
|
import { DictionaryEntry } from "./DictionaryEntry";
|
||||||
import { POPULAR_LANGUAGES } from "./constants";
|
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
import { performDictionaryLookup } from "./utils";
|
||||||
|
|
||||||
interface SearchResultProps {
|
interface SearchResultProps {
|
||||||
searchResult: DictWordResponse | DictPhraseResponse;
|
searchResult: DictWordResponse | DictPhraseResponse;
|
||||||
@@ -46,26 +44,21 @@ export function SearchResult({
|
|||||||
const handleRelookup = async () => {
|
const handleRelookup = async () => {
|
||||||
onSearchingChange(true);
|
onSearchingChange(true);
|
||||||
|
|
||||||
try {
|
const result = await performDictionaryLookup(
|
||||||
const result = await lookUp({
|
{
|
||||||
text: searchQuery,
|
text: searchQuery,
|
||||||
definitionLang: getNativeName(definitionLang),
|
|
||||||
queryLang: getNativeName(queryLang),
|
queryLang: getNativeName(queryLang),
|
||||||
|
definitionLang: getNativeName(definitionLang),
|
||||||
forceRelook: true
|
forceRelook: true
|
||||||
});
|
},
|
||||||
|
t
|
||||||
|
);
|
||||||
|
|
||||||
if (isDictErrorResponse(result)) {
|
if (result.success && result.data) {
|
||||||
toast.error(result.error);
|
onResultUpdate(result.data);
|
||||||
} else {
|
|
||||||
onResultUpdate(result);
|
|
||||||
toast.success(t("relookupSuccess"));
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.error("词典重新查询失败:", error);
|
|
||||||
toast.error(t("lookupFailed"));
|
|
||||||
} finally {
|
|
||||||
onSearchingChange(false);
|
onSearchingChange(false);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
|
|||||||
51
src/app/(features)/dictionary/utils.ts
Normal file
51
src/app/(features)/dictionary/utils.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { toast } from "sonner";
|
||||||
|
import { lookUp } from "@/lib/server/bigmodel/dictionaryActions";
|
||||||
|
import {
|
||||||
|
DictWordResponse,
|
||||||
|
DictPhraseResponse,
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
|
interface LookupOptions {
|
||||||
|
text: string;
|
||||||
|
queryLang: string;
|
||||||
|
definitionLang: string;
|
||||||
|
forceRelook?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LookupResult {
|
||||||
|
success: boolean;
|
||||||
|
data?: DictWordResponse | DictPhraseResponse;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行词典查询的通用函数
|
||||||
|
* @param options - 查询选项
|
||||||
|
* @param t - 翻译函数
|
||||||
|
* @returns 查询结果
|
||||||
|
*/
|
||||||
|
export async function performDictionaryLookup(
|
||||||
|
options: LookupOptions,
|
||||||
|
t?: (key: string) => string
|
||||||
|
): Promise<LookupResult> {
|
||||||
|
const { text, queryLang, definitionLang, forceRelook = false } = options;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await lookUp({
|
||||||
|
text,
|
||||||
|
queryLang,
|
||||||
|
definitionLang,
|
||||||
|
forceRelook
|
||||||
|
});
|
||||||
|
|
||||||
|
// 成功时显示提示(仅强制重新查询时)
|
||||||
|
if (forceRelook && t) {
|
||||||
|
toast.success(t("relookupSuccess"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true, data: result };
|
||||||
|
} catch (error) {
|
||||||
|
toast.error(String(error));
|
||||||
|
return { success: false, error: String(error) };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,16 +24,12 @@ export async function executeDictionaryLookup(
|
|||||||
// 代码层面验证:输入是否有效
|
// 代码层面验证:输入是否有效
|
||||||
if (!analysis.isValid) {
|
if (!analysis.isValid) {
|
||||||
console.log("[阶段1] 输入无效:", analysis.reason);
|
console.log("[阶段1] 输入无效:", analysis.reason);
|
||||||
return {
|
throw analysis.reason || "无效输入";
|
||||||
error: analysis.reason || "无效输入",
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (analysis.isEmpty) {
|
if (analysis.isEmpty) {
|
||||||
console.log("[阶段1] 输入为空");
|
console.log("[阶段1] 输入为空");
|
||||||
return {
|
throw "输入为空";
|
||||||
error: "输入为空",
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("[阶段1] 输入分析完成:", analysis);
|
console.log("[阶段1] 输入分析完成:", analysis);
|
||||||
@@ -65,9 +61,7 @@ export async function executeDictionaryLookup(
|
|||||||
// 代码层面验证:标准形式不能为空
|
// 代码层面验证:标准形式不能为空
|
||||||
if (!standardFormResult.standardForm) {
|
if (!standardFormResult.standardForm) {
|
||||||
console.error("[阶段3] 标准形式为空");
|
console.error("[阶段3] 标准形式为空");
|
||||||
return {
|
throw "无法生成标准形式";
|
||||||
error: "无法生成标准形式",
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("[阶段3] 标准形式生成完成:", standardFormResult);
|
console.log("[阶段3] 标准形式生成完成:", standardFormResult);
|
||||||
@@ -99,8 +93,6 @@ export async function executeDictionaryLookup(
|
|||||||
|
|
||||||
// 任何阶段失败都返回错误(包含 reason)
|
// 任何阶段失败都返回错误(包含 reason)
|
||||||
const errorMessage = error instanceof Error ? error.message : "未知错误";
|
const errorMessage = error instanceof Error ? error.message : "未知错误";
|
||||||
return {
|
throw errorMessage;
|
||||||
error: errorMessage,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,11 @@
|
|||||||
|
|
||||||
import { executeDictionaryLookup } from "./dictionary";
|
import { executeDictionaryLookup } from "./dictionary";
|
||||||
import { createLookUp, createPhrase, createWord, createPhraseEntry, createWordEntry, selectLastLookUp } from "../services/dictionaryService";
|
import { createLookUp, createPhrase, createWord, createPhraseEntry, createWordEntry, selectLastLookUp } from "../services/dictionaryService";
|
||||||
import { DictLookUpRequest, DictWordResponse, isDictErrorResponse, isDictPhraseResponse, isDictWordResponse, type DictLookUpResponse } from "@/lib/shared";
|
import { DictLookUpRequest, DictWordResponse, isDictPhraseResponse, isDictWordResponse, type DictLookUpResponse } from "@/lib/shared";
|
||||||
import { text } from "node:stream/consumers";
|
import { lookUpValidation } from "@/lib/shared/validations/dictionaryValidations";
|
||||||
|
|
||||||
const saveResult = async (req: DictLookUpRequest, res: DictLookUpResponse) => {
|
const saveResult = async (req: DictLookUpRequest, res: DictLookUpResponse) => {
|
||||||
if (isDictErrorResponse(res)) return;
|
if (isDictPhraseResponse(res)) {
|
||||||
else if (isDictPhraseResponse(res)) {
|
|
||||||
// 先创建 Phrase
|
// 先创建 Phrase
|
||||||
const phrase = await createPhrase({
|
const phrase = await createPhrase({
|
||||||
standardForm: res.standardForm,
|
standardForm: res.standardForm,
|
||||||
@@ -83,7 +82,8 @@ export const lookUp = async (req: DictLookUpRequest): Promise<DictLookUpResponse
|
|||||||
userId
|
userId
|
||||||
} = req;
|
} = req;
|
||||||
|
|
||||||
try {
|
lookUpValidation(req);
|
||||||
|
|
||||||
const lastLookUp = await selectLastLookUp({
|
const lastLookUp = await selectLastLookUp({
|
||||||
text,
|
text,
|
||||||
queryLang,
|
queryLang,
|
||||||
@@ -134,11 +134,7 @@ export const lookUp = async (req: DictLookUpRequest): Promise<DictLookUpResponse
|
|||||||
entries: lastLookUp.dictionaryPhrase!.entries
|
entries: lastLookUp.dictionaryPhrase!.entries
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return { error: "Database structure error!" };
|
throw "错误D101";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
return { error: "look up error" };
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,10 +18,6 @@ export type DictPhraseEntry = {
|
|||||||
example: string;
|
example: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DictErrorResponse = {
|
|
||||||
error: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type DictWordResponse = {
|
export type DictWordResponse = {
|
||||||
standardForm: string;
|
standardForm: string;
|
||||||
entries: DictWordEntry[];
|
entries: DictWordEntry[];
|
||||||
@@ -33,22 +29,13 @@ export type DictPhraseResponse = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type DictLookUpResponse =
|
export type DictLookUpResponse =
|
||||||
| DictErrorResponse
|
|
||||||
| DictWordResponse
|
| DictWordResponse
|
||||||
| DictPhraseResponse;
|
| DictPhraseResponse;
|
||||||
|
|
||||||
// 类型守卫:判断是否为错误响应
|
|
||||||
export function isDictErrorResponse(
|
|
||||||
response: DictLookUpResponse
|
|
||||||
): response is DictErrorResponse {
|
|
||||||
return "error" in response;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 类型守卫:判断是否为单词响应
|
// 类型守卫:判断是否为单词响应
|
||||||
export function isDictWordResponse(
|
export function isDictWordResponse(
|
||||||
response: DictLookUpResponse
|
response: DictLookUpResponse
|
||||||
): response is DictWordResponse {
|
): response is DictWordResponse {
|
||||||
if (isDictErrorResponse(response)) return false;
|
|
||||||
const entries = (response as DictWordResponse | DictPhraseResponse).entries;
|
const entries = (response as DictWordResponse | DictPhraseResponse).entries;
|
||||||
return entries.length > 0 && "ipa" in entries[0] && "partOfSpeech" in entries[0];
|
return entries.length > 0 && "ipa" in entries[0] && "partOfSpeech" in entries[0];
|
||||||
}
|
}
|
||||||
@@ -57,7 +44,6 @@ export function isDictWordResponse(
|
|||||||
export function isDictPhraseResponse(
|
export function isDictPhraseResponse(
|
||||||
response: DictLookUpResponse
|
response: DictLookUpResponse
|
||||||
): response is DictPhraseResponse {
|
): response is DictPhraseResponse {
|
||||||
if (isDictErrorResponse(response)) return false;
|
|
||||||
const entries = (response as DictWordResponse | DictPhraseResponse).entries;
|
const entries = (response as DictWordResponse | DictPhraseResponse).entries;
|
||||||
return entries.length > 0 && !("ipa" in entries[0] || "partOfSpeech" in entries[0]);
|
return entries.length > 0 && !("ipa" in entries[0] || "partOfSpeech" in entries[0]);
|
||||||
}
|
}
|
||||||
|
|||||||
22
src/lib/shared/validations/dictionaryValidations.ts
Normal file
22
src/lib/shared/validations/dictionaryValidations.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { DictLookUpRequest } from "@/lib/shared";
|
||||||
|
|
||||||
|
export const lookUpValidation = (req: DictLookUpRequest) => {
|
||||||
|
const {
|
||||||
|
text,
|
||||||
|
queryLang,
|
||||||
|
definitionLang,
|
||||||
|
} = req;
|
||||||
|
|
||||||
|
if (text.length > 30)
|
||||||
|
throw Error("The input should not exceed 30 characters.");
|
||||||
|
if (queryLang.length > 20)
|
||||||
|
throw Error("The query language should not exceed 20 characters.");
|
||||||
|
if (definitionLang.length > 20)
|
||||||
|
throw Error("The definition language should not exceed 20 characters.");
|
||||||
|
if (queryLang.length > 20)
|
||||||
|
throw Error("The query language should not exceed 20 characters.");
|
||||||
|
if (queryLang.length > 20)
|
||||||
|
throw Error("The query language should not exceed 20 characters.");
|
||||||
|
if (queryLang.length > 20)
|
||||||
|
throw Error("The query language should not exceed 20 characters.");
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user