|
|
import styles from "./powerpoint.module.scss";
|
|
|
import chatStyles from "@/app/components/chat.module.scss";
|
|
|
import clsx from "clsx";
|
|
|
|
|
|
import { IconButton } from "../button";
|
|
|
import Locale from "@/app/locales";
|
|
|
import { useNavigate, useLocation } from "react-router-dom";
|
|
|
import { useMobileScreen } from "@/app/utils";
|
|
|
import { Path } from "@/app/constant";
|
|
|
import { useRef, useEffect, useMemo } from "react";
|
|
|
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 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);
|
|
|
const isPowerpoint = location.pathname === Path.Powerpoint;
|
|
|
const isMobileScreen = useMobileScreen();
|
|
|
const navigate = useNavigate();
|
|
|
const clientConfig = useMemo(() => getClientConfig(), []);
|
|
|
const showMaxIcon = !isMobileScreen && !clientConfig?.isApp;
|
|
|
const config = useAppConfig();
|
|
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
|
|
|
|
const query = useLocation();
|
|
|
const { msg, pptMessage } = query.state || {};
|
|
|
|
|
|
const getToken = async () => {
|
|
|
if (!accessStore.accessCode) {
|
|
|
return message.error("请先输入登录秘钥");
|
|
|
}
|
|
|
const res = await fetch("/api/ppt/createApiToken", {
|
|
|
method: "POST",
|
|
|
body: JSON.stringify(accessStore.accessCode),
|
|
|
});
|
|
|
const data = await res.json();
|
|
|
console.log(data);
|
|
|
if (data.status == 200) {
|
|
|
return data.data.data.token;
|
|
|
} else {
|
|
|
message.error(data.error || "获取 token 失败");
|
|
|
}
|
|
|
return "";
|
|
|
};
|
|
|
|
|
|
useEffect(() => {
|
|
|
const initializeDocmee = async () => {
|
|
|
let token = localStorage.getItem("token");
|
|
|
// 如果本地没有token,则获取新token
|
|
|
if (!token) {
|
|
|
token = await getToken();
|
|
|
if (!token) {
|
|
|
message.error("无效token请检查登录密码!");
|
|
|
return navigate(Path.Settings); // 跳转回聊天页
|
|
|
}
|
|
|
localStorage.setItem("token", token);
|
|
|
}
|
|
|
if (!containerRef.current) {
|
|
|
throw new Error("Container element not found");
|
|
|
}
|
|
|
const docmee = new DocmeeUI({
|
|
|
container: containerRef.current,
|
|
|
page: "creator-v2",
|
|
|
token: token,
|
|
|
mode: "light",
|
|
|
lang: "zh",
|
|
|
isMobile: isMobileScreen,
|
|
|
creatorData: {
|
|
|
type: CreatorType.AI_GEN,
|
|
|
subject: "Ai行业未来10年的发展预测",
|
|
|
},
|
|
|
});
|
|
|
if (msg) {
|
|
|
docmee.on("mounted", (msg: generateOutline) => {
|
|
|
docmee.changeCreatorData({ text: pptMessage }, true);
|
|
|
});
|
|
|
}
|
|
|
docmee.on("beforeGenerate", (msg: generateOutline) => {
|
|
|
axios.post("https://docmee.cn/api/ppt/v2/generateContent", msg, {
|
|
|
headers: { token: token },
|
|
|
});
|
|
|
});
|
|
|
docmee.on("error", (msg: generateError) => {
|
|
|
if (msg.data.code == 98) {
|
|
|
docmee.updateToken(token);
|
|
|
}
|
|
|
message.error(msg.data.message);
|
|
|
});
|
|
|
return () => docmee.destroy();
|
|
|
};
|
|
|
initializeDocmee().catch(console.error);
|
|
|
}, [navigate, isMobileScreen, msg, pptMessage]);
|
|
|
|
|
|
return (
|
|
|
<>
|
|
|
<div style={{ width: "100%", height: "100%" }}>
|
|
|
<div className={chatStyles.chat} key={"1"}>
|
|
|
<div className="window-header" data-tauri-drag-region>
|
|
|
<div
|
|
|
className={clsx(
|
|
|
"window-header-title",
|
|
|
chatStyles["chat-body-title"],
|
|
|
)}
|
|
|
>
|
|
|
<div className={`window-header-main-title`}>PPT制作</div>
|
|
|
</div>
|
|
|
<div className={chatStyles["chat-message-actions"]}>
|
|
|
<div className={chatStyles["chat-input-actions"]}></div>
|
|
|
</div>
|
|
|
<div className="window-actions">
|
|
|
<IconButton
|
|
|
aria="返回首页"
|
|
|
icon={<ReturnIcon />}
|
|
|
bordered
|
|
|
title={Locale.Chat.Actions.ChatList}
|
|
|
onClick={() => navigate(Path.Chat)}
|
|
|
/>
|
|
|
{showMaxIcon && (
|
|
|
<div className="window-action-button">
|
|
|
<IconButton
|
|
|
aria={Locale.Chat.Actions.FullScreen}
|
|
|
icon={config.tightBorder ? <MinIcon /> : <MaxIcon />}
|
|
|
bordered
|
|
|
onClick={() => {
|
|
|
config.update(
|
|
|
(config) => (config.tightBorder = !config.tightBorder),
|
|
|
);
|
|
|
}}
|
|
|
/>
|
|
|
</div>
|
|
|
)}
|
|
|
</div>
|
|
|
</div>
|
|
|
<div className={chatStyles["chat-body"]} ref={scrollRef}>
|
|
|
<div ref={containerRef} className={styles["container"]}></div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</>
|
|
|
);
|
|
|
} |
...
|
...
|
|