From 8a4eaa867d5c4f5d88b72efeb9d221ca413cd1ae Mon Sep 17 00:00:00 2001
From: 202304001 <941456317@qq.com>
Date: Fri, 28 Mar 2025 19:26:59 +0800
Subject: [PATCH] 新增生成PPT功能,新增生成图片功能

---
 app/api/[provider]/[...path]/route.ts            |  11 +++++++++--
 app/api/docmee.ts                                |  48 ++++++++++++++++++++++++++++++++++++++++++++++++
 app/api/generateImg.ts                           |  42 ++++++++++++++++++++++++++++++++++++++++++
 app/api/openai.ts                                |   2 --
 app/api/zuotang.ts                               | 229 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------------------------------------------------------------------------------------------
 app/components/bgRemoval/bg-removal-panel.tsx    |  26 ++++++++++++++++++++++++++
 app/components/chat.tsx                          |  15 +++++++++++++++
 app/components/home.tsx                          |  15 +++++++++++++--
 app/components/mind/mind.tsx                     |   4 ++--
 app/components/powerpoint/index.tsx              |   1 +
 app/components/powerpoint/powerpoint.module.scss |   4 ++++
 app/components/powerpoint/powerpoint.tsx         | 168 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 app/components/powerpoint/ppt-siderbar.tsx       |  82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 app/components/settings.tsx                      |   4 ++++
 app/components/sidebar.tsx                       |   3 ++-
 app/config/server.ts                             |  18 +++++++++++++-----
 app/constant.ts                                  |   9 ++++++---
 app/locales/cn.ts                                |  48 ++++++++++++++++++++++++------------------------
 app/types/docmee.d.ts                            |  24 ++++++++++++++++++++++++
 package.json                                     |   1 +
 yarn.lock                                        |  31 +++++++++++++++++++++++++++++++
 21 files changed, 631 insertions(+), 154 deletions(-)
 create mode 100644 app/api/docmee.ts
 create mode 100644 app/api/generateImg.ts
 create mode 100644 app/components/powerpoint/index.tsx
 create mode 100644 app/components/powerpoint/powerpoint.module.scss
 create mode 100644 app/components/powerpoint/powerpoint.tsx
 create mode 100644 app/components/powerpoint/ppt-siderbar.tsx
 create mode 100644 app/types/docmee.d.ts

diff --git a/app/api/[provider]/[...path]/route.ts b/app/api/[provider]/[...path]/route.ts
index 3e14932..32aa190 100644
--- a/app/api/[provider]/[...path]/route.ts
+++ b/app/api/[provider]/[...path]/route.ts
@@ -16,7 +16,10 @@ import { handle as xaiHandler } from "../../xai";
 import { handle as chatglmHandler } from "../../glm";
 import { handle as proxyHandler } from "../../proxy";
 //20250321 新增佐糖API
-import {handle as zuotangHandler} from "../../zuotang";
+import { handle as zuotangHandler } from "../../zuotang";
+//20250328新增PPT API
+import { handle as docmeeHandler } from "../../docmee";
+import { handle as generateImgHandler } from "../../generateImg";
 
 async function handle(
   req: NextRequest,
@@ -55,7 +58,11 @@ async function handle(
     case ApiPath.OpenAI:
       return openaiHandler(req, { params });
     case ApiPath.ZuoTang:
-      return zuotangHandler(req,{ params })
+      return zuotangHandler(req, { params });
+    case ApiPath.Docmee:
+      return docmeeHandler(req, { params });
+    case ApiPath.OpenAiImg:
+      return generateImgHandler(req, { params });
     default:
       return proxyHandler(req, { params });
   }
diff --git a/app/api/docmee.ts b/app/api/docmee.ts
new file mode 100644
index 0000000..3fabe28
--- /dev/null
+++ b/app/api/docmee.ts
@@ -0,0 +1,48 @@
+import { getServerSideConfig } from "@/app/config/server";
+import { NextRequest, NextResponse } from "next/server";
+
+export async function handle(
+  req: NextRequest,
+  { params }: { params: { path: string[] } },
+) {
+  const config = getServerSideConfig();
+  const baseUrl = config.docmeeUrl;
+  const subPath = params.path.join("/");
+  // if(subPath==='createApiToken'){const reqUrl = `${baseUrl}/api/user/${subPath}`;}
+  const reqUrl = `${baseUrl}/api/user/${subPath}`;
+  console.log(reqUrl);
+  try {
+    // 获取 accessCode
+    const body = await new Response(req.body).text();
+    const uid = JSON.parse(body);
+    const headers = new Headers({
+      "Api-Key": config.docmeeApiKey,
+      "Content-Type": "application/json",
+    });
+    console.log("********************" + config.docmeeApiKey);
+    // 使用 async/await 处理 fetch 请求
+    const response = await fetch(reqUrl, {
+      headers,
+      method: "POST",
+      body: JSON.stringify({ uid: uid, limit: config.docmeeMaxDailyUses }),
+    });
+    if (!response.ok) {
+      throw new Error(
+        `HTTP error! status: ${response.status} ${response.text()}`,
+      );
+    }
+    const data = await response.json();
+    console.log("-----------------data", data);
+    // 返回固定的 token
+    return NextResponse.json({
+      data: data,
+      status: 200,
+    });
+  } catch (error) {
+    console.error("处理请求时出错:", error);
+    return NextResponse.json({
+      data: "处理请求时出错",
+      status: 500,
+    });
+  }
+}
diff --git a/app/api/generateImg.ts b/app/api/generateImg.ts
new file mode 100644
index 0000000..0b72425
--- /dev/null
+++ b/app/api/generateImg.ts
@@ -0,0 +1,42 @@
+import { getServerSideConfig } from "@/app/config/server";
+import { NextRequest, NextResponse } from "next/server";
+
+export async function handle(
+  req: NextRequest,
+  { params }: { params: { path: string[] } },
+) {
+  const config = getServerSideConfig();
+  const baseUrl = config.baseUrl;
+  const subPath = params.path.join("/");
+  // if(subPath==='createApiToken'){const reqUrl = `${baseUrl}/api/user/${subPath}`;}
+  const reqUrl = `${baseUrl}/v1/images/${subPath}`;
+  const apiKey = config.apiKey;
+  try {
+    const headers = new Headers({
+      Authorization: `Bearer ${apiKey}`,
+      "Content-Type": "application/json",
+    });
+    const body = await new Response(req.body).text();
+    const prompt = JSON.parse(body);
+    const response = await fetch(reqUrl, {
+      headers,
+      method: "POST",
+      body: JSON.stringify({
+        model: "dall-e-3",
+        prompt: prompt,
+        n: 1,
+        size: "1024x1024",
+      }),
+    });
+    const data = await response.json();
+    return NextResponse.json({
+      data: data,
+      status: 200,
+    });
+  } catch {
+    return NextResponse.json({
+      data: "处理请求时出错",
+      status: 500,
+    });
+  }
+}
diff --git a/app/api/openai.ts b/app/api/openai.ts
index b16b1ce..94bad5a 100644
--- a/app/api/openai.ts
+++ b/app/api/openai.ts
@@ -30,7 +30,6 @@ export async function handle(
   req: NextRequest,
   { params }: { params: { path: string[] } },
 ) {
-  console.log("------------------------------------进入opanai handle")
   console.log("[OpenAI Route] params ", params);
 
   if (req.method === "OPTIONS") {
@@ -61,7 +60,6 @@ export async function handle(
 
   try {
     const response = await requestOpenai(req);
-
     // list models
     if (subpath === OpenaiPath.ListModelPath && response.status === 200) {
       const resJson = (await response.json()) as OpenAIListModelResponse;
diff --git a/app/api/zuotang.ts b/app/api/zuotang.ts
index b10b47b..2a8bb43 100644
--- a/app/api/zuotang.ts
+++ b/app/api/zuotang.ts
@@ -1,135 +1,138 @@
 import { getServerSideConfig } from "@/app/config/server";
-import type { CreateTaskResponse, GetTaskResponse, GetGenerateTaskResponse, LocalData } from "../types/zuotang";
+import type {
+  CreateTaskResponse,
+  GetTaskResponse,
+  GetGenerateTaskResponse,
+  LocalData,
+} from "../types/zuotang";
 import { NextRequest, NextResponse } from "next/server";
-import { error } from "console";
-import { message } from "antd";
 import md5 from "spark-md5";
 
-
 // 类型守卫函数
 function isString(value: unknown): value is string {
-    return typeof value === "string";
+  return typeof value === "string";
 }
 
 // 统一错误响应生成器
 function createErrorResponse(
-    message: string,
-    status: number,
-    maxDailyUses?: number
+  message: string,
+  status: number,
+  maxDailyUses?: number,
 ): NextResponse<CreateTaskResponse> {
-    const response: CreateTaskResponse = {
-        status,
-        message,
-        data: { task_id: "" },
-        maxDailyUses,
-    };
-    return NextResponse.json(response, { status, headers: { "Content-Type": "application/json" } });
+  const response: CreateTaskResponse = {
+    status,
+    message,
+    data: { task_id: "" },
+    maxDailyUses,
+  };
+  return NextResponse.json(response, {
+    status,
+    headers: { "Content-Type": "application/json" },
+  });
 }
 
 // 处理每日使用限制逻辑
 function parseDailyUsage(allowNum: string, configMax: number): number {
-    if (allowNum === "first") return configMax;
-    const parsed = parseInt(allowNum, 10);
-    return Number.isNaN(parsed) ? configMax : parsed;
+  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[] } }
+  req: NextRequest,
+  { params }: { params: { path: string[] } },
 ) {
-    const config = getServerSideConfig();
-    const baseUrl = config.bgRemovalUrl;
-    const subPath = params.path.join("/");
-    const reqUrl = `${baseUrl}/api/tasks/${subPath}`;
-
-    try {
-        if (req.method === "POST") {
-            const formData = await req.formData();
-
-            // 验证访问码
-            const accessCode = formData.get("accessCode");
-            if (!isString(accessCode) || !config.codes.has(md5.hash(accessCode))) {
-                return createErrorResponse("无效访问密码!", 401);
-            }
-
-            // 解析使用限制数据
-            const localData = formData.get("localData");
-            if (!isString(localData)) {
-                return createErrorResponse("无效请求参数", 400);
-            }
-
-            const localDataObj: LocalData = JSON.parse(localData);
-            const maxDailyUses = parseDailyUsage(localDataObj.maxDailyUses, config.maxDailyUses);
-
-            if (maxDailyUses <= 0) {
-                return createErrorResponse("今日次数已用完!", 429, 0);
-            }
-
-            // 准备API请求
-            const imageFile = formData.get("image_file");
-            if (!(imageFile instanceof Blob)) {
-                return createErrorResponse("无效图片文件", 400);
-            }
-
-            const headers = new Headers({ "X-API-KEY": config.bgRemovalApiKey });
-            const newFormData = new FormData();
-            newFormData.append("image_file", imageFile);
-
-            if (subPath === "visual/r-background") {
-                newFormData.append("batch_size", "1");
-                const prompt = formData.get("prompt") as string;
-                const trimmedPrompt = prompt ? prompt.trim() : null;
-                if (!trimmedPrompt) {
-                    return createErrorResponse("背景提示词不能为空!", 400);
-                }
-                newFormData.append("prompt", trimmedPrompt);
-            }
-
-            const response = await fetch(reqUrl, {
-                headers,
-                method: "POST",
-                body: newFormData,
-            });
-
-            if (!response.ok) {
-                throw new Error(`API请求失败: ${response.statusText}`);
-            }
-
-            const responseData: CreateTaskResponse = await response.json();
-            responseData.maxDailyUses = maxDailyUses - 1;
-
-            return NextResponse.json(responseData, {
-                status: response.status,
-                headers: { "Content-Type": "application/json" },
-            });
-
-        } else if (req.method === "GET") {
-            const headers = { "X-API-KEY": config.bgRemovalApiKey };
-            const response = await fetch(reqUrl, { headers });
-
-            if (!response.ok) {
-                throw new Error(`API请求失败: ${response.statusText}`);
-            }
-
-            const isVisualRoute = subPath.includes("visual/r-background");
-            const responseData = isVisualRoute
-                ? (await response.json() as GetTaskResponse)
-                : (await response.json() as GetGenerateTaskResponse);
-
-            return NextResponse.json(responseData, {
-                status: response.status,
-                headers: { "Content-Type": "application/json" },
-            });
+  const config = getServerSideConfig();
+  const baseUrl = config.bgRemovalUrl;
+  const subPath = params.path.join("/");
+  const reqUrl = `${baseUrl}/api/tasks/${subPath}`;
+
+  try {
+    if (req.method === "POST") {
+      const formData = await req.formData();
+
+      // 验证访问码
+      const accessCode = formData.get("accessCode");
+      if (!isString(accessCode) || !config.codes.has(md5.hash(accessCode))) {
+        return createErrorResponse("无效访问密码!", 401);
+      }
+      // 解析使用限制数据
+      const localData = formData.get("localData");
+      if (!isString(localData)) {
+        return createErrorResponse("无效请求参数", 400);
+      }
+      const localDataObj: LocalData = JSON.parse(localData);
+      const maxDailyUses = parseDailyUsage(
+        localDataObj.maxDailyUses,
+        config.maxDailyUses,
+      );
+
+      if (maxDailyUses <= 0) {
+        return createErrorResponse("今日次数已用完!", 429, 0);
+      }
+
+      // 准备API请求
+      const imageFile = formData.get("image_file");
+      if (!(imageFile instanceof Blob)) {
+        return createErrorResponse("无效图片文件", 400);
+      }
+
+      const headers = new Headers({ "X-API-KEY": config.bgRemovalApiKey });
+      const newFormData = new FormData();
+      newFormData.append("image_file", imageFile);
+
+      if (subPath === "visual/r-background") {
+        newFormData.append("batch_size", "1");
+        const prompt = formData.get("prompt") as string;
+        const trimmedPrompt = prompt ? prompt.trim() : null;
+        if (!trimmedPrompt) {
+          return createErrorResponse("背景提示词不能为空!", 400);
         }
-
-        return createErrorResponse("方法不允许", 405);
-
-    } catch (error) {
-        console.error("请求处理错误:", error);
-        return createErrorResponse(
-            error instanceof Error ? error.message : "服务器内部错误",
-            500
-        );
+        newFormData.append("prompt", trimmedPrompt);
+      }
+
+      const response = await fetch(reqUrl, {
+        headers,
+        method: "POST",
+        body: newFormData,
+      });
+
+      if (!response.ok) {
+        throw new Error(`API请求失败: ${response.statusText}`);
+      }
+
+      const responseData: CreateTaskResponse = await response.json();
+      responseData.maxDailyUses = maxDailyUses - 1;
+
+      return NextResponse.json(responseData, {
+        status: response.status,
+        headers: { "Content-Type": "application/json" },
+      });
+    } else if (req.method === "GET") {
+      const headers = { "X-API-KEY": config.bgRemovalApiKey };
+      const response = await fetch(reqUrl, { headers });
+
+      if (!response.ok) {
+        throw new Error(`API请求失败: ${response.statusText}`);
+      }
+
+      const isVisualRoute = subPath.includes("visual/r-background");
+      const responseData = isVisualRoute
+        ? ((await response.json()) as GetTaskResponse)
+        : ((await response.json()) as GetGenerateTaskResponse);
+
+      return NextResponse.json(responseData, {
+        status: response.status,
+        headers: { "Content-Type": "application/json" },
+      });
     }
+
+    return createErrorResponse("方法不允许", 405);
+  } catch (error) {
+    console.error("请求处理错误:", error);
+    return createErrorResponse(
+      error instanceof Error ? error.message : "服务器内部错误",
+      500,
+    );
+  }
 }
diff --git a/app/components/bgRemoval/bg-removal-panel.tsx b/app/components/bgRemoval/bg-removal-panel.tsx
index 8c652b0..db3939e 100644
--- a/app/components/bgRemoval/bg-removal-panel.tsx
+++ b/app/components/bgRemoval/bg-removal-panel.tsx
@@ -278,6 +278,25 @@ export function BgPanel(props: FileProps) {
   const { pollTask } = useTaskPoller();
   const { updateLocalUsage, getLocalData } = useLocalStorage();
 
+  const handleGenerateImg = async () => {
+    if (!prompt.trim()) {
+      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);
+    }
+    const responseData = await res.json();
+    console.log(responseData.data.data[0]);
+    setPreviewUrl(responseData.data.data[0].url);
+    setIsLoading(false);
+  };
+
   const handleApiRequest = async (endpoint: string) => {
     if (!previewUrl) {
       message.error(Locale.BgRemoval.error.selectImg);
@@ -391,6 +410,13 @@ export function BgPanel(props: FileProps) {
       <ControlParamItem title={Locale.BgRemoval.subTitle}>
         <div className={styles["ai-models"]}>
           <IconButton
+            text={Locale.BgRemoval.generateImg}
+            type="primary"
+            shadow
+            onClick={handleGenerateImg}
+            disabled={isLoading}
+          />
+          <IconButton
             text={Locale.BgRemoval.bgRemoveBtn}
             type="primary"
             shadow
diff --git a/app/components/chat.tsx b/app/components/chat.tsx
index 2a33ac4..dfc6e24 100644
--- a/app/components/chat.tsx
+++ b/app/components/chat.tsx
@@ -53,6 +53,7 @@ import HeadphoneIcon from "../icons/headphone.svg";
 import ExcelIcon from "../icons/excel.svg";
 import WordIcon from "../icons/word.svg";
 import MindIcon from "../icons/mind.svg";
+import PptIcon from "../icons/ppt.svg";
 
 import {
   BOT_HELLO,
@@ -1739,6 +1740,11 @@ function _Chat() {
     navigate("/mind", { state: { msg: true } });
   }
 
+  //20250328新增PPT导出
+  function toPowerpoint(pptMessage: string) {
+    navigate("/powerpoint", { state: { pptMessage: pptMessage, msg: true } });
+  }
+
   return (
     <>
       <div className={styles.chat} key={session.id}>
@@ -2019,6 +2025,15 @@ function _Chat() {
                                           />
                                         </>
                                       )}
+                                      <ChatAction
+                                        text={Locale.Chat.Actions.Ppt}
+                                        icon={<PptIcon />}
+                                        onClick={() =>
+                                          toPowerpoint(
+                                            getMessageTextContent(message),
+                                          )
+                                        }
+                                      />
                                       {/* 以上 20250317 新增Word excel导出按钮 */}
                                       {config.ttsConfig.enable && (
                                         <ChatAction
diff --git a/app/components/home.tsx b/app/components/home.tsx
index 7cf09e7..b0cf84d 100644
--- a/app/components/home.tsx
+++ b/app/components/home.tsx
@@ -86,21 +86,29 @@ const McpMarketPage = dynamic(
     loading: () => <Loading noLogo />,
   },
 );
-//以下新增思维导图路由,抠图路由 20250319
+//以下新增思维导图路由20250319
 const Mind = dynamic(async () => (await import("./mind")).MindPage, {
   loading: () => <Loading noLogo />,
 });
 
+//以下新增抠图页面20250319
 const BgRemoval = dynamic(async () => (await import("./bgRemoval")).BgRemoval, {
   loading: () => <Loading noLogo />,
 });
-
+//以下新增写作页面20250325
 const WritingPage = dynamic(
   async () => (await import("./writing")).WritingPage,
   {
     loading: () => <Loading noLogo />,
   },
 );
+//以下新增PPT制作 页面 20250328
+const PowerPoint = dynamic(
+  async () => (await import("./powerpoint")).PowerPoint,
+  {
+    loading: () => <Loading noLogo />,
+  },
+);
 
 export function useSwitchTheme() {
   const config = useAppConfig();
@@ -189,6 +197,7 @@ function Screen() {
   const isMind = location.pathname === Path.Mind;
   const isBgRemoval = location.pathname === Path.BgRemoval;
   const isWrting = location.pathname === Path.Writing;
+  const isPowerpoint = location.pathname === Path.Powerpoint;
 
   const isMobileScreen = useMobileScreen();
   const shouldTightBorder =
@@ -215,6 +224,8 @@ function Screen() {
     if (isBgRemoval) return <BgRemoval />;
     //20250325新增AI写作界面
     if (isWrting) return <WritingPage />;
+    //20250328新增ppt制作页面
+    if (isPowerpoint) return <PowerPoint />;
 
     return (
       <>
diff --git a/app/components/mind/mind.tsx b/app/components/mind/mind.tsx
index 2917530..4f24f8b 100644
--- a/app/components/mind/mind.tsx
+++ b/app/components/mind/mind.tsx
@@ -49,6 +49,8 @@ export function MindPage() {
     // 初始化配置项
     const options: Options = {
       el: containerRef.current,
+      locale: "zh_CN",
+      draggable: true,
       contextMenu: true,
       toolBar: true,
       nodeMenu: true,
@@ -66,7 +68,6 @@ export function MindPage() {
               newMessages,
               "gpt-4o-mini",
             );
-            console.log("原始响应:", response);
             let cleanedContent = response.startsWith("```json")
               ? response.substring(8)
               : response;
@@ -77,7 +78,6 @@ export function MindPage() {
               );
             }
             const parsedData: MindElixirData = JSON.parse(cleanedContent);
-            console.log("解析后响应:", parsedData);
             // 增强校验逻辑
             if (
               !parsedData?.nodeData?.id ||
diff --git a/app/components/powerpoint/index.tsx b/app/components/powerpoint/index.tsx
new file mode 100644
index 0000000..f442cc9
--- /dev/null
+++ b/app/components/powerpoint/index.tsx
@@ -0,0 +1 @@
+export * from "./powerpoint";
diff --git a/app/components/powerpoint/powerpoint.module.scss b/app/components/powerpoint/powerpoint.module.scss
new file mode 100644
index 0000000..69f6619
--- /dev/null
+++ b/app/components/powerpoint/powerpoint.module.scss
@@ -0,0 +1,4 @@
+.container{
+    width: 100%;
+    height: 100%;
+}
\ No newline at end of file
diff --git a/app/components/powerpoint/powerpoint.tsx b/app/components/powerpoint/powerpoint.tsx
new file mode 100644
index 0000000..cf470ef
--- /dev/null
+++ b/app/components/powerpoint/powerpoint.tsx
@@ -0,0 +1,168 @@
+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>
+    </>
+  );
+}
diff --git a/app/components/powerpoint/ppt-siderbar.tsx b/app/components/powerpoint/ppt-siderbar.tsx
new file mode 100644
index 0000000..4853234
--- /dev/null
+++ b/app/components/powerpoint/ppt-siderbar.tsx
@@ -0,0 +1,82 @@
+import { useMobileScreen } from "@/app/utils";
+import dynamic from "next/dynamic";
+import {
+  SideBarContainer,
+  SideBarHeader,
+  useDragSideBar,
+  useHotKey,
+} from "@/app/components/sidebar";
+import { IconButton } from "@/app/components/button";
+import ReturnIcon from "@/app/icons/return.svg";
+import HistoryIcon from "@/app/icons/history.svg";
+import Locale from "@/app/locales";
+import { Path } from "@/app/constant";
+import { useNavigate } from "react-router-dom";
+import SDIcon from "@/app/icons/sd.svg";
+
+const MindPanel = dynamic(
+  async () => (await import("@/app/components/mind")).MindPanel,
+  {
+    loading: () => null,
+  },
+);
+
+export function PptSiderBar(props: { className?: string }) {
+  const isMobileScreen = useMobileScreen();
+  const { onDragStart, shouldNarrow } = useDragSideBar();
+  const navigate = useNavigate();
+  useHotKey();
+  return (
+    <>
+      <SideBarContainer
+        onDragStart={onDragStart}
+        shouldNarrow={shouldNarrow}
+        {...props}
+      >
+        {isMobileScreen ? (
+          <div
+            className="window-header"
+            data-tauri-drag-region
+            style={{
+              paddingLeft: 0,
+              paddingRight: 0,
+            }}
+          >
+            <div className="window-actions">
+              <div className="window-action-button">
+                <IconButton
+                  icon={<ReturnIcon />}
+                  bordered
+                  title={Locale.Sd.Actions.ReturnHome}
+                  onClick={() => navigate(Path.Home)}
+                />
+              </div>
+            </div>
+            <SDIcon width={50} height={50} />
+            <div className="window-actions">
+              <div className="window-action-button">
+                <IconButton
+                  icon={<HistoryIcon />}
+                  bordered
+                  title={Locale.Sd.Actions.History}
+                  onClick={() => navigate(Path.SdNew)}
+                />
+              </div>
+            </div>
+          </div>
+        ) : (
+          <SideBarHeader
+            title={
+              <IconButton
+                icon={<ReturnIcon />}
+                bordered
+                title={Locale.Sd.Actions.ReturnHome}
+                onClick={() => navigate(Path.Home)}
+              />
+            }
+          ></SideBarHeader>
+        )}
+      </SideBarContainer>
+    </>
+  );
+}
diff --git a/app/components/settings.tsx b/app/components/settings.tsx
index 68ebcf0..80414d3 100644
--- a/app/components/settings.tsx
+++ b/app/components/settings.tsx
@@ -685,6 +685,10 @@ export function Settings() {
         type="text"
         placeholder={Locale.Settings.Access.AccessCode.Placeholder}
         onChange={(e) => {
+          console.log("更改密码了");
+          if (localStorage.getItem("token")) {
+            localStorage.removeItem("token");
+          } //20250328新增更改访问密码删除本地储存token
           accessStore.update(
             (access) => (access.accessCode = e.currentTarget.value),
           );
diff --git a/app/components/sidebar.tsx b/app/components/sidebar.tsx
index dc64df8..c5fa35f 100644
--- a/app/components/sidebar.tsx
+++ b/app/components/sidebar.tsx
@@ -38,8 +38,9 @@ const DISCOVERY = [
   { name: "Stable Diffusion", path: Path.Sd },
   { name: Locale.SearchChat.Page.Title, path: Path.SearchChat },
   { name: "智能抠图", path: Path.BgRemoval },
-  { name: "AI-writing", path: Path.Writing },
+  { name: "AI-Writing", path: Path.Writing },
   { name: "思维导图", path: Path.Mind },
+  { name: "AI-PPT", path: Path.Powerpoint },
 ];
 
 const ChatList = dynamic(async () => (await import("./chat-list")).ChatList, {
diff --git a/app/config/server.ts b/app/config/server.ts
index 96c7de4..f0fb3db 100644
--- a/app/config/server.ts
+++ b/app/config/server.ts
@@ -98,11 +98,14 @@ declare global {
       BACKGROUND_REMOVAL_API_KEY: string;
       MAX_DAILY_USES: number;
 
-      NXET_PUBLIC_BGREMOVAL_MODEL : string;
-      NXET_PUBLIC_WRITING_MODEL :string;
+      DOCMEE_URL: string;
+      DOCMEE_API_KEY: string;
+
+      NXET_PUBLIC_BGREMOVAL_MODEL: string;
+      NXET_PUBLIC_WRITING_MODEL: string;
+    }
   }
 }
-}
 
 const ACCESS_CODES = (function getAccessCodes(): Set<string> {
   const code = process.env.CODE;
@@ -124,7 +127,8 @@ function getApiKey(keys?: string) {
   const apiKey = apiKeys[randomIndex];
   if (apiKey) {
     console.log(
-      `[Server Config] using ${randomIndex + 1} of ${apiKeys.length
+      `[Server Config] using ${randomIndex + 1} of ${
+        apiKeys.length
       } api key - ${apiKey}`,
     );
   }
@@ -277,8 +281,12 @@ export const getServerSideConfig = () => {
     bgRemovalUrl: process.env.BACKGROUND_REMOVAL_URL ?? "",
     bgRemovalApiKey: process.env.BACKGROUND_REMOVAL_API_KEY ?? "",
     maxDailyUses: process.env.MAX_DAILY_USES,
+    //20250328新增 ppt api
+    docmeeUrl: process.env.DOCMEE_URL,
+    docmeeApiKey: process.env.DOCMEE_API_KEY ?? "",
+    docmeeMaxDailyUses: process.env.DOCMEE_MAX_DAILY_USES,
 
-    bgRemovalModel : process.env.NXET_PUBLIC_BGREMOVAL_MODEL,
+    bgRemovalModel: process.env.NXET_PUBLIC_BGREMOVAL_MODEL,
     writingModel: process.env.NXET_PUBLIC_WRITING_MODEL,
   };
 };
diff --git a/app/constant.ts b/app/constant.ts
index 3ce6b1f..f1dfe4c 100644
--- a/app/constant.ts
+++ b/app/constant.ts
@@ -54,8 +54,9 @@ export enum Path {
   McpMarket = "/mcp-market",
   //20250317新增路由 思维导图
   Mind = "/mind",
-  BgRemoval="/background-removal",
-  Writing="/aiWriting"
+  BgRemoval = "/background-removal",
+  Writing = "/aiWriting",
+  Powerpoint = "/powerpoint",
 }
 
 export enum ApiPath {
@@ -77,7 +78,9 @@ export enum ApiPath {
   DeepSeek = "/api/deepseek",
   SiliconFlow = "/api/siliconflow",
   //20250321 新增佐糖API
-  ZuoTang="/api/tasks"
+  ZuoTang = "/api/tasks",
+  Docmee = "/api/ppt",
+  OpenAiImg = "/api/v1",
 }
 ///api/tasks/visual/segmentation
 //api/tasks/visual/segmentation/{task_id}
diff --git a/app/locales/cn.ts b/app/locales/cn.ts
index 2389ce0..6a88e68 100644
--- a/app/locales/cn.ts
+++ b/app/locales/cn.ts
@@ -1,4 +1,3 @@
-import { title } from "process";
 import { getClientConfig } from "../config/client";
 import { SubmitKey } from "../store/config";
 import { SAAS_CHAT_UTM_URL } from "@/app/constant";
@@ -62,11 +61,11 @@ const cn = {
       //20250317新增
       Word: "导出Word",
       Excel: "下载Excel",
-      Pdf:"导出PDF",
-      Ppt:"导出PPT",
+      Pdf: "导出PDF",
+      Ppt: "导出PPT",
       Mind: "生成思维导图",
       Drag: "拖动模式",
-      ReWrite:"重写",
+      ReWrite: "重写",
     },
     Commands: {
       new: "新建聊天",
@@ -860,26 +859,27 @@ const cn = {
   },
 
   // 20250320新增
-  BgRemoval:{
-    Title:"智能抠图",
-    subTitle:"AI抠图",
-    error:{
-      reqErr:"请求失败",
-      selectImg:"请选择图片",
-      code:"请先输入访问密码",
-      prompt:"请输入背景提示词",
-      resultErr:"结果图片加载失败",
-      downLoadErr:"请先完成图片处理",
-      statuErr:"状态查询失败",
-      timeoutErr:"处理超时,请稍后重试",
-      imgLoadingErr:"图片加载失败",
-    },
-    success:"图片处理成功,请在一小时内保存图片!",
-    bgRemoveBtn:"一键抠图",
-    downloadImg:"下载图片",
-    generateBg:"生成背景",
-    promptTitle:"背景提示词",
-  }
+  BgRemoval: {
+    Title: "智能抠图",
+    subTitle: "AI抠图",
+    error: {
+      reqErr: "请求失败",
+      selectImg: "请选择图片",
+      code: "请先输入访问密码",
+      prompt: "请输入背景提示词",
+      resultErr: "结果图片加载失败",
+      downLoadErr: "请先完成图片处理",
+      statuErr: "状态查询失败",
+      timeoutErr: "处理超时,请稍后重试",
+      imgLoadingErr: "图片加载失败",
+    },
+    success: "图片处理成功,请在一小时内保存图片!",
+    generateImg: "生成图片",
+    bgRemoveBtn: "一键抠图",
+    downloadImg: "下载图片",
+    generateBg: "生成背景",
+    promptTitle: "背景提示词",
+  },
 };
 
 type DeepPartial<T> = T extends object
diff --git a/app/types/docmee.d.ts b/app/types/docmee.d.ts
new file mode 100644
index 0000000..13c54b2
--- /dev/null
+++ b/app/types/docmee.d.ts
@@ -0,0 +1,24 @@
+export type generateOutline = {
+  data: {
+    fields: {
+      enableWeb: boolean;
+      length: string;
+      prompt: string;
+      subject: string;
+      _lang: string;
+      text?: string;
+      file?: File;
+      website?: string;
+      outline?: File;
+    };
+    subtype: string;
+  };
+  type: string;
+};
+export type generateError = {
+  data: {
+    code: number;
+    message: string;
+  };
+  type: string;
+};
diff --git a/package.json b/package.json
index c737041..5afbb43 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
     "test:ci": "node --no-warnings --experimental-vm-modules $(yarn bin jest) --ci"
   },
   "dependencies": {
+    "@docmee/sdk-ui": "^1.1.17",
     "@fortaine/fetch-event-source": "^3.0.6",
     "@hello-pangea/dnd": "^16.5.0",
     "@modelcontextprotocol/sdk": "^1.0.4",
diff --git a/yarn.lock b/yarn.lock
index 861ba16..f91ca83 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1371,6 +1371,13 @@
   dependencies:
     "@jridgewell/trace-mapping" "0.3.9"
 
+"@docmee/sdk-ui@^1.1.17":
+  version "1.1.17"
+  resolved "https://registry.npmmirror.com/@docmee/sdk-ui/-/sdk-ui-1.1.17.tgz#549ee8b20dfe07eada422e9943b651d5a5196dd0"
+  integrity sha512-K/pWu2tg9ZrE9wbI5Naylh+LVd86kwMG7A9pu5urN1XjV+MAHj7ruOMrmqMkGbN5fVfFN8DbxWx4yUMcCMHTxA==
+  dependencies:
+    query-string "^9.1.1"
+
 "@emotion/hash@^0.8.0":
   version "0.8.0"
   resolved "https://registry.npmmirror.com/@emotion/hash/-/hash-0.8.0.tgz"
@@ -4266,6 +4273,11 @@ decode-named-character-reference@^1.0.0:
   dependencies:
     character-entities "^2.0.0"
 
+decode-uri-component@^0.4.1:
+  version "0.4.1"
+  resolved "https://registry.npmmirror.com/decode-uri-component/-/decode-uri-component-0.4.1.tgz#2ac4859663c704be22bf7db760a1494a49ab2cc5"
+  integrity sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==
+
 dedent@^1.0.0:
   version "1.5.3"
   resolved "https://registry.npmmirror.com/dedent/-/dedent-1.5.3.tgz"
@@ -5136,6 +5148,11 @@ fill-range@^7.0.1:
   dependencies:
     to-regex-range "^5.0.1"
 
+filter-obj@^5.1.0:
+  version "5.1.0"
+  resolved "https://registry.npmmirror.com/filter-obj/-/filter-obj-5.1.0.tgz#5bd89676000a713d7db2e197f660274428e524ed"
+  integrity sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==
+
 find-cache-dir@^3.3.1:
   version "3.3.2"
   resolved "https://registry.npmmirror.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz"
@@ -8035,6 +8052,15 @@ pure-rand@^6.0.0:
   resolved "https://registry.npmmirror.com/pure-rand/-/pure-rand-6.1.0.tgz"
   integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==
 
+query-string@^9.1.1:
+  version "9.1.1"
+  resolved "https://registry.npmmirror.com/query-string/-/query-string-9.1.1.tgz#dbfebb4196aeb2919915f2b2b81b91b965cf03a0"
+  integrity sha512-MWkCOVIcJP9QSKU52Ngow6bsAWAPlPK2MludXvcrS2bGZSl+T1qX9MZvRIkqUIkGLJquMJHWfsT6eRqUpp4aWg==
+  dependencies:
+    decode-uri-component "^0.4.1"
+    filter-obj "^5.1.0"
+    split-on-first "^3.0.0"
+
 querystringify@^2.1.1:
   version "2.2.0"
   resolved "https://registry.npmmirror.com/querystringify/-/querystringify-2.2.0.tgz"
@@ -9068,6 +9094,11 @@ spawn-command@0.0.2:
   resolved "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz"
   integrity sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==
 
+split-on-first@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.npmmirror.com/split-on-first/-/split-on-first-3.0.0.tgz#f04959c9ea8101b9b0bbf35a61b9ebea784a23e7"
+  integrity sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==
+
 sprintf-js@~1.0.2:
   version "1.0.3"
   resolved "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz"
--
libgit2 0.24.0