作者 202304001

新增生成PPT功能,新增生成图片功能

@@ -16,7 +16,10 @@ import { handle as xaiHandler } from "../../xai"; @@ -16,7 +16,10 @@ import { handle as xaiHandler } from "../../xai";
16 import { handle as chatglmHandler } from "../../glm"; 16 import { handle as chatglmHandler } from "../../glm";
17 import { handle as proxyHandler } from "../../proxy"; 17 import { handle as proxyHandler } from "../../proxy";
18 //20250321 新增佐糖API 18 //20250321 新增佐糖API
19 -import {handle as zuotangHandler} from "../../zuotang"; 19 +import { handle as zuotangHandler } from "../../zuotang";
  20 +//20250328新增PPT API
  21 +import { handle as docmeeHandler } from "../../docmee";
  22 +import { handle as generateImgHandler } from "../../generateImg";
20 23
21 async function handle( 24 async function handle(
22 req: NextRequest, 25 req: NextRequest,
@@ -55,7 +58,11 @@ async function handle( @@ -55,7 +58,11 @@ async function handle(
55 case ApiPath.OpenAI: 58 case ApiPath.OpenAI:
56 return openaiHandler(req, { params }); 59 return openaiHandler(req, { params });
57 case ApiPath.ZuoTang: 60 case ApiPath.ZuoTang:
58 - return zuotangHandler(req,{ params }) 61 + return zuotangHandler(req, { params });
  62 + case ApiPath.Docmee:
  63 + return docmeeHandler(req, { params });
  64 + case ApiPath.OpenAiImg:
  65 + return generateImgHandler(req, { params });
59 default: 66 default:
60 return proxyHandler(req, { params }); 67 return proxyHandler(req, { params });
61 } 68 }
  1 +import { getServerSideConfig } from "@/app/config/server";
  2 +import { NextRequest, NextResponse } from "next/server";
  3 +
  4 +export async function handle(
  5 + req: NextRequest,
  6 + { params }: { params: { path: string[] } },
  7 +) {
  8 + const config = getServerSideConfig();
  9 + const baseUrl = config.docmeeUrl;
  10 + const subPath = params.path.join("/");
  11 + // if(subPath==='createApiToken'){const reqUrl = `${baseUrl}/api/user/${subPath}`;}
  12 + const reqUrl = `${baseUrl}/api/user/${subPath}`;
  13 + console.log(reqUrl);
  14 + try {
  15 + // 获取 accessCode
  16 + const body = await new Response(req.body).text();
  17 + const uid = JSON.parse(body);
  18 + const headers = new Headers({
  19 + "Api-Key": config.docmeeApiKey,
  20 + "Content-Type": "application/json",
  21 + });
  22 + console.log("********************" + config.docmeeApiKey);
  23 + // 使用 async/await 处理 fetch 请求
  24 + const response = await fetch(reqUrl, {
  25 + headers,
  26 + method: "POST",
  27 + body: JSON.stringify({ uid: uid, limit: config.docmeeMaxDailyUses }),
  28 + });
  29 + if (!response.ok) {
  30 + throw new Error(
  31 + `HTTP error! status: ${response.status} ${response.text()}`,
  32 + );
  33 + }
  34 + const data = await response.json();
  35 + console.log("-----------------data", data);
  36 + // 返回固定的 token
  37 + return NextResponse.json({
  38 + data: data,
  39 + status: 200,
  40 + });
  41 + } catch (error) {
  42 + console.error("处理请求时出错:", error);
  43 + return NextResponse.json({
  44 + data: "处理请求时出错",
  45 + status: 500,
  46 + });
  47 + }
  48 +}
  1 +import { getServerSideConfig } from "@/app/config/server";
  2 +import { NextRequest, NextResponse } from "next/server";
  3 +
  4 +export async function handle(
  5 + req: NextRequest,
  6 + { params }: { params: { path: string[] } },
  7 +) {
  8 + const config = getServerSideConfig();
  9 + const baseUrl = config.baseUrl;
  10 + const subPath = params.path.join("/");
  11 + // if(subPath==='createApiToken'){const reqUrl = `${baseUrl}/api/user/${subPath}`;}
  12 + const reqUrl = `${baseUrl}/v1/images/${subPath}`;
  13 + const apiKey = config.apiKey;
  14 + try {
  15 + const headers = new Headers({
  16 + Authorization: `Bearer ${apiKey}`,
  17 + "Content-Type": "application/json",
  18 + });
  19 + const body = await new Response(req.body).text();
  20 + const prompt = JSON.parse(body);
  21 + const response = await fetch(reqUrl, {
  22 + headers,
  23 + method: "POST",
  24 + body: JSON.stringify({
  25 + model: "dall-e-3",
  26 + prompt: prompt,
  27 + n: 1,
  28 + size: "1024x1024",
  29 + }),
  30 + });
  31 + const data = await response.json();
  32 + return NextResponse.json({
  33 + data: data,
  34 + status: 200,
  35 + });
  36 + } catch {
  37 + return NextResponse.json({
  38 + data: "处理请求时出错",
  39 + status: 500,
  40 + });
  41 + }
  42 +}
@@ -30,7 +30,6 @@ export async function handle( @@ -30,7 +30,6 @@ export async function handle(
30 req: NextRequest, 30 req: NextRequest,
31 { params }: { params: { path: string[] } }, 31 { params }: { params: { path: string[] } },
32 ) { 32 ) {
33 - console.log("------------------------------------进入opanai handle")  
34 console.log("[OpenAI Route] params ", params); 33 console.log("[OpenAI Route] params ", params);
35 34
36 if (req.method === "OPTIONS") { 35 if (req.method === "OPTIONS") {
@@ -61,7 +60,6 @@ export async function handle( @@ -61,7 +60,6 @@ export async function handle(
61 60
62 try { 61 try {
63 const response = await requestOpenai(req); 62 const response = await requestOpenai(req);
64 -  
65 // list models 63 // list models
66 if (subpath === OpenaiPath.ListModelPath && response.status === 200) { 64 if (subpath === OpenaiPath.ListModelPath && response.status === 200) {
67 const resJson = (await response.json()) as OpenAIListModelResponse; 65 const resJson = (await response.json()) as OpenAIListModelResponse;
1 import { getServerSideConfig } from "@/app/config/server"; 1 import { getServerSideConfig } from "@/app/config/server";
2 -import type { CreateTaskResponse, GetTaskResponse, GetGenerateTaskResponse, LocalData } from "../types/zuotang"; 2 +import type {
  3 + CreateTaskResponse,
  4 + GetTaskResponse,
  5 + GetGenerateTaskResponse,
  6 + LocalData,
  7 +} from "../types/zuotang";
3 import { NextRequest, NextResponse } from "next/server"; 8 import { NextRequest, NextResponse } from "next/server";
4 -import { error } from "console";  
5 -import { message } from "antd";  
6 import md5 from "spark-md5"; 9 import md5 from "spark-md5";
7 10
8 -  
9 // 类型守卫函数 11 // 类型守卫函数
10 function isString(value: unknown): value is string { 12 function isString(value: unknown): value is string {
11 - return typeof value === "string"; 13 + return typeof value === "string";
12 } 14 }
13 15
14 // 统一错误响应生成器 16 // 统一错误响应生成器
15 function createErrorResponse( 17 function createErrorResponse(
16 - message: string,  
17 - status: number,  
18 - maxDailyUses?: number 18 + message: string,
  19 + status: number,
  20 + maxDailyUses?: number,
19 ): NextResponse<CreateTaskResponse> { 21 ): NextResponse<CreateTaskResponse> {
20 - const response: CreateTaskResponse = {  
21 - status,  
22 - message,  
23 - data: { task_id: "" },  
24 - maxDailyUses,  
25 - };  
26 - return NextResponse.json(response, { status, headers: { "Content-Type": "application/json" } }); 22 + const response: CreateTaskResponse = {
  23 + status,
  24 + message,
  25 + data: { task_id: "" },
  26 + maxDailyUses,
  27 + };
  28 + return NextResponse.json(response, {
  29 + status,
  30 + headers: { "Content-Type": "application/json" },
  31 + });
27 } 32 }
28 33
29 // 处理每日使用限制逻辑 34 // 处理每日使用限制逻辑
30 function parseDailyUsage(allowNum: string, configMax: number): number { 35 function parseDailyUsage(allowNum: string, configMax: number): number {
31 - if (allowNum === "first") return configMax;  
32 - const parsed = parseInt(allowNum, 10);  
33 - return Number.isNaN(parsed) ? configMax : parsed; 36 + if (allowNum === "first") return configMax;
  37 + const parsed = parseInt(allowNum, 10);
  38 + return Number.isNaN(parsed) ? configMax : parsed;
34 } 39 }
35 40
36 -  
37 export async function handle( 41 export async function handle(
38 - req: NextRequest,  
39 - { params }: { params: { path: string[] } } 42 + req: NextRequest,
  43 + { params }: { params: { path: string[] } },
40 ) { 44 ) {
41 - const config = getServerSideConfig();  
42 - const baseUrl = config.bgRemovalUrl;  
43 - const subPath = params.path.join("/");  
44 - const reqUrl = `${baseUrl}/api/tasks/${subPath}`;  
45 -  
46 - try {  
47 - if (req.method === "POST") {  
48 - const formData = await req.formData();  
49 -  
50 - // 验证访问码  
51 - const accessCode = formData.get("accessCode");  
52 - if (!isString(accessCode) || !config.codes.has(md5.hash(accessCode))) {  
53 - return createErrorResponse("无效访问密码!", 401);  
54 - }  
55 -  
56 - // 解析使用限制数据  
57 - const localData = formData.get("localData");  
58 - if (!isString(localData)) {  
59 - return createErrorResponse("无效请求参数", 400);  
60 - }  
61 -  
62 - const localDataObj: LocalData = JSON.parse(localData);  
63 - const maxDailyUses = parseDailyUsage(localDataObj.maxDailyUses, config.maxDailyUses);  
64 -  
65 - if (maxDailyUses <= 0) {  
66 - return createErrorResponse("今日次数已用完!", 429, 0);  
67 - }  
68 -  
69 - // 准备API请求  
70 - const imageFile = formData.get("image_file");  
71 - if (!(imageFile instanceof Blob)) {  
72 - return createErrorResponse("无效图片文件", 400);  
73 - }  
74 -  
75 - const headers = new Headers({ "X-API-KEY": config.bgRemovalApiKey });  
76 - const newFormData = new FormData();  
77 - newFormData.append("image_file", imageFile);  
78 -  
79 - if (subPath === "visual/r-background") {  
80 - newFormData.append("batch_size", "1");  
81 - const prompt = formData.get("prompt") as string;  
82 - const trimmedPrompt = prompt ? prompt.trim() : null;  
83 - if (!trimmedPrompt) {  
84 - return createErrorResponse("背景提示词不能为空!", 400);  
85 - }  
86 - newFormData.append("prompt", trimmedPrompt);  
87 - }  
88 -  
89 - const response = await fetch(reqUrl, {  
90 - headers,  
91 - method: "POST",  
92 - body: newFormData,  
93 - });  
94 -  
95 - if (!response.ok) {  
96 - throw new Error(`API请求失败: ${response.statusText}`);  
97 - }  
98 -  
99 - const responseData: CreateTaskResponse = await response.json();  
100 - responseData.maxDailyUses = maxDailyUses - 1;  
101 -  
102 - return NextResponse.json(responseData, {  
103 - status: response.status,  
104 - headers: { "Content-Type": "application/json" },  
105 - });  
106 -  
107 - } else if (req.method === "GET") {  
108 - const headers = { "X-API-KEY": config.bgRemovalApiKey };  
109 - const response = await fetch(reqUrl, { headers });  
110 -  
111 - if (!response.ok) {  
112 - throw new Error(`API请求失败: ${response.statusText}`);  
113 - }  
114 -  
115 - const isVisualRoute = subPath.includes("visual/r-background");  
116 - const responseData = isVisualRoute  
117 - ? (await response.json() as GetTaskResponse)  
118 - : (await response.json() as GetGenerateTaskResponse);  
119 -  
120 - return NextResponse.json(responseData, {  
121 - status: response.status,  
122 - headers: { "Content-Type": "application/json" },  
123 - }); 45 + const config = getServerSideConfig();
  46 + const baseUrl = config.bgRemovalUrl;
  47 + const subPath = params.path.join("/");
  48 + const reqUrl = `${baseUrl}/api/tasks/${subPath}`;
  49 +
  50 + try {
  51 + if (req.method === "POST") {
  52 + const formData = await req.formData();
  53 +
  54 + // 验证访问码
  55 + const accessCode = formData.get("accessCode");
  56 + if (!isString(accessCode) || !config.codes.has(md5.hash(accessCode))) {
  57 + return createErrorResponse("无效访问密码!", 401);
  58 + }
  59 + // 解析使用限制数据
  60 + const localData = formData.get("localData");
  61 + if (!isString(localData)) {
  62 + return createErrorResponse("无效请求参数", 400);
  63 + }
  64 + const localDataObj: LocalData = JSON.parse(localData);
  65 + const maxDailyUses = parseDailyUsage(
  66 + localDataObj.maxDailyUses,
  67 + config.maxDailyUses,
  68 + );
  69 +
  70 + if (maxDailyUses <= 0) {
  71 + return createErrorResponse("今日次数已用完!", 429, 0);
  72 + }
  73 +
  74 + // 准备API请求
  75 + const imageFile = formData.get("image_file");
  76 + if (!(imageFile instanceof Blob)) {
  77 + return createErrorResponse("无效图片文件", 400);
  78 + }
  79 +
  80 + const headers = new Headers({ "X-API-KEY": config.bgRemovalApiKey });
  81 + const newFormData = new FormData();
  82 + newFormData.append("image_file", imageFile);
  83 +
  84 + if (subPath === "visual/r-background") {
  85 + newFormData.append("batch_size", "1");
  86 + const prompt = formData.get("prompt") as string;
  87 + const trimmedPrompt = prompt ? prompt.trim() : null;
  88 + if (!trimmedPrompt) {
  89 + return createErrorResponse("背景提示词不能为空!", 400);
124 } 90 }
125 -  
126 - return createErrorResponse("方法不允许", 405);  
127 -  
128 - } catch (error) {  
129 - console.error("请求处理错误:", error);  
130 - return createErrorResponse(  
131 - error instanceof Error ? error.message : "服务器内部错误",  
132 - 500  
133 - ); 91 + newFormData.append("prompt", trimmedPrompt);
  92 + }
  93 +
  94 + const response = await fetch(reqUrl, {
  95 + headers,
  96 + method: "POST",
  97 + body: newFormData,
  98 + });
  99 +
  100 + if (!response.ok) {
  101 + throw new Error(`API请求失败: ${response.statusText}`);
  102 + }
  103 +
  104 + const responseData: CreateTaskResponse = await response.json();
  105 + responseData.maxDailyUses = maxDailyUses - 1;
  106 +
  107 + return NextResponse.json(responseData, {
  108 + status: response.status,
  109 + headers: { "Content-Type": "application/json" },
  110 + });
  111 + } else if (req.method === "GET") {
  112 + const headers = { "X-API-KEY": config.bgRemovalApiKey };
  113 + const response = await fetch(reqUrl, { headers });
  114 +
  115 + if (!response.ok) {
  116 + throw new Error(`API请求失败: ${response.statusText}`);
  117 + }
  118 +
  119 + const isVisualRoute = subPath.includes("visual/r-background");
  120 + const responseData = isVisualRoute
  121 + ? ((await response.json()) as GetTaskResponse)
  122 + : ((await response.json()) as GetGenerateTaskResponse);
  123 +
  124 + return NextResponse.json(responseData, {
  125 + status: response.status,
  126 + headers: { "Content-Type": "application/json" },
  127 + });
134 } 128 }
  129 +
  130 + return createErrorResponse("方法不允许", 405);
  131 + } catch (error) {
  132 + console.error("请求处理错误:", error);
  133 + return createErrorResponse(
  134 + error instanceof Error ? error.message : "服务器内部错误",
  135 + 500,
  136 + );
  137 + }
135 } 138 }
@@ -278,6 +278,25 @@ export function BgPanel(props: FileProps) { @@ -278,6 +278,25 @@ export function BgPanel(props: FileProps) {
278 const { pollTask } = useTaskPoller(); 278 const { pollTask } = useTaskPoller();
279 const { updateLocalUsage, getLocalData } = useLocalStorage(); 279 const { updateLocalUsage, getLocalData } = useLocalStorage();
280 280
  281 + const handleGenerateImg = async () => {
  282 + if (!prompt.trim()) {
  283 + return message.error("请先输入提示词");
  284 + }
  285 + setIsLoading(true);
  286 + const res = await fetch(`${ApiPath.OpenAiImg}/generations`, {
  287 + method: "POST",
  288 + body: JSON.stringify(prompt),
  289 + });
  290 + if (!res.ok) {
  291 + const errorData = await res.json();
  292 + throw new Error(errorData.message || Locale.BgRemoval.error.reqErr);
  293 + }
  294 + const responseData = await res.json();
  295 + console.log(responseData.data.data[0]);
  296 + setPreviewUrl(responseData.data.data[0].url);
  297 + setIsLoading(false);
  298 + };
  299 +
281 const handleApiRequest = async (endpoint: string) => { 300 const handleApiRequest = async (endpoint: string) => {
282 if (!previewUrl) { 301 if (!previewUrl) {
283 message.error(Locale.BgRemoval.error.selectImg); 302 message.error(Locale.BgRemoval.error.selectImg);
@@ -391,6 +410,13 @@ export function BgPanel(props: FileProps) { @@ -391,6 +410,13 @@ export function BgPanel(props: FileProps) {
391 <ControlParamItem title={Locale.BgRemoval.subTitle}> 410 <ControlParamItem title={Locale.BgRemoval.subTitle}>
392 <div className={styles["ai-models"]}> 411 <div className={styles["ai-models"]}>
393 <IconButton 412 <IconButton
  413 + text={Locale.BgRemoval.generateImg}
  414 + type="primary"
  415 + shadow
  416 + onClick={handleGenerateImg}
  417 + disabled={isLoading}
  418 + />
  419 + <IconButton
394 text={Locale.BgRemoval.bgRemoveBtn} 420 text={Locale.BgRemoval.bgRemoveBtn}
395 type="primary" 421 type="primary"
396 shadow 422 shadow
@@ -53,6 +53,7 @@ import HeadphoneIcon from "../icons/headphone.svg"; @@ -53,6 +53,7 @@ import HeadphoneIcon from "../icons/headphone.svg";
53 import ExcelIcon from "../icons/excel.svg"; 53 import ExcelIcon from "../icons/excel.svg";
54 import WordIcon from "../icons/word.svg"; 54 import WordIcon from "../icons/word.svg";
55 import MindIcon from "../icons/mind.svg"; 55 import MindIcon from "../icons/mind.svg";
  56 +import PptIcon from "../icons/ppt.svg";
56 57
57 import { 58 import {
58 BOT_HELLO, 59 BOT_HELLO,
@@ -1739,6 +1740,11 @@ function _Chat() { @@ -1739,6 +1740,11 @@ function _Chat() {
1739 navigate("/mind", { state: { msg: true } }); 1740 navigate("/mind", { state: { msg: true } });
1740 } 1741 }
1741 1742
  1743 + //20250328新增PPT导出
  1744 + function toPowerpoint(pptMessage: string) {
  1745 + navigate("/powerpoint", { state: { pptMessage: pptMessage, msg: true } });
  1746 + }
  1747 +
1742 return ( 1748 return (
1743 <> 1749 <>
1744 <div className={styles.chat} key={session.id}> 1750 <div className={styles.chat} key={session.id}>
@@ -2019,6 +2025,15 @@ function _Chat() { @@ -2019,6 +2025,15 @@ function _Chat() {
2019 /> 2025 />
2020 </> 2026 </>
2021 )} 2027 )}
  2028 + <ChatAction
  2029 + text={Locale.Chat.Actions.Ppt}
  2030 + icon={<PptIcon />}
  2031 + onClick={() =>
  2032 + toPowerpoint(
  2033 + getMessageTextContent(message),
  2034 + )
  2035 + }
  2036 + />
2022 {/* 以上 20250317 新增Word excel导出按钮 */} 2037 {/* 以上 20250317 新增Word excel导出按钮 */}
2023 {config.ttsConfig.enable && ( 2038 {config.ttsConfig.enable && (
2024 <ChatAction 2039 <ChatAction
@@ -86,21 +86,29 @@ const McpMarketPage = dynamic( @@ -86,21 +86,29 @@ const McpMarketPage = dynamic(
86 loading: () => <Loading noLogo />, 86 loading: () => <Loading noLogo />,
87 }, 87 },
88 ); 88 );
89 -//以下新增思维导图路由,抠图路由 20250319 89 +//以下新增思维导图路由20250319
90 const Mind = dynamic(async () => (await import("./mind")).MindPage, { 90 const Mind = dynamic(async () => (await import("./mind")).MindPage, {
91 loading: () => <Loading noLogo />, 91 loading: () => <Loading noLogo />,
92 }); 92 });
93 93
  94 +//以下新增抠图页面20250319
94 const BgRemoval = dynamic(async () => (await import("./bgRemoval")).BgRemoval, { 95 const BgRemoval = dynamic(async () => (await import("./bgRemoval")).BgRemoval, {
95 loading: () => <Loading noLogo />, 96 loading: () => <Loading noLogo />,
96 }); 97 });
97 - 98 +//以下新增写作页面20250325
98 const WritingPage = dynamic( 99 const WritingPage = dynamic(
99 async () => (await import("./writing")).WritingPage, 100 async () => (await import("./writing")).WritingPage,
100 { 101 {
101 loading: () => <Loading noLogo />, 102 loading: () => <Loading noLogo />,
102 }, 103 },
103 ); 104 );
  105 +//以下新增PPT制作 页面 20250328
  106 +const PowerPoint = dynamic(
  107 + async () => (await import("./powerpoint")).PowerPoint,
  108 + {
  109 + loading: () => <Loading noLogo />,
  110 + },
  111 +);
104 112
105 export function useSwitchTheme() { 113 export function useSwitchTheme() {
106 const config = useAppConfig(); 114 const config = useAppConfig();
@@ -189,6 +197,7 @@ function Screen() { @@ -189,6 +197,7 @@ function Screen() {
189 const isMind = location.pathname === Path.Mind; 197 const isMind = location.pathname === Path.Mind;
190 const isBgRemoval = location.pathname === Path.BgRemoval; 198 const isBgRemoval = location.pathname === Path.BgRemoval;
191 const isWrting = location.pathname === Path.Writing; 199 const isWrting = location.pathname === Path.Writing;
  200 + const isPowerpoint = location.pathname === Path.Powerpoint;
192 201
193 const isMobileScreen = useMobileScreen(); 202 const isMobileScreen = useMobileScreen();
194 const shouldTightBorder = 203 const shouldTightBorder =
@@ -215,6 +224,8 @@ function Screen() { @@ -215,6 +224,8 @@ function Screen() {
215 if (isBgRemoval) return <BgRemoval />; 224 if (isBgRemoval) return <BgRemoval />;
216 //20250325新增AI写作界面 225 //20250325新增AI写作界面
217 if (isWrting) return <WritingPage />; 226 if (isWrting) return <WritingPage />;
  227 + //20250328新增ppt制作页面
  228 + if (isPowerpoint) return <PowerPoint />;
218 229
219 return ( 230 return (
220 <> 231 <>
@@ -49,6 +49,8 @@ export function MindPage() { @@ -49,6 +49,8 @@ export function MindPage() {
49 // 初始化配置项 49 // 初始化配置项
50 const options: Options = { 50 const options: Options = {
51 el: containerRef.current, 51 el: containerRef.current,
  52 + locale: "zh_CN",
  53 + draggable: true,
52 contextMenu: true, 54 contextMenu: true,
53 toolBar: true, 55 toolBar: true,
54 nodeMenu: true, 56 nodeMenu: true,
@@ -66,7 +68,6 @@ export function MindPage() { @@ -66,7 +68,6 @@ export function MindPage() {
66 newMessages, 68 newMessages,
67 "gpt-4o-mini", 69 "gpt-4o-mini",
68 ); 70 );
69 - console.log("原始响应:", response);  
70 let cleanedContent = response.startsWith("```json") 71 let cleanedContent = response.startsWith("```json")
71 ? response.substring(8) 72 ? response.substring(8)
72 : response; 73 : response;
@@ -77,7 +78,6 @@ export function MindPage() { @@ -77,7 +78,6 @@ export function MindPage() {
77 ); 78 );
78 } 79 }
79 const parsedData: MindElixirData = JSON.parse(cleanedContent); 80 const parsedData: MindElixirData = JSON.parse(cleanedContent);
80 - console.log("解析后响应:", parsedData);  
81 // 增强校验逻辑 81 // 增强校验逻辑
82 if ( 82 if (
83 !parsedData?.nodeData?.id || 83 !parsedData?.nodeData?.id ||
  1 +export * from "./powerpoint";
  1 +.container{
  2 + width: 100%;
  3 + height: 100%;
  4 +}
  1 +import styles from "./powerpoint.module.scss";
  2 +import chatStyles from "@/app/components/chat.module.scss";
  3 +import clsx from "clsx";
  4 +
  5 +import { IconButton } from "../button";
  6 +import Locale from "@/app/locales";
  7 +import { useNavigate, useLocation } from "react-router-dom";
  8 +import { useMobileScreen } from "@/app/utils";
  9 +import { Path } from "@/app/constant";
  10 +import { useRef, useEffect, useMemo } from "react";
  11 +import { CreatorType, DocmeeUI } from "@docmee/sdk-ui";
  12 +import { getClientConfig } from "@/app/config/client";
  13 +import { useAppConfig, useAccessStore } from "@/app/store";
  14 +import type { generateError, generateOutline } from "@/app/types/docmee";
  15 +import axios from "axios";
  16 +import ReturnIcon from "@/app/icons/return.svg";
  17 +import MinIcon from "@/app/icons/min.svg";
  18 +import MaxIcon from "@/app/icons/max.svg";
  19 +import { message } from "antd";
  20 +
  21 +// 错误消息映射
  22 +const errorMap: { [key: number]: string } = {
  23 + [-1]: "操作失败",
  24 + [88]: "功能受限(积分已用完 或 非VIP)",
  25 + [98]: "认证失败(检查token是否过期)",
  26 + [99]: "登录过期",
  27 + [1001]: "数据不存在",
  28 + [1002]: "数据访问异常",
  29 + [1003]: "无权限访问",
  30 + [1006]: "内容涉及敏感信息",
  31 + [1009]: "AI服务异常",
  32 + [1010]: "你的次数已用完",
  33 + [1012]: "请求太频繁,限流",
  34 +};
  35 +
  36 +// 获取错误消息的函数
  37 +const getErrorMessage = (errorCode: number): string => {
  38 + return errorMap[errorCode] || `未知错误(错误码:${errorCode})`;
  39 +};
  40 +
  41 +export function PowerPoint() {
  42 + const accessStore = useAccessStore();
  43 + const containerRef = useRef<HTMLDivElement>(null);
  44 + const isPowerpoint = location.pathname === Path.Powerpoint;
  45 + const isMobileScreen = useMobileScreen();
  46 + const navigate = useNavigate();
  47 + const clientConfig = useMemo(() => getClientConfig(), []);
  48 + const showMaxIcon = !isMobileScreen && !clientConfig?.isApp;
  49 + const config = useAppConfig();
  50 + const scrollRef = useRef<HTMLDivElement>(null);
  51 +
  52 + const query = useLocation();
  53 + const { msg, pptMessage } = query.state || {};
  54 +
  55 + const getToken = async () => {
  56 + if (!accessStore.accessCode) {
  57 + return message.error("请先输入登录秘钥");
  58 + }
  59 + const res = await fetch("/api/ppt/createApiToken", {
  60 + method: "POST",
  61 + body: JSON.stringify(accessStore.accessCode),
  62 + });
  63 + const data = await res.json();
  64 + console.log(data);
  65 + if (data.status == 200) {
  66 + return data.data.data.token;
  67 + } else {
  68 + message.error(data.error || "获取 token 失败");
  69 + }
  70 + return "";
  71 + };
  72 +
  73 + useEffect(() => {
  74 + const initializeDocmee = async () => {
  75 + let token = localStorage.getItem("token");
  76 + // 如果本地没有token,则获取新token
  77 + if (!token) {
  78 + token = await getToken();
  79 + if (!token) {
  80 + message.error("无效token请检查登录密码!");
  81 + return navigate(Path.Settings); // 跳转回聊天页
  82 + }
  83 + localStorage.setItem("token", token);
  84 + }
  85 + if (!containerRef.current) {
  86 + throw new Error("Container element not found");
  87 + }
  88 + const docmee = new DocmeeUI({
  89 + container: containerRef.current,
  90 + page: "creator-v2",
  91 + token: token,
  92 + mode: "light",
  93 + lang: "zh",
  94 + isMobile: isMobileScreen,
  95 + creatorData: {
  96 + type: CreatorType.AI_GEN,
  97 + subject: "Ai行业未来10年的发展预测",
  98 + },
  99 + });
  100 + if (msg) {
  101 + docmee.on("mounted", (msg: generateOutline) => {
  102 + docmee.changeCreatorData({ text: pptMessage }, true);
  103 + });
  104 + }
  105 + docmee.on("beforeGenerate", (msg: generateOutline) => {
  106 + axios.post("https://docmee.cn/api/ppt/v2/generateContent", msg, {
  107 + headers: { token: token },
  108 + });
  109 + });
  110 + docmee.on("error", (msg: generateError) => {
  111 + if (msg.data.code == 98) {
  112 + docmee.updateToken(token);
  113 + }
  114 + message.error(msg.data.message);
  115 + });
  116 + return () => docmee.destroy();
  117 + };
  118 + initializeDocmee().catch(console.error);
  119 + }, [navigate, isMobileScreen, msg, pptMessage]);
  120 +
  121 + return (
  122 + <>
  123 + <div style={{ width: "100%", height: "100%" }}>
  124 + <div className={chatStyles.chat} key={"1"}>
  125 + <div className="window-header" data-tauri-drag-region>
  126 + <div
  127 + className={clsx(
  128 + "window-header-title",
  129 + chatStyles["chat-body-title"],
  130 + )}
  131 + >
  132 + <div className={`window-header-main-title`}>PPT制作</div>
  133 + </div>
  134 + <div className={chatStyles["chat-message-actions"]}>
  135 + <div className={chatStyles["chat-input-actions"]}></div>
  136 + </div>
  137 + <div className="window-actions">
  138 + <IconButton
  139 + aria="返回首页"
  140 + icon={<ReturnIcon />}
  141 + bordered
  142 + title={Locale.Chat.Actions.ChatList}
  143 + onClick={() => navigate(Path.Chat)}
  144 + />
  145 + {showMaxIcon && (
  146 + <div className="window-action-button">
  147 + <IconButton
  148 + aria={Locale.Chat.Actions.FullScreen}
  149 + icon={config.tightBorder ? <MinIcon /> : <MaxIcon />}
  150 + bordered
  151 + onClick={() => {
  152 + config.update(
  153 + (config) => (config.tightBorder = !config.tightBorder),
  154 + );
  155 + }}
  156 + />
  157 + </div>
  158 + )}
  159 + </div>
  160 + </div>
  161 + <div className={chatStyles["chat-body"]} ref={scrollRef}>
  162 + <div ref={containerRef} className={styles["container"]}></div>
  163 + </div>
  164 + </div>
  165 + </div>
  166 + </>
  167 + );
  168 +}
  1 +import { useMobileScreen } from "@/app/utils";
  2 +import dynamic from "next/dynamic";
  3 +import {
  4 + SideBarContainer,
  5 + SideBarHeader,
  6 + useDragSideBar,
  7 + useHotKey,
  8 +} from "@/app/components/sidebar";
  9 +import { IconButton } from "@/app/components/button";
  10 +import ReturnIcon from "@/app/icons/return.svg";
  11 +import HistoryIcon from "@/app/icons/history.svg";
  12 +import Locale from "@/app/locales";
  13 +import { Path } from "@/app/constant";
  14 +import { useNavigate } from "react-router-dom";
  15 +import SDIcon from "@/app/icons/sd.svg";
  16 +
  17 +const MindPanel = dynamic(
  18 + async () => (await import("@/app/components/mind")).MindPanel,
  19 + {
  20 + loading: () => null,
  21 + },
  22 +);
  23 +
  24 +export function PptSiderBar(props: { className?: string }) {
  25 + const isMobileScreen = useMobileScreen();
  26 + const { onDragStart, shouldNarrow } = useDragSideBar();
  27 + const navigate = useNavigate();
  28 + useHotKey();
  29 + return (
  30 + <>
  31 + <SideBarContainer
  32 + onDragStart={onDragStart}
  33 + shouldNarrow={shouldNarrow}
  34 + {...props}
  35 + >
  36 + {isMobileScreen ? (
  37 + <div
  38 + className="window-header"
  39 + data-tauri-drag-region
  40 + style={{
  41 + paddingLeft: 0,
  42 + paddingRight: 0,
  43 + }}
  44 + >
  45 + <div className="window-actions">
  46 + <div className="window-action-button">
  47 + <IconButton
  48 + icon={<ReturnIcon />}
  49 + bordered
  50 + title={Locale.Sd.Actions.ReturnHome}
  51 + onClick={() => navigate(Path.Home)}
  52 + />
  53 + </div>
  54 + </div>
  55 + <SDIcon width={50} height={50} />
  56 + <div className="window-actions">
  57 + <div className="window-action-button">
  58 + <IconButton
  59 + icon={<HistoryIcon />}
  60 + bordered
  61 + title={Locale.Sd.Actions.History}
  62 + onClick={() => navigate(Path.SdNew)}
  63 + />
  64 + </div>
  65 + </div>
  66 + </div>
  67 + ) : (
  68 + <SideBarHeader
  69 + title={
  70 + <IconButton
  71 + icon={<ReturnIcon />}
  72 + bordered
  73 + title={Locale.Sd.Actions.ReturnHome}
  74 + onClick={() => navigate(Path.Home)}
  75 + />
  76 + }
  77 + ></SideBarHeader>
  78 + )}
  79 + </SideBarContainer>
  80 + </>
  81 + );
  82 +}
@@ -685,6 +685,10 @@ export function Settings() { @@ -685,6 +685,10 @@ export function Settings() {
685 type="text" 685 type="text"
686 placeholder={Locale.Settings.Access.AccessCode.Placeholder} 686 placeholder={Locale.Settings.Access.AccessCode.Placeholder}
687 onChange={(e) => { 687 onChange={(e) => {
  688 + console.log("更改密码了");
  689 + if (localStorage.getItem("token")) {
  690 + localStorage.removeItem("token");
  691 + } //20250328新增更改访问密码删除本地储存token
688 accessStore.update( 692 accessStore.update(
689 (access) => (access.accessCode = e.currentTarget.value), 693 (access) => (access.accessCode = e.currentTarget.value),
690 ); 694 );
@@ -38,8 +38,9 @@ const DISCOVERY = [ @@ -38,8 +38,9 @@ const DISCOVERY = [
38 { name: "Stable Diffusion", path: Path.Sd }, 38 { name: "Stable Diffusion", path: Path.Sd },
39 { name: Locale.SearchChat.Page.Title, path: Path.SearchChat }, 39 { name: Locale.SearchChat.Page.Title, path: Path.SearchChat },
40 { name: "智能抠图", path: Path.BgRemoval }, 40 { name: "智能抠图", path: Path.BgRemoval },
41 - { name: "AI-writing", path: Path.Writing }, 41 + { name: "AI-Writing", path: Path.Writing },
42 { name: "思维导图", path: Path.Mind }, 42 { name: "思维导图", path: Path.Mind },
  43 + { name: "AI-PPT", path: Path.Powerpoint },
43 ]; 44 ];
44 45
45 const ChatList = dynamic(async () => (await import("./chat-list")).ChatList, { 46 const ChatList = dynamic(async () => (await import("./chat-list")).ChatList, {
@@ -98,11 +98,14 @@ declare global { @@ -98,11 +98,14 @@ declare global {
98 BACKGROUND_REMOVAL_API_KEY: string; 98 BACKGROUND_REMOVAL_API_KEY: string;
99 MAX_DAILY_USES: number; 99 MAX_DAILY_USES: number;
100 100
101 - NXET_PUBLIC_BGREMOVAL_MODEL : string;  
102 - NXET_PUBLIC_WRITING_MODEL :string; 101 + DOCMEE_URL: string;
  102 + DOCMEE_API_KEY: string;
  103 +
  104 + NXET_PUBLIC_BGREMOVAL_MODEL: string;
  105 + NXET_PUBLIC_WRITING_MODEL: string;
  106 + }
103 } 107 }
104 } 108 }
105 -}  
106 109
107 const ACCESS_CODES = (function getAccessCodes(): Set<string> { 110 const ACCESS_CODES = (function getAccessCodes(): Set<string> {
108 const code = process.env.CODE; 111 const code = process.env.CODE;
@@ -124,7 +127,8 @@ function getApiKey(keys?: string) { @@ -124,7 +127,8 @@ function getApiKey(keys?: string) {
124 const apiKey = apiKeys[randomIndex]; 127 const apiKey = apiKeys[randomIndex];
125 if (apiKey) { 128 if (apiKey) {
126 console.log( 129 console.log(
127 - `[Server Config] using ${randomIndex + 1} of ${apiKeys.length 130 + `[Server Config] using ${randomIndex + 1} of ${
  131 + apiKeys.length
128 } api key - ${apiKey}`, 132 } api key - ${apiKey}`,
129 ); 133 );
130 } 134 }
@@ -277,8 +281,12 @@ export const getServerSideConfig = () => { @@ -277,8 +281,12 @@ export const getServerSideConfig = () => {
277 bgRemovalUrl: process.env.BACKGROUND_REMOVAL_URL ?? "", 281 bgRemovalUrl: process.env.BACKGROUND_REMOVAL_URL ?? "",
278 bgRemovalApiKey: process.env.BACKGROUND_REMOVAL_API_KEY ?? "", 282 bgRemovalApiKey: process.env.BACKGROUND_REMOVAL_API_KEY ?? "",
279 maxDailyUses: process.env.MAX_DAILY_USES, 283 maxDailyUses: process.env.MAX_DAILY_USES,
  284 + //20250328新增 ppt api
  285 + docmeeUrl: process.env.DOCMEE_URL,
  286 + docmeeApiKey: process.env.DOCMEE_API_KEY ?? "",
  287 + docmeeMaxDailyUses: process.env.DOCMEE_MAX_DAILY_USES,
280 288
281 - bgRemovalModel : process.env.NXET_PUBLIC_BGREMOVAL_MODEL, 289 + bgRemovalModel: process.env.NXET_PUBLIC_BGREMOVAL_MODEL,
282 writingModel: process.env.NXET_PUBLIC_WRITING_MODEL, 290 writingModel: process.env.NXET_PUBLIC_WRITING_MODEL,
283 }; 291 };
284 }; 292 };
@@ -54,8 +54,9 @@ export enum Path { @@ -54,8 +54,9 @@ export enum Path {
54 McpMarket = "/mcp-market", 54 McpMarket = "/mcp-market",
55 //20250317新增路由 思维导图 55 //20250317新增路由 思维导图
56 Mind = "/mind", 56 Mind = "/mind",
57 - BgRemoval="/background-removal",  
58 - Writing="/aiWriting" 57 + BgRemoval = "/background-removal",
  58 + Writing = "/aiWriting",
  59 + Powerpoint = "/powerpoint",
59 } 60 }
60 61
61 export enum ApiPath { 62 export enum ApiPath {
@@ -77,7 +78,9 @@ export enum ApiPath { @@ -77,7 +78,9 @@ export enum ApiPath {
77 DeepSeek = "/api/deepseek", 78 DeepSeek = "/api/deepseek",
78 SiliconFlow = "/api/siliconflow", 79 SiliconFlow = "/api/siliconflow",
79 //20250321 新增佐糖API 80 //20250321 新增佐糖API
80 - ZuoTang="/api/tasks" 81 + ZuoTang = "/api/tasks",
  82 + Docmee = "/api/ppt",
  83 + OpenAiImg = "/api/v1",
81 } 84 }
82 ///api/tasks/visual/segmentation 85 ///api/tasks/visual/segmentation
83 //api/tasks/visual/segmentation/{task_id} 86 //api/tasks/visual/segmentation/{task_id}
1 -import { title } from "process";  
2 import { getClientConfig } from "../config/client"; 1 import { getClientConfig } from "../config/client";
3 import { SubmitKey } from "../store/config"; 2 import { SubmitKey } from "../store/config";
4 import { SAAS_CHAT_UTM_URL } from "@/app/constant"; 3 import { SAAS_CHAT_UTM_URL } from "@/app/constant";
@@ -62,11 +61,11 @@ const cn = { @@ -62,11 +61,11 @@ const cn = {
62 //20250317新增 61 //20250317新增
63 Word: "导出Word", 62 Word: "导出Word",
64 Excel: "下载Excel", 63 Excel: "下载Excel",
65 - Pdf:"导出PDF",  
66 - Ppt:"导出PPT", 64 + Pdf: "导出PDF",
  65 + Ppt: "导出PPT",
67 Mind: "生成思维导图", 66 Mind: "生成思维导图",
68 Drag: "拖动模式", 67 Drag: "拖动模式",
69 - ReWrite:"重写", 68 + ReWrite: "重写",
70 }, 69 },
71 Commands: { 70 Commands: {
72 new: "新建聊天", 71 new: "新建聊天",
@@ -860,26 +859,27 @@ const cn = { @@ -860,26 +859,27 @@ const cn = {
860 }, 859 },
861 860
862 // 20250320新增 861 // 20250320新增
863 - BgRemoval:{  
864 - Title:"智能抠图",  
865 - subTitle:"AI抠图",  
866 - error:{  
867 - reqErr:"请求失败",  
868 - selectImg:"请选择图片",  
869 - code:"请先输入访问密码",  
870 - prompt:"请输入背景提示词",  
871 - resultErr:"结果图片加载失败",  
872 - downLoadErr:"请先完成图片处理",  
873 - statuErr:"状态查询失败",  
874 - timeoutErr:"处理超时,请稍后重试",  
875 - imgLoadingErr:"图片加载失败",  
876 - },  
877 - success:"图片处理成功,请在一小时内保存图片!",  
878 - bgRemoveBtn:"一键抠图",  
879 - downloadImg:"下载图片",  
880 - generateBg:"生成背景",  
881 - promptTitle:"背景提示词",  
882 - } 862 + BgRemoval: {
  863 + Title: "智能抠图",
  864 + subTitle: "AI抠图",
  865 + error: {
  866 + reqErr: "请求失败",
  867 + selectImg: "请选择图片",
  868 + code: "请先输入访问密码",
  869 + prompt: "请输入背景提示词",
  870 + resultErr: "结果图片加载失败",
  871 + downLoadErr: "请先完成图片处理",
  872 + statuErr: "状态查询失败",
  873 + timeoutErr: "处理超时,请稍后重试",
  874 + imgLoadingErr: "图片加载失败",
  875 + },
  876 + success: "图片处理成功,请在一小时内保存图片!",
  877 + generateImg: "生成图片",
  878 + bgRemoveBtn: "一键抠图",
  879 + downloadImg: "下载图片",
  880 + generateBg: "生成背景",
  881 + promptTitle: "背景提示词",
  882 + },
883 }; 883 };
884 884
885 type DeepPartial<T> = T extends object 885 type DeepPartial<T> = T extends object
  1 +export type generateOutline = {
  2 + data: {
  3 + fields: {
  4 + enableWeb: boolean;
  5 + length: string;
  6 + prompt: string;
  7 + subject: string;
  8 + _lang: string;
  9 + text?: string;
  10 + file?: File;
  11 + website?: string;
  12 + outline?: File;
  13 + };
  14 + subtype: string;
  15 + };
  16 + type: string;
  17 +};
  18 +export type generateError = {
  19 + data: {
  20 + code: number;
  21 + message: string;
  22 + };
  23 + type: string;
  24 +};
@@ -21,6 +21,7 @@ @@ -21,6 +21,7 @@
21 "test:ci": "node --no-warnings --experimental-vm-modules $(yarn bin jest) --ci" 21 "test:ci": "node --no-warnings --experimental-vm-modules $(yarn bin jest) --ci"
22 }, 22 },
23 "dependencies": { 23 "dependencies": {
  24 + "@docmee/sdk-ui": "^1.1.17",
24 "@fortaine/fetch-event-source": "^3.0.6", 25 "@fortaine/fetch-event-source": "^3.0.6",
25 "@hello-pangea/dnd": "^16.5.0", 26 "@hello-pangea/dnd": "^16.5.0",
26 "@modelcontextprotocol/sdk": "^1.0.4", 27 "@modelcontextprotocol/sdk": "^1.0.4",
@@ -1371,6 +1371,13 @@ @@ -1371,6 +1371,13 @@
1371 dependencies: 1371 dependencies:
1372 "@jridgewell/trace-mapping" "0.3.9" 1372 "@jridgewell/trace-mapping" "0.3.9"
1373 1373
  1374 +"@docmee/sdk-ui@^1.1.17":
  1375 + version "1.1.17"
  1376 + resolved "https://registry.npmmirror.com/@docmee/sdk-ui/-/sdk-ui-1.1.17.tgz#549ee8b20dfe07eada422e9943b651d5a5196dd0"
  1377 + integrity sha512-K/pWu2tg9ZrE9wbI5Naylh+LVd86kwMG7A9pu5urN1XjV+MAHj7ruOMrmqMkGbN5fVfFN8DbxWx4yUMcCMHTxA==
  1378 + dependencies:
  1379 + query-string "^9.1.1"
  1380 +
1374 "@emotion/hash@^0.8.0": 1381 "@emotion/hash@^0.8.0":
1375 version "0.8.0" 1382 version "0.8.0"
1376 resolved "https://registry.npmmirror.com/@emotion/hash/-/hash-0.8.0.tgz" 1383 resolved "https://registry.npmmirror.com/@emotion/hash/-/hash-0.8.0.tgz"
@@ -4266,6 +4273,11 @@ decode-named-character-reference@^1.0.0: @@ -4266,6 +4273,11 @@ decode-named-character-reference@^1.0.0:
4266 dependencies: 4273 dependencies:
4267 character-entities "^2.0.0" 4274 character-entities "^2.0.0"
4268 4275
  4276 +decode-uri-component@^0.4.1:
  4277 + version "0.4.1"
  4278 + resolved "https://registry.npmmirror.com/decode-uri-component/-/decode-uri-component-0.4.1.tgz#2ac4859663c704be22bf7db760a1494a49ab2cc5"
  4279 + integrity sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==
  4280 +
4269 dedent@^1.0.0: 4281 dedent@^1.0.0:
4270 version "1.5.3" 4282 version "1.5.3"
4271 resolved "https://registry.npmmirror.com/dedent/-/dedent-1.5.3.tgz" 4283 resolved "https://registry.npmmirror.com/dedent/-/dedent-1.5.3.tgz"
@@ -5136,6 +5148,11 @@ fill-range@^7.0.1: @@ -5136,6 +5148,11 @@ fill-range@^7.0.1:
5136 dependencies: 5148 dependencies:
5137 to-regex-range "^5.0.1" 5149 to-regex-range "^5.0.1"
5138 5150
  5151 +filter-obj@^5.1.0:
  5152 + version "5.1.0"
  5153 + resolved "https://registry.npmmirror.com/filter-obj/-/filter-obj-5.1.0.tgz#5bd89676000a713d7db2e197f660274428e524ed"
  5154 + integrity sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==
  5155 +
5139 find-cache-dir@^3.3.1: 5156 find-cache-dir@^3.3.1:
5140 version "3.3.2" 5157 version "3.3.2"
5141 resolved "https://registry.npmmirror.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz" 5158 resolved "https://registry.npmmirror.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz"
@@ -8035,6 +8052,15 @@ pure-rand@^6.0.0: @@ -8035,6 +8052,15 @@ pure-rand@^6.0.0:
8035 resolved "https://registry.npmmirror.com/pure-rand/-/pure-rand-6.1.0.tgz" 8052 resolved "https://registry.npmmirror.com/pure-rand/-/pure-rand-6.1.0.tgz"
8036 integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== 8053 integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==
8037 8054
  8055 +query-string@^9.1.1:
  8056 + version "9.1.1"
  8057 + resolved "https://registry.npmmirror.com/query-string/-/query-string-9.1.1.tgz#dbfebb4196aeb2919915f2b2b81b91b965cf03a0"
  8058 + integrity sha512-MWkCOVIcJP9QSKU52Ngow6bsAWAPlPK2MludXvcrS2bGZSl+T1qX9MZvRIkqUIkGLJquMJHWfsT6eRqUpp4aWg==
  8059 + dependencies:
  8060 + decode-uri-component "^0.4.1"
  8061 + filter-obj "^5.1.0"
  8062 + split-on-first "^3.0.0"
  8063 +
8038 querystringify@^2.1.1: 8064 querystringify@^2.1.1:
8039 version "2.2.0" 8065 version "2.2.0"
8040 resolved "https://registry.npmmirror.com/querystringify/-/querystringify-2.2.0.tgz" 8066 resolved "https://registry.npmmirror.com/querystringify/-/querystringify-2.2.0.tgz"
@@ -9068,6 +9094,11 @@ spawn-command@0.0.2: @@ -9068,6 +9094,11 @@ spawn-command@0.0.2:
9068 resolved "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz" 9094 resolved "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz"
9069 integrity sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ== 9095 integrity sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==
9070 9096
  9097 +split-on-first@^3.0.0:
  9098 + version "3.0.0"
  9099 + resolved "https://registry.npmmirror.com/split-on-first/-/split-on-first-3.0.0.tgz#f04959c9ea8101b9b0bbf35a61b9ebea784a23e7"
  9100 + integrity sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==
  9101 +
9071 sprintf-js@~1.0.2: 9102 sprintf-js@~1.0.2:
9072 version "1.0.3" 9103 version "1.0.3"
9073 resolved "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz" 9104 resolved "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz"