From 586b2f72511ba56dabecea85ac0640b3c5aa20d0 Mon Sep 17 00:00:00 2001 From: JOYCEQL <1449239013@qq.com> Date: Sun, 22 Dec 2024 16:10:54 +0800 Subject: [PATCH] refactor: grammar hook and store --- .../src/components/preview/PreviewDock.tsx | 1 - apps/fronted/src/hooks/useGrammarCheck.ts | 152 +----------------- apps/fronted/src/store/useGrammarStore.ts | 150 +++++++++++++++++ 3 files changed, 154 insertions(+), 149 deletions(-) create mode 100644 apps/fronted/src/store/useGrammarStore.ts diff --git a/apps/fronted/src/components/preview/PreviewDock.tsx b/apps/fronted/src/components/preview/PreviewDock.tsx index 60376b2..169a68b 100644 --- a/apps/fronted/src/components/preview/PreviewDock.tsx +++ b/apps/fronted/src/components/preview/PreviewDock.tsx @@ -74,7 +74,6 @@ export const PreviewDock = ({ try { const text = resumeContentRef.current.innerText; await checkGrammar(text); - // toast.success("语法检查完成"); } catch (error) { toast.error("语法检查失败,请重试"); } diff --git a/apps/fronted/src/hooks/useGrammarCheck.ts b/apps/fronted/src/hooks/useGrammarCheck.ts index f97e832..6ffebfa 100644 --- a/apps/fronted/src/hooks/useGrammarCheck.ts +++ b/apps/fronted/src/hooks/useGrammarCheck.ts @@ -1,7 +1,7 @@ import { create } from "zustand"; import { toast } from "sonner"; import Mark from "mark.js"; -import { useAIConfigStore } from "@/store/useAIConfigStore"; +import { useGrammarStore } from "@/store/useGrammarStore"; export interface GrammarError { text: string; @@ -10,160 +10,16 @@ export interface GrammarError { suggestions: string[]; } -interface GrammarState { - errors: GrammarError[]; - isChecking: boolean; - selectedErrorIndex: number | null; - highlightKey: number; - setErrors: (errors: GrammarError[]) => void; - setIsChecking: (isChecking: boolean) => void; - setSelectedErrorIndex: (index: number | null) => void; - incrementHighlightKey: () => void; -} - -export const useGrammarStore = create((set) => ({ - errors: [], - isChecking: false, - selectedErrorIndex: null, - highlightKey: 0, - setErrors: (errors) => { - const preview = document.getElementById("resume-preview"); - if (preview) { - const marker = new Mark(preview); - marker.unmark(); - } - set({ errors }); - }, - setIsChecking: (isChecking) => set({ isChecking }), - setSelectedErrorIndex: (selectedErrorIndex) => set({ selectedErrorIndex }), - incrementHighlightKey: () => - set((state) => ({ highlightKey: state.highlightKey + 1 })), -})); - export const useGrammarCheck = () => { const { errors, isChecking, selectedErrorIndex, - highlightKey, - setErrors, - setIsChecking, - setSelectedErrorIndex, + checkGrammar, + clearErrors, + selectError, } = useGrammarStore(); - const { doubaoApiKey, doubaoModelId } = useAIConfigStore(); - - const checkGrammar = async (text: string) => { - setIsChecking(true); - try { - const response = await fetch("/api/grammar", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - apiKey: doubaoApiKey, - model: doubaoModelId, - content: text, - }), - }); - - if (!response.ok) { - throw new Error(`API request failed: ${response.status}`); - } - - const data = await response.json(); - - if (data.error.code === "AuthenticationError") { - toast.error("ApiKey 或 模型Id 不正确"); - throw new Error(data.error.message); - } - - const aiResponse = data.choices[0]?.message?.content; - - try { - const grammarErrors = JSON.parse(aiResponse).errors; - toast.success("语法检查完成"); - setErrors(grammarErrors); - - const preview = document.getElementById("resume-preview"); - if (preview && grammarErrors.length > 0) { - const marker = new Mark(preview); - grammarErrors.forEach((error: GrammarError) => { - marker.mark(error.text, { - className: `grammar-error ${error.type}`, - acrossElements: true, - separateWordSearch: false, - caseSensitive: true, - }); - }); - } - } catch (parseError) { - toast.error(`解析AI响应失败: ${parseError}`); - setErrors([]); - } - } catch (error) { - setErrors([]); - } finally { - setIsChecking(false); - } - }; - - const clearErrors = () => { - const preview = document.getElementById("resume-preview"); - if (preview) { - const marker = new Mark(preview); - marker.unmark(); - } - setErrors([]); - setSelectedErrorIndex(null); - }; - - const selectError = (index: number) => { - setSelectedErrorIndex(index); - const error = errors[index]; - console.log(error, "error"); - if (!error) return; - - const preview = document.getElementById("resume-preview"); - if (preview) { - if (preview && errors.length > 0) { - const marker = new Mark(preview); - errors.forEach((error: GrammarError) => { - marker.mark(error.text, { - className: `grammar-error ${error.type}`, - acrossElements: true, - separateWordSearch: false, - caseSensitive: true, - }); - }); - } - - const errorElements = preview.querySelectorAll( - `.grammar-error[data-markjs=true]` - ); - - errorElements.forEach((el) => { - el.classList.remove("active"); - }); - - errorElements.forEach((el) => { - if (el.textContent === error.text) { - el.classList.add(`active-${highlightKey}`); - - el.scrollIntoView({ - behavior: "smooth", - block: "center", - }); - - setTimeout(() => { - el.classList.remove(`active-${highlightKey}`); - }, 2000); - } - }); - } - }; - return { errors, isChecking, diff --git a/apps/fronted/src/store/useGrammarStore.ts b/apps/fronted/src/store/useGrammarStore.ts new file mode 100644 index 0000000..032406b --- /dev/null +++ b/apps/fronted/src/store/useGrammarStore.ts @@ -0,0 +1,150 @@ +import { create } from "zustand"; +import { toast } from "sonner"; +import Mark from "mark.js"; +import { useAIConfigStore } from "@/store/useAIConfigStore"; + +export interface GrammarError { + error: string; + suggestion: string; + context: string; + type?: string; + text?: string; +} + +interface GrammarStore { + isChecking: boolean; + errors: GrammarError[]; + selectedErrorIndex: number | null; + highlightKey: number; + setErrors: (errors: GrammarError[]) => void; + setIsChecking: (isChecking: boolean) => void; + setSelectedErrorIndex: (index: number | null) => void; + incrementHighlightKey: () => void; + checkGrammar: (text: string) => Promise; + clearErrors: () => void; + selectError: (index: number) => void; +} + +export const useGrammarStore = create((set, get) => ({ + isChecking: false, + errors: [], + selectedErrorIndex: null, + highlightKey: 0, + + setErrors: (errors) => set({ errors }), + setIsChecking: (isChecking) => set({ isChecking }), + setSelectedErrorIndex: (selectedErrorIndex) => set({ selectedErrorIndex }), + incrementHighlightKey: () => + set((state) => ({ highlightKey: state.highlightKey + 1 })), + + checkGrammar: async (text: string) => { + const { doubaoApiKey, doubaoModelId } = useAIConfigStore.getState(); + set({ isChecking: true }); + + try { + const response = await fetch("/api/grammar", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + content: text, + apiKey: doubaoApiKey, + model: doubaoModelId, + }), + }); + + if (!response.ok) { + throw new Error(`API request failed: ${response.status}`); + } + + const data = await response.json(); + + if (data.error?.code === "AuthenticationError") { + toast.error("ApiKey 或 模型Id 不正确"); + throw new Error(data.error.message); + } + + const aiResponse = data.choices[0]?.message?.content; + + try { + const grammarErrors = JSON.parse(aiResponse); + toast.success("语法检查完成"); + set({ errors: grammarErrors }); + + const preview = document.getElementById("resume-preview"); + if (preview) { + const marker = new Mark(preview); + marker.unmark(); + grammarErrors.forEach((error: GrammarError) => { + marker.mark(error.context || error.text || "", { + className: "bg-yellow-200 dark:bg-yellow-900", + }); + }); + } + } catch (parseError) { + toast.error(`解析AI响应失败: ${parseError}`); + set({ errors: [] }); + } + } catch (error) { + set({ errors: [] }); + } finally { + set({ isChecking: false }); + } + }, + + clearErrors: () => { + const preview = document.getElementById("resume-preview"); + if (preview) { + const marker = new Mark(preview); + marker.unmark(); + } + set({ errors: [], selectedErrorIndex: null }); + }, + + selectError: (index: number) => { + const state = get(); + const error = state.errors[index]; + if (!error) return; + + set({ selectedErrorIndex: index }); + + const preview = document.getElementById("resume-preview"); + if (preview) { + const marker = new Mark(preview); + marker.unmark(); + + state.errors.forEach((error) => { + marker.mark(error.context || error.text || "", { + className: `grammar-error ${error.type || ""}`, + acrossElements: true, + separateWordSearch: false, + caseSensitive: true, + }); + }); + + const errorElements = preview.querySelectorAll( + `.grammar-error[data-markjs=true]` + ); + + errorElements.forEach((el) => { + el.classList.remove("active"); + }); + + errorElements.forEach((el) => { + if (el.textContent === (error.context || error.text)) { + el.classList.add(`active-${state.highlightKey}`); + + el.scrollIntoView({ + behavior: "smooth", + block: "center", + }); + + setTimeout(() => { + el.classList.remove(`active-${state.highlightKey}`); + }, 2000); + } + }); + } + }, +}));