refactor: grammar hook and store

This commit is contained in:
JOYCEQL
2024-12-22 16:10:54 +08:00
committed by qingchen
parent 9d6d044f9e
commit 586b2f7251
3 changed files with 154 additions and 149 deletions
@@ -74,7 +74,6 @@ export const PreviewDock = ({
try {
const text = resumeContentRef.current.innerText;
await checkGrammar(text);
// toast.success("语法检查完成");
} catch (error) {
toast.error("语法检查失败,请重试");
}
+4 -148
View File
@@ -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<GrammarState>((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,
+150
View File
@@ -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<void>;
clearErrors: () => void;
selectError: (index: number) => void;
}
export const useGrammarStore = create<GrammarStore>((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);
}
});
}
},
}));