作者 202304001

代码优化

import { getServerSideConfig } from "@/app/config/server";
import { NextRequest, NextResponse } from "next/server";
// 处理每日使用限制逻辑
function parseDailyUsage(allowNum: string, configMax: number): number {
if (allowNum === "first") return configMax;
const parsed = parseInt(allowNum, 10);
return Number.isNaN(parsed) ? configMax : parsed;
}
export async function handle(
req: NextRequest,
{ params }: { params: { path: string[] } },
... ... @@ -14,7 +21,25 @@ export async function handle(
try {
// 获取 accessCode
const body = await new Response(req.body).text();
const uid = JSON.parse(body);
const parsedBody = JSON.parse(body);
// 提取 accessCode 和 max_use 参数
const uid = parsedBody.accessCode;
let maxUse = parsedBody.max_use;
if (!uid) {
return NextResponse.json({
data: "请前往设置页面输入登录密码",
status: 400,
});
}
if (!maxUse) {
return NextResponse.json({
data: "参数错误",
status: 400,
});
}
maxUse = parseDailyUsage(maxUse, config.docmeeMaxDailyUses);
const headers = new Headers({
"Api-Key": config.docmeeApiKey,
"Content-Type": "application/json",
... ... @@ -24,7 +49,7 @@ export async function handle(
const response = await fetch(reqUrl, {
headers,
method: "POST",
body: JSON.stringify({ uid: uid, limit: config.docmeeMaxDailyUses }),
body: JSON.stringify({ uid: uid, limit: maxUse }),
});
if (!response.ok) {
throw new Error(
... ...
... ... @@ -62,6 +62,9 @@ export async function handle(
return createErrorResponse("无效请求参数", 400);
}
const localDataObj: LocalData = JSON.parse(localData);
if (!localDataObj.maxDailyUses) {
return createErrorResponse("缺少请求参数", 400);
}
const maxDailyUses = parseDailyUsage(
localDataObj.maxDailyUses,
config.maxDailyUses,
... ...
... ... @@ -95,7 +95,7 @@ const useTaskPoller = () => {
};
// 日期处理工具
const useDateUtils = () => {
export const useDateUtils = () => {
const getFormattedToday = (): string => {
const today = new Date();
return today.toISOString().split("T")[0].replace(/-/g, "");
... ... @@ -109,9 +109,8 @@ const useDateUtils = () => {
};
// 本地存储管理
const useLocalStorage = () => {
export const useLocalStorage = () => {
const { getFormattedToday, isToday } = useDateUtils();
const getLocalData = (accessCode: string): LocalData => {
try {
const data = localStorage.getItem(accessCode);
... ... @@ -123,25 +122,45 @@ const useLocalStorage = () => {
return {
date: parsed.date || getFormattedToday(),
maxDailyUses: parsed.maxDailyUses || "first",
pptMaxUses: parsed.pptMsxUses || "first",
};
} catch (e) {
return defaultLocalData();
}
};
const updateLocalUsage = (accessCode: string, maxDailyUses: number) => {
const saveData: LocalData = {
const updateLocalUsage = (
accessCode: string,
maxDailyUses?: number,
pptMaxUses?: number,
) => {
const existingData = getLocalData(accessCode);
// 检查现有数据的日期是否是今天
const isNewDay = !isToday(existingData.date);
// 创建新的数据对象
const newData: LocalData = {
date: getFormattedToday(),
maxDailyUses: maxDailyUses.toString(),
maxDailyUses:
maxDailyUses !== undefined
? maxDailyUses.toString()
: isNewDay
? "first"
: existingData.maxDailyUses,
pptMaxUses:
pptMaxUses !== undefined
? pptMaxUses.toString()
: isNewDay
? "first"
: existingData.pptMaxUses,
};
localStorage.setItem(accessCode, JSON.stringify(saveData));
localStorage.setItem(accessCode, JSON.stringify(newData));
};
const defaultLocalData = (): LocalData => ({
date: getFormattedToday(),
maxDailyUses: "first",
pptMaxUses: "first",
});
return { getLocalData, updateLocalUsage };
};
... ... @@ -283,18 +302,28 @@ export function BgPanel(props: FileProps) {
return message.error("请先输入提示词");
}
setIsLoading(true);
const res = await fetch(`${ApiPath.OpenAiImg}/generations`, {
method: "POST",
body: JSON.stringify(prompt),
});
if (!res.ok) {
const errorData = await res.json();
throw new Error(errorData.message || Locale.BgRemoval.error.reqErr);
try {
const res = await fetch(`${ApiPath.OpenAiImg}/generations`, {
method: "POST",
body: JSON.stringify(prompt),
});
if (!res.ok) {
const errorData = await res.json();
throw new Error(errorData.message || Locale.BgRemoval.error.reqErr);
}
const responseData = await res.json();
// 获取新图片的Blob
const newBlob = await urlToBlob(responseData.data.data[0].url);
// 创建新的对象URL
const newUrl = URL.createObjectURL(newBlob);
// 同步更新所有相关状态
setPreviewUrl(newUrl);
setFileData(newBlob);
} catch (error) {
message.error(Locale.BgRemoval.error.reqErr);
} finally {
setIsLoading(false);
}
const responseData = await res.json();
console.log(responseData.data.data[0]);
setPreviewUrl(responseData.data.data[0].url);
setIsLoading(false);
};
const handleApiRequest = async (endpoint: string) => {
... ... @@ -315,7 +344,7 @@ export function BgPanel(props: FileProps) {
const formData = new FormData();
const localData = getLocalData(accessStore.accessCode); // 获取本地数据
formData.append("accessCode", accessStore.accessCode);
formData.append("localData", JSON.stringify(localData)); // 序列化后添加
formData.append("localData", JSON.stringify(localData));
formData.append("image_file", fileData as Blob);
if (endpoint === "visual/r-background") {
formData.append("prompt", prompt);
... ...
... ... @@ -6,6 +6,7 @@ import { getMindPrompt } from "@/app/utils/prompt";
import { useChatStore } from "@/app/store";
import { type MindElixirData } from "mind-elixir";
import { message } from "antd";
import Locale from "@/app/locales";
export interface MindPanelProps {
setData: React.Dispatch<React.SetStateAction<MindElixirData>>;
... ... @@ -19,31 +20,21 @@ export function MindPanel(props: MindPanelProps) {
const [loading, setLoading] = useState(false);
const chatStore = useChatStore();
const submit = async () => {
if (!inputValue.trim()) return message.error("请输入提示词!");
if (!inputValue.trim()) return message.error(Locale.BgRemoval.error.prompt);
setIsLoading(true);
try {
const prompt = getMindPrompt(inputValue, false);
console.log("请求------------");
const response = await chatStore.directLlmInvoke(prompt, "gpt-4o-mini");
console.log("原始响应:", response);
let cleanedContent = response.startsWith("```json")
? response.substring(8)
: response;
if (cleanedContent.endsWith("```")) {
cleanedContent = cleanedContent.substring(0, cleanedContent.length - 4);
}
const parsedData: MindElixirData = JSON.parse(response);
console.log("解析后响应:", parsedData);
// 增强校验逻辑
if (
!parsedData?.nodeData?.id ||
!Array.isArray(parsedData.nodeData.children)
) {
throw new Error("数据结构不完整");
}
console.log(response);
const cleanedContent = response.replace(/^```json|```$/g, "");
console.log(cleanedContent);
const parsedData: MindElixirData = JSON.parse(cleanedContent);
console.log("-----" + parsedData);
setData(parsedData);
} catch (error) {
console.log(error);
message.error("请求失败,请重试");
message.error(Locale.BgRemoval.error.reqErr);
} finally {
setIsLoading(false); // 确保关闭加载状态‌:ml-citation{ref="2,5" data="citationList"}
}
... ...
... ... @@ -21,6 +21,20 @@ import SDIcon from "@/app/icons/sd.svg";
import { useChatStore, useMindMapStore } from "@/app/store";
import { message } from "antd";
// 常量配置抽取
const INITIAL_DATA: MindElixirData = {
nodeData: {
id: "root",
topic: "中心主题",
},
};
const LOADING_DATA: MindElixirData = {
nodeData: {
id: "root",
topic: "生成中....",
},
};
export function MindPage() {
const isMobileScreen = useMobileScreen();
const navigate = useNavigate();
... ... @@ -35,13 +49,8 @@ export function MindPage() {
const chatStore = useChatStore();
const { newMessages, content } = useMindMapStore.getState();
const query = useLocation();
const { msg } = query.state || {};
const [data, setData] = useState<MindElixirData>({
nodeData: {
id: "root",
topic: "中心主题",
},
});
let { msg } = query.state || {};
const [data, setData] = useState<MindElixirData>(INITIAL_DATA);
useEffect(() => {
// 确保容器元素已挂载
... ... @@ -68,15 +77,7 @@ export function MindPage() {
newMessages,
"gpt-4o-mini",
);
let cleanedContent = response.startsWith("```json")
? response.substring(8)
: response;
if (cleanedContent.endsWith("```")) {
cleanedContent = cleanedContent.substring(
0,
cleanedContent.length - 4,
);
}
const cleanedContent = response.replace(/^```json|```$/g, "");
const parsedData: MindElixirData = JSON.parse(cleanedContent);
// 增强校验逻辑
if (
... ... @@ -86,6 +87,7 @@ export function MindPage() {
throw new Error("数据结构不完整");
}
setData(parsedData);
navigate(Path.Mind, { replace: true, state: { msg: null } });
} catch (error) {
console.log(error);
message.error("请求失败,请重试");
... ... @@ -100,9 +102,6 @@ export function MindPage() {
return () => {
if (mindInstance.current) {
mindInstance.current.destroy();
// 移除事件监听器
document.removeEventListener("mousemove", () => {});
document.removeEventListener("mouseup", () => {});
}
};
}, []);
... ... @@ -113,76 +112,10 @@ export function MindPage() {
useEffect(() => {
if (isLoading) {
mindInstance.current?.refresh({
nodeData: {
id: "root",
topic: "生成中....",
},
});
} else {
mindInstance.current?.refresh({
nodeData: {
id: "root",
topic: "中心主题",
},
});
mindInstance.current?.refresh(LOADING_DATA);
}
}, [isLoading]);
// const meNode=document.querySelector('me-nodes')
// const draggableElement = meNode?.querySelector('me-root') as HTMLElement | null;
// if (draggableElement) {
// let isDragging = false; // 是否正在拖拽
// let startX = 0; // 鼠标按下时的 X 坐标
// let startY = 0; // 鼠标按下时的 Y 坐标
// let initialLeft = 0; // 元素初始的左偏移量
// let initialTop = 0; // 元素初始的上偏移量
// // 鼠标按下事件
// draggableElement.addEventListener('mousedown', (e) => {
// isDragging = true;
// startX = e.clientX;
// startY = e.clientY;
// // 获取 me-root 元素当前的位置
// const draggableStyle = window.getComputedStyle(draggableElement);
// initialLeft = parseInt(draggableStyle.left) || 0;
// initialTop = parseInt(draggableStyle.top) || 0;
// // 确保元素可以移动
// draggableElement.style.position = 'absolute';
// });
// // 鼠标移动事件
// document.addEventListener('mousemove', (e) => {
// if (isDragging) {
// const currentX = e.clientX;
// const currentY = e.clientY;
// // 计算偏移量
// const diffX = currentX - startX;
// const diffY = currentY - startY;
// // 更新 me-root 元素位置
// draggableElement.style.left = `${initialLeft + diffX}px`;
// draggableElement.style.top = `${initialTop + diffY}px`;
// // 检查 meMain 是否存在,如果存在则更新其位置
// const meMain = meNode?.querySelector('me-main') as HTMLElement | null;
// if (meMain) {
// // 获取 meMain 元素当前的位置
// const meMainStyle = window.getComputedStyle(meMain);
// const meMainInitialLeft = parseInt(meMainStyle.left) || 0;
// const meMainInitialTop = parseInt(meMainStyle.top) || 0;
// // 更新 meMain 元素位置
// meMain.style.position = 'absolute';
// meMain.style.left = `${meMainInitialLeft + diffX}px`;
// meMain.style.top = `${meMainInitialTop + diffY}px`;
// }
// }
// });
// // 鼠标释放事件
// document.addEventListener('mouseup', () => {
// isDragging = false;
// });
// } else {
// console.error('未找到 me-root 元素');
// }
return (
<>
<MindSiderBar
... ...
... ... @@ -12,32 +12,15 @@ import { CreatorType, DocmeeUI } from "@docmee/sdk-ui";
import { getClientConfig } from "@/app/config/client";
import { useAppConfig, useAccessStore } from "@/app/store";
import type { generateError, generateOutline } from "@/app/types/docmee";
import type { LocalData } from "@/app/types/zuotang";
import { useLocalStorage } from "../bgRemoval";
import axios from "axios";
import ReturnIcon from "@/app/icons/return.svg";
import MinIcon from "@/app/icons/min.svg";
import MaxIcon from "@/app/icons/max.svg";
import { message } from "antd";
// 错误消息映射
const errorMap: { [key: number]: string } = {
[-1]: "操作失败",
[88]: "功能受限(积分已用完 或 非VIP)",
[98]: "认证失败(检查token是否过期)",
[99]: "登录过期",
[1001]: "数据不存在",
[1002]: "数据访问异常",
[1003]: "无权限访问",
[1006]: "内容涉及敏感信息",
[1009]: "AI服务异常",
[1010]: "你的次数已用完",
[1012]: "请求太频繁,限流",
};
// 获取错误消息的函数
const getErrorMessage = (errorCode: number): string => {
return errorMap[errorCode] || `未知错误(错误码:${errorCode})`;
};
export function PowerPoint() {
const accessStore = useAccessStore();
const containerRef = useRef<HTMLDivElement>(null);
... ... @@ -48,20 +31,23 @@ export function PowerPoint() {
const showMaxIcon = !isMobileScreen && !clientConfig?.isApp;
const config = useAppConfig();
const scrollRef = useRef<HTMLDivElement>(null);
const { updateLocalUsage, getLocalData } = useLocalStorage();
const query = useLocation(); //获取路由参数
const { msg, pptMessage } = query.state || {}; //获取路由参数
const query = useLocation();
const { msg, pptMessage } = query.state || {};
const localData: LocalData = getLocalData(accessStore.accessCode); //获取限制次数
const getToken = async () => {
if (!accessStore.accessCode) {
return message.error("请先输入登录秘钥");
}
const res = await fetch("/api/ppt/createApiToken", {
method: "POST",
body: JSON.stringify(accessStore.accessCode),
body: JSON.stringify({
accessCode: accessStore.accessCode,
max_use: localData.pptMaxUses,
}),
});
const data = await res.json();
console.log(data);
if (data.status == 200) {
return data.data.data.token;
} else {
... ... @@ -89,7 +75,7 @@ export function PowerPoint() {
container: containerRef.current,
page: "creator-v2",
token: token,
mode: "light",
mode: config.theme as "dark" | "light",
lang: "zh",
isMobile: isMobileScreen,
creatorData: {
... ... @@ -99,19 +85,52 @@ export function PowerPoint() {
});
if (msg) {
docmee.on("mounted", (msg: generateOutline) => {
if (localData.pptMaxUses == "0") {
message.error("你的免费次数已用完");
return false;
}
docmee.changeCreatorData({ text: pptMessage }, true);
});
}
docmee.on("beforeGenerate", (msg: generateOutline) => {
//生成大纲事件
console.log(localData.pptMaxUses);
if (localData.pptMaxUses == "0") {
message.error("你的免费次数已用完");
return false;
}
axios.post("https://docmee.cn/api/ppt/v2/generateContent", msg, {
headers: { token: token },
});
});
docmee.on("error", (msg: generateError) => {
docmee.on("charge", (msg: string) => {
//PPT生成后扣费
// 解析 pptMaxUses 为数字
let max_use = parseInt(localData.pptMaxUses, 10);
// 校验解析结果是否为有效数字
if (isNaN(max_use)) {
max_use = 0; // 如果解析失败,设置默认值为 0
}
max_use = max_use - 1;
if (max_use < 0) {
max_use = 0;
}
updateLocalUsage(accessStore.accessCode, undefined, max_use);
});
docmee.on("error", async (msg: generateError) => {
//请求错误事件
console.log(msg);
if (msg.data.code == 98) {
// message.error('token失效,请重试')
const token = await getToken();
docmee.updateToken(token);
return;
}
message.error(msg.data.message);
message.error(
msg.data.code == 403 ? "请检查登录密码或网络" : msg.data.message,
);
});
return () => docmee.destroy();
};
... ...
... ... @@ -685,7 +685,6 @@ export function Settings() {
type="text"
placeholder={Locale.Settings.Access.AccessCode.Placeholder}
onChange={(e) => {
console.log("更改密码了");
if (localStorage.getItem("token")) {
localStorage.removeItem("token");
} //20250328新增更改访问密码删除本地储存token
... ...
... ... @@ -100,6 +100,7 @@ declare global {
DOCMEE_URL: string;
DOCMEE_API_KEY: string;
DOCMEE_MAX_DAILY_USES: number;
NXET_PUBLIC_BGREMOVAL_MODEL: string;
NXET_PUBLIC_WRITING_MODEL: string;
... ...
... ... @@ -866,7 +866,7 @@ const cn = {
reqErr: "请求失败",
selectImg: "请选择图片",
code: "请先输入访问密码",
prompt: "请输入背景提示词",
prompt: "请输入提示词",
resultErr: "结果图片加载失败",
downLoadErr: "请先完成图片处理",
statuErr: "状态查询失败",
... ...
export type FileProps={
fileData: Blob | null;
setFileData: React.Dispatch<React.SetStateAction<Blob | null>>;
previewUrl: string | null;
setPreviewUrl: React.Dispatch<React.SetStateAction<string | null>>;
isLoading:boolean;
setIsLoading: React.Dispatch<React.SetStateAction<boolean>>;
}
export type FileProps = {
fileData: Blob | null;
setFileData: React.Dispatch<React.SetStateAction<Blob | null>>;
previewUrl: string | null;
setPreviewUrl: React.Dispatch<React.SetStateAction<string | null>>;
isLoading: boolean;
setIsLoading: React.Dispatch<React.SetStateAction<boolean>>;
};
export type LocalData={
date:string
maxDailyUses:string
}
export type LocalData = {
date: string;
maxDailyUses: string;
pptMaxUses: string;
};
export type CreateTaskResponse = {
status: number;
message: string;
data: {
task_id: string;
};
maxDailyUses?: number;
};
export type CreateTaskResponse={
status: number;
message: string;
data: {
task_id: string;
};
maxDailyUses?:number;
}
export type GetTaskResponse={
status: number;
message: string;
data: {
err_info: string; //错误信息
image: string; //抠图完成后的图片URL
image_height: number; //抠图完成后的图片高
image_width: number; //抠图完成后的图片宽
output_type: number; //返回图片 1为返回图片和蒙版 2为图片 3为蒙版
result_type: string; //图片前景类型
return_type: number; //返回图片的类型 1为图片URL 2为base64
type: string; //图片前景类型
task_id: string; //任务队列ID
state:number; //任务状态码。1 为成功,大于 1 为处理中,小于 0 为失败。
state_detail: string; //结果为Completed表示转换完成
};
}
export type GetGenerateTaskResponse={
status: number;
message: string;
data: {
/**错误信息 */
err_info: string;
/**抠图完成后的图片URL */
image_1: string;
/**抠图完成后的图片高 */
image_height: number;
/**抠图完成后的图片宽 */
image_width: number;
/**返回图片 1为返回图片和蒙版 2为图片 3为蒙版 */
output_type: number;
/**图片前景类型 */
result_type: string;
/**返回图片的类型 1为图片URL 2为base64 */
return_type: number;
/**图片前景类型 */
type: string;
/**任务队列ID */
task_id: string;
state:number; //任务状态码。1 为成功,大于 1 为处理中,小于 0 为失败。
/**结果为Completed表示转换完成 */
state_detail: string;
};
}
\ No newline at end of file
export type GetTaskResponse = {
status: number;
message: string;
data: {
err_info: string; //错误信息
image: string; //抠图完成后的图片URL
image_height: number; //抠图完成后的图片高
image_width: number; //抠图完成后的图片宽
output_type: number; //返回图片 1为返回图片和蒙版 2为图片 3为蒙版
result_type: string; //图片前景类型
return_type: number; //返回图片的类型 1为图片URL 2为base64
type: string; //图片前景类型
task_id: string; //任务队列ID
state: number; //任务状态码。1 为成功,大于 1 为处理中,小于 0 为失败。
state_detail: string; //结果为Completed表示转换完成
};
};
export type GetGenerateTaskResponse = {
status: number;
message: string;
data: {
/**错误信息 */
err_info: string;
/**抠图完成后的图片URL */
image_1: string;
/**抠图完成后的图片高 */
image_height: number;
/**抠图完成后的图片宽 */
image_width: number;
/**返回图片 1为返回图片和蒙版 2为图片 3为蒙版 */
output_type: number;
/**图片前景类型 */
result_type: string;
/**返回图片的类型 1为图片URL 2为base64 */
return_type: number;
/**图片前景类型 */
type: string;
/**任务队列ID */
task_id: string;
state: number; //任务状态码。1 为成功,大于 1 为处理中,小于 0 为失败。
/**结果为Completed表示转换完成 */
state_detail: string;
};
};
... ...