mirror of
				https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
				synced 2025-11-04 16:23:41 +08:00 
			
		
		
		
	Compare commits
	
		
			6 Commits
		
	
	
		
			dependabot
			...
			d3904f2166
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					d3904f2166 | ||
| 
						 | 
					ebf84a3b2c | ||
| 
						 | 
					fd796521d4 | ||
| 
						 | 
					6a25bdcf2d | ||
| 
						 | 
					a899e9aceb | ||
| 
						 | 
					c0826d2275 | 
@@ -81,3 +81,9 @@ SILICONFLOW_API_KEY=
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
### siliconflow Api url (optional)
 | 
					### siliconflow Api url (optional)
 | 
				
			||||||
SILICONFLOW_URL=
 | 
					SILICONFLOW_URL=
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### ppio Api key (optional)
 | 
				
			||||||
 | 
					PPIO_API_KEY=
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### ppio Api url (optional)
 | 
				
			||||||
 | 
					PPIO_URL=
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -361,6 +361,14 @@ SiliconFlow API Key.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
SiliconFlow API URL.
 | 
					SiliconFlow API URL.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### `PPIO_API_KEY` (optional)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PPIO API Key.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### `PPIO_URL` (optional)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PPIO API URL.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Requirements
 | 
					## Requirements
 | 
				
			||||||
 | 
					
 | 
				
			||||||
NodeJS >= 18, Docker >= 20
 | 
					NodeJS >= 18, Docker >= 20
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -275,6 +275,14 @@ SiliconFlow API Key.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
SiliconFlow API URL.
 | 
					SiliconFlow API URL.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### `PPIO_API_KEY` (optional)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PPIO API Key.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### `PPIO_URL` (optional)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PPIO API URL.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 开发
 | 
					## 开发
 | 
				
			||||||
 | 
					
 | 
				
			||||||
点击下方按钮,开始二次开发:
 | 
					点击下方按钮,开始二次开发:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,6 +12,7 @@ import { handle as stabilityHandler } from "../../stability";
 | 
				
			|||||||
import { handle as iflytekHandler } from "../../iflytek";
 | 
					import { handle as iflytekHandler } from "../../iflytek";
 | 
				
			||||||
import { handle as deepseekHandler } from "../../deepseek";
 | 
					import { handle as deepseekHandler } from "../../deepseek";
 | 
				
			||||||
import { handle as siliconflowHandler } from "../../siliconflow";
 | 
					import { handle as siliconflowHandler } from "../../siliconflow";
 | 
				
			||||||
 | 
					import { handle as ppioHandler } from "../../ppio";
 | 
				
			||||||
import { handle as xaiHandler } from "../../xai";
 | 
					import { handle as xaiHandler } from "../../xai";
 | 
				
			||||||
import { handle as chatglmHandler } from "../../glm";
 | 
					import { handle as chatglmHandler } from "../../glm";
 | 
				
			||||||
import { handle as proxyHandler } from "../../proxy";
 | 
					import { handle as proxyHandler } from "../../proxy";
 | 
				
			||||||
@@ -50,6 +51,8 @@ async function handle(
 | 
				
			|||||||
      return chatglmHandler(req, { params });
 | 
					      return chatglmHandler(req, { params });
 | 
				
			||||||
    case ApiPath.SiliconFlow:
 | 
					    case ApiPath.SiliconFlow:
 | 
				
			||||||
      return siliconflowHandler(req, { params });
 | 
					      return siliconflowHandler(req, { params });
 | 
				
			||||||
 | 
					    case ApiPath.PPIO:
 | 
				
			||||||
 | 
					      return ppioHandler(req, { params });
 | 
				
			||||||
    case ApiPath.OpenAI:
 | 
					    case ApiPath.OpenAI:
 | 
				
			||||||
      return openaiHandler(req, { params });
 | 
					      return openaiHandler(req, { params });
 | 
				
			||||||
    default:
 | 
					    default:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -104,6 +104,9 @@ export function auth(req: NextRequest, modelProvider: ModelProvider) {
 | 
				
			|||||||
      case ModelProvider.SiliconFlow:
 | 
					      case ModelProvider.SiliconFlow:
 | 
				
			||||||
        systemApiKey = serverConfig.siliconFlowApiKey;
 | 
					        systemApiKey = serverConfig.siliconFlowApiKey;
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
 | 
					      case ModelProvider.PPIO:
 | 
				
			||||||
 | 
					        systemApiKey = serverConfig.ppioApiKey;
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
      case ModelProvider.GPT:
 | 
					      case ModelProvider.GPT:
 | 
				
			||||||
      default:
 | 
					      default:
 | 
				
			||||||
        if (req.nextUrl.pathname.includes("azure/deployments")) {
 | 
					        if (req.nextUrl.pathname.includes("azure/deployments")) {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										128
									
								
								app/api/ppio.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								app/api/ppio.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,128 @@
 | 
				
			|||||||
 | 
					import { getServerSideConfig } from "@/app/config/server";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  PPIO_BASE_URL,
 | 
				
			||||||
 | 
					  ApiPath,
 | 
				
			||||||
 | 
					  ModelProvider,
 | 
				
			||||||
 | 
					  ServiceProvider,
 | 
				
			||||||
 | 
					} from "@/app/constant";
 | 
				
			||||||
 | 
					import { prettyObject } from "@/app/utils/format";
 | 
				
			||||||
 | 
					import { NextRequest, NextResponse } from "next/server";
 | 
				
			||||||
 | 
					import { auth } from "@/app/api/auth";
 | 
				
			||||||
 | 
					import { isModelNotavailableInServer } from "@/app/utils/model";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const serverConfig = getServerSideConfig();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function handle(
 | 
				
			||||||
 | 
					  req: NextRequest,
 | 
				
			||||||
 | 
					  { params }: { params: { path: string[] } },
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					  console.log("[PPIO Route] params ", params);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (req.method === "OPTIONS") {
 | 
				
			||||||
 | 
					    return NextResponse.json({ body: "OK" }, { status: 200 });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const authResult = auth(req, ModelProvider.PPIO);
 | 
				
			||||||
 | 
					  if (authResult.error) {
 | 
				
			||||||
 | 
					    return NextResponse.json(authResult, {
 | 
				
			||||||
 | 
					      status: 401,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    const response = await request(req);
 | 
				
			||||||
 | 
					    return response;
 | 
				
			||||||
 | 
					  } catch (e) {
 | 
				
			||||||
 | 
					    console.error("[PPIO] ", e);
 | 
				
			||||||
 | 
					    return NextResponse.json(prettyObject(e));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function request(req: NextRequest) {
 | 
				
			||||||
 | 
					  const controller = new AbortController();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // alibaba use base url or just remove the path
 | 
				
			||||||
 | 
					  let path = `${req.nextUrl.pathname}`.replaceAll(ApiPath.PPIO, "");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let baseUrl = serverConfig.ppioUrl || PPIO_BASE_URL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!baseUrl.startsWith("http")) {
 | 
				
			||||||
 | 
					    baseUrl = `https://${baseUrl}`;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (baseUrl.endsWith("/")) {
 | 
				
			||||||
 | 
					    baseUrl = baseUrl.slice(0, -1);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  console.log("[Proxy] ", path);
 | 
				
			||||||
 | 
					  console.log("[Base Url]", baseUrl);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const timeoutId = setTimeout(
 | 
				
			||||||
 | 
					    () => {
 | 
				
			||||||
 | 
					      controller.abort();
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    10 * 60 * 1000,
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const fetchUrl = `${baseUrl}${path}`;
 | 
				
			||||||
 | 
					  const fetchOptions: RequestInit = {
 | 
				
			||||||
 | 
					    headers: {
 | 
				
			||||||
 | 
					      "Content-Type": "application/json",
 | 
				
			||||||
 | 
					      Authorization: req.headers.get("Authorization") ?? "",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    method: req.method,
 | 
				
			||||||
 | 
					    body: req.body,
 | 
				
			||||||
 | 
					    redirect: "manual",
 | 
				
			||||||
 | 
					    // @ts-ignore
 | 
				
			||||||
 | 
					    duplex: "half",
 | 
				
			||||||
 | 
					    signal: controller.signal,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // #1815 try to refuse some request to some models
 | 
				
			||||||
 | 
					  if (serverConfig.customModels && req.body) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const clonedBody = await req.text();
 | 
				
			||||||
 | 
					      fetchOptions.body = clonedBody;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const jsonBody = JSON.parse(clonedBody) as { model?: string };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // not undefined and is false
 | 
				
			||||||
 | 
					      if (
 | 
				
			||||||
 | 
					        isModelNotavailableInServer(
 | 
				
			||||||
 | 
					          serverConfig.customModels,
 | 
				
			||||||
 | 
					          jsonBody?.model as string,
 | 
				
			||||||
 | 
					          ServiceProvider.PPIO as string,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					      ) {
 | 
				
			||||||
 | 
					        return NextResponse.json(
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            error: true,
 | 
				
			||||||
 | 
					            message: `you are not allowed to use ${jsonBody?.model} model`,
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            status: 403,
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      console.error(`[PPIO] filter`, e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    const res = await fetch(fetchUrl, fetchOptions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // to prevent browser prompt for credentials
 | 
				
			||||||
 | 
					    const newHeaders = new Headers(res.headers);
 | 
				
			||||||
 | 
					    newHeaders.delete("www-authenticate");
 | 
				
			||||||
 | 
					    // to disable nginx buffering
 | 
				
			||||||
 | 
					    newHeaders.set("X-Accel-Buffering", "no");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return new Response(res.body, {
 | 
				
			||||||
 | 
					      status: res.status,
 | 
				
			||||||
 | 
					      statusText: res.statusText,
 | 
				
			||||||
 | 
					      headers: newHeaders,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  } finally {
 | 
				
			||||||
 | 
					    clearTimeout(timeoutId);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -24,6 +24,7 @@ import { DeepSeekApi } from "./platforms/deepseek";
 | 
				
			|||||||
import { XAIApi } from "./platforms/xai";
 | 
					import { XAIApi } from "./platforms/xai";
 | 
				
			||||||
import { ChatGLMApi } from "./platforms/glm";
 | 
					import { ChatGLMApi } from "./platforms/glm";
 | 
				
			||||||
import { SiliconflowApi } from "./platforms/siliconflow";
 | 
					import { SiliconflowApi } from "./platforms/siliconflow";
 | 
				
			||||||
 | 
					import { PPIOApi } from "./platforms/ppio";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const ROLES = ["system", "user", "assistant"] as const;
 | 
					export const ROLES = ["system", "user", "assistant"] as const;
 | 
				
			||||||
export type MessageRole = (typeof ROLES)[number];
 | 
					export type MessageRole = (typeof ROLES)[number];
 | 
				
			||||||
@@ -173,6 +174,9 @@ export class ClientApi {
 | 
				
			|||||||
      case ModelProvider.SiliconFlow:
 | 
					      case ModelProvider.SiliconFlow:
 | 
				
			||||||
        this.llm = new SiliconflowApi();
 | 
					        this.llm = new SiliconflowApi();
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
 | 
					      case ModelProvider.PPIO:
 | 
				
			||||||
 | 
					        this.llm = new PPIOApi();
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
      default:
 | 
					      default:
 | 
				
			||||||
        this.llm = new ChatGPTApi();
 | 
					        this.llm = new ChatGPTApi();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -265,6 +269,7 @@ export function getHeaders(ignoreHeaders: boolean = false) {
 | 
				
			|||||||
    const isChatGLM = modelConfig.providerName === ServiceProvider.ChatGLM;
 | 
					    const isChatGLM = modelConfig.providerName === ServiceProvider.ChatGLM;
 | 
				
			||||||
    const isSiliconFlow =
 | 
					    const isSiliconFlow =
 | 
				
			||||||
      modelConfig.providerName === ServiceProvider.SiliconFlow;
 | 
					      modelConfig.providerName === ServiceProvider.SiliconFlow;
 | 
				
			||||||
 | 
					    const isPPIO = modelConfig.providerName === ServiceProvider.PPIO;
 | 
				
			||||||
    const isEnabledAccessControl = accessStore.enabledAccessControl();
 | 
					    const isEnabledAccessControl = accessStore.enabledAccessControl();
 | 
				
			||||||
    const apiKey = isGoogle
 | 
					    const apiKey = isGoogle
 | 
				
			||||||
      ? accessStore.googleApiKey
 | 
					      ? accessStore.googleApiKey
 | 
				
			||||||
@@ -286,6 +291,8 @@ export function getHeaders(ignoreHeaders: boolean = false) {
 | 
				
			|||||||
      ? accessStore.chatglmApiKey
 | 
					      ? accessStore.chatglmApiKey
 | 
				
			||||||
      : isSiliconFlow
 | 
					      : isSiliconFlow
 | 
				
			||||||
      ? accessStore.siliconflowApiKey
 | 
					      ? accessStore.siliconflowApiKey
 | 
				
			||||||
 | 
					      : isPPIO
 | 
				
			||||||
 | 
					      ? accessStore.ppioApiKey
 | 
				
			||||||
      : isIflytek
 | 
					      : isIflytek
 | 
				
			||||||
      ? accessStore.iflytekApiKey && accessStore.iflytekApiSecret
 | 
					      ? accessStore.iflytekApiKey && accessStore.iflytekApiSecret
 | 
				
			||||||
        ? accessStore.iflytekApiKey + ":" + accessStore.iflytekApiSecret
 | 
					        ? accessStore.iflytekApiKey + ":" + accessStore.iflytekApiSecret
 | 
				
			||||||
@@ -304,6 +311,7 @@ export function getHeaders(ignoreHeaders: boolean = false) {
 | 
				
			|||||||
      isXAI,
 | 
					      isXAI,
 | 
				
			||||||
      isChatGLM,
 | 
					      isChatGLM,
 | 
				
			||||||
      isSiliconFlow,
 | 
					      isSiliconFlow,
 | 
				
			||||||
 | 
					      isPPIO,
 | 
				
			||||||
      apiKey,
 | 
					      apiKey,
 | 
				
			||||||
      isEnabledAccessControl,
 | 
					      isEnabledAccessControl,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
@@ -332,6 +340,7 @@ export function getHeaders(ignoreHeaders: boolean = false) {
 | 
				
			|||||||
    isXAI,
 | 
					    isXAI,
 | 
				
			||||||
    isChatGLM,
 | 
					    isChatGLM,
 | 
				
			||||||
    isSiliconFlow,
 | 
					    isSiliconFlow,
 | 
				
			||||||
 | 
					    isPPIO,
 | 
				
			||||||
    apiKey,
 | 
					    apiKey,
 | 
				
			||||||
    isEnabledAccessControl,
 | 
					    isEnabledAccessControl,
 | 
				
			||||||
  } = getConfig();
 | 
					  } = getConfig();
 | 
				
			||||||
@@ -382,6 +391,8 @@ export function getClientApi(provider: ServiceProvider): ClientApi {
 | 
				
			|||||||
      return new ClientApi(ModelProvider.ChatGLM);
 | 
					      return new ClientApi(ModelProvider.ChatGLM);
 | 
				
			||||||
    case ServiceProvider.SiliconFlow:
 | 
					    case ServiceProvider.SiliconFlow:
 | 
				
			||||||
      return new ClientApi(ModelProvider.SiliconFlow);
 | 
					      return new ClientApi(ModelProvider.SiliconFlow);
 | 
				
			||||||
 | 
					    case ServiceProvider.PPIO:
 | 
				
			||||||
 | 
					      return new ClientApi(ModelProvider.PPIO);
 | 
				
			||||||
    default:
 | 
					    default:
 | 
				
			||||||
      return new ClientApi(ModelProvider.GPT);
 | 
					      return new ClientApi(ModelProvider.GPT);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										279
									
								
								app/client/platforms/ppio.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										279
									
								
								app/client/platforms/ppio.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,279 @@
 | 
				
			|||||||
 | 
					"use client";
 | 
				
			||||||
 | 
					// azure and openai, using same models. so using same LLMApi.
 | 
				
			||||||
 | 
					import { ApiPath, PPIO_BASE_URL, PPIO, DEFAULT_MODELS } from "@/app/constant";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  useAccessStore,
 | 
				
			||||||
 | 
					  useAppConfig,
 | 
				
			||||||
 | 
					  useChatStore,
 | 
				
			||||||
 | 
					  ChatMessageTool,
 | 
				
			||||||
 | 
					  usePluginStore,
 | 
				
			||||||
 | 
					} from "@/app/store";
 | 
				
			||||||
 | 
					import { preProcessImageContent, streamWithThink } from "@/app/utils/chat";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  ChatOptions,
 | 
				
			||||||
 | 
					  getHeaders,
 | 
				
			||||||
 | 
					  LLMApi,
 | 
				
			||||||
 | 
					  LLMModel,
 | 
				
			||||||
 | 
					  SpeechOptions,
 | 
				
			||||||
 | 
					} from "../api";
 | 
				
			||||||
 | 
					import { getClientConfig } from "@/app/config/client";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  getMessageTextContent,
 | 
				
			||||||
 | 
					  getMessageTextContentWithoutThinking,
 | 
				
			||||||
 | 
					  isVisionModel,
 | 
				
			||||||
 | 
					  getTimeoutMSByModel,
 | 
				
			||||||
 | 
					} from "@/app/utils";
 | 
				
			||||||
 | 
					import { RequestPayload } from "./openai";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { fetch } from "@/app/utils/stream";
 | 
				
			||||||
 | 
					export interface PPIOListModelResponse {
 | 
				
			||||||
 | 
					  object: string;
 | 
				
			||||||
 | 
					  data: Array<{
 | 
				
			||||||
 | 
					    id: string;
 | 
				
			||||||
 | 
					    object: string;
 | 
				
			||||||
 | 
					    root: string;
 | 
				
			||||||
 | 
					  }>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class PPIOApi implements LLMApi {
 | 
				
			||||||
 | 
					  private disableListModels = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  path(path: string): string {
 | 
				
			||||||
 | 
					    const accessStore = useAccessStore.getState();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let baseUrl = "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (accessStore.useCustomConfig) {
 | 
				
			||||||
 | 
					      baseUrl = accessStore.ppioUrl;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (baseUrl.length === 0) {
 | 
				
			||||||
 | 
					      const isApp = !!getClientConfig()?.isApp;
 | 
				
			||||||
 | 
					      const apiPath = ApiPath.PPIO;
 | 
				
			||||||
 | 
					      baseUrl = isApp ? PPIO_BASE_URL : apiPath;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (baseUrl.endsWith("/")) {
 | 
				
			||||||
 | 
					      baseUrl = baseUrl.slice(0, baseUrl.length - 1);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (!baseUrl.startsWith("http") && !baseUrl.startsWith(ApiPath.PPIO)) {
 | 
				
			||||||
 | 
					      baseUrl = "https://" + baseUrl;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    console.log("[Proxy Endpoint] ", baseUrl, path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return [baseUrl, path].join("/");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  extractMessage(res: any) {
 | 
				
			||||||
 | 
					    return res.choices?.at(0)?.message?.content ?? "";
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  speech(options: SpeechOptions): Promise<ArrayBuffer> {
 | 
				
			||||||
 | 
					    throw new Error("Method not implemented.");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async chat(options: ChatOptions) {
 | 
				
			||||||
 | 
					    const visionModel = isVisionModel(options.config.model);
 | 
				
			||||||
 | 
					    const messages: ChatOptions["messages"] = [];
 | 
				
			||||||
 | 
					    for (const v of options.messages) {
 | 
				
			||||||
 | 
					      if (v.role === "assistant") {
 | 
				
			||||||
 | 
					        const content = getMessageTextContentWithoutThinking(v);
 | 
				
			||||||
 | 
					        messages.push({ role: v.role, content });
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        const content = visionModel
 | 
				
			||||||
 | 
					          ? await preProcessImageContent(v.content)
 | 
				
			||||||
 | 
					          : getMessageTextContent(v);
 | 
				
			||||||
 | 
					        messages.push({ role: v.role, content });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const modelConfig = {
 | 
				
			||||||
 | 
					      ...useAppConfig.getState().modelConfig,
 | 
				
			||||||
 | 
					      ...useChatStore.getState().currentSession().mask.modelConfig,
 | 
				
			||||||
 | 
					      ...{
 | 
				
			||||||
 | 
					        model: options.config.model,
 | 
				
			||||||
 | 
					        providerName: options.config.providerName,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const requestPayload: RequestPayload = {
 | 
				
			||||||
 | 
					      messages,
 | 
				
			||||||
 | 
					      stream: options.config.stream,
 | 
				
			||||||
 | 
					      model: modelConfig.model,
 | 
				
			||||||
 | 
					      temperature: modelConfig.temperature,
 | 
				
			||||||
 | 
					      presence_penalty: modelConfig.presence_penalty,
 | 
				
			||||||
 | 
					      frequency_penalty: modelConfig.frequency_penalty,
 | 
				
			||||||
 | 
					      top_p: modelConfig.top_p,
 | 
				
			||||||
 | 
					      // max_tokens: Math.max(modelConfig.max_tokens, 1024),
 | 
				
			||||||
 | 
					      // Please do not ask me why not send max_tokens, no reason, this param is just shit, I dont want to explain anymore.
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    console.log("[Request] openai payload: ", requestPayload);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const shouldStream = !!options.config.stream;
 | 
				
			||||||
 | 
					    const controller = new AbortController();
 | 
				
			||||||
 | 
					    options.onController?.(controller);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const chatPath = this.path(PPIO.ChatPath);
 | 
				
			||||||
 | 
					      const chatPayload = {
 | 
				
			||||||
 | 
					        method: "POST",
 | 
				
			||||||
 | 
					        body: JSON.stringify(requestPayload),
 | 
				
			||||||
 | 
					        signal: controller.signal,
 | 
				
			||||||
 | 
					        headers: getHeaders(),
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // console.log(chatPayload);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Use extended timeout for thinking models as they typically require more processing time
 | 
				
			||||||
 | 
					      const requestTimeoutId = setTimeout(
 | 
				
			||||||
 | 
					        () => controller.abort(),
 | 
				
			||||||
 | 
					        getTimeoutMSByModel(options.config.model),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (shouldStream) {
 | 
				
			||||||
 | 
					        const [tools, funcs] = usePluginStore
 | 
				
			||||||
 | 
					          .getState()
 | 
				
			||||||
 | 
					          .getAsTools(
 | 
				
			||||||
 | 
					            useChatStore.getState().currentSession().mask?.plugin || [],
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					        return streamWithThink(
 | 
				
			||||||
 | 
					          chatPath,
 | 
				
			||||||
 | 
					          requestPayload,
 | 
				
			||||||
 | 
					          getHeaders(),
 | 
				
			||||||
 | 
					          tools as any,
 | 
				
			||||||
 | 
					          funcs,
 | 
				
			||||||
 | 
					          controller,
 | 
				
			||||||
 | 
					          // parseSSE
 | 
				
			||||||
 | 
					          (text: string, runTools: ChatMessageTool[]) => {
 | 
				
			||||||
 | 
					            // console.log("parseSSE", text, runTools);
 | 
				
			||||||
 | 
					            const json = JSON.parse(text);
 | 
				
			||||||
 | 
					            const choices = json.choices as Array<{
 | 
				
			||||||
 | 
					              delta: {
 | 
				
			||||||
 | 
					                content: string | null;
 | 
				
			||||||
 | 
					                tool_calls: ChatMessageTool[];
 | 
				
			||||||
 | 
					                reasoning_content: string | null;
 | 
				
			||||||
 | 
					              };
 | 
				
			||||||
 | 
					            }>;
 | 
				
			||||||
 | 
					            const tool_calls = choices[0]?.delta?.tool_calls;
 | 
				
			||||||
 | 
					            if (tool_calls?.length > 0) {
 | 
				
			||||||
 | 
					              const index = tool_calls[0]?.index;
 | 
				
			||||||
 | 
					              const id = tool_calls[0]?.id;
 | 
				
			||||||
 | 
					              const args = tool_calls[0]?.function?.arguments;
 | 
				
			||||||
 | 
					              if (id) {
 | 
				
			||||||
 | 
					                runTools.push({
 | 
				
			||||||
 | 
					                  id,
 | 
				
			||||||
 | 
					                  type: tool_calls[0]?.type,
 | 
				
			||||||
 | 
					                  function: {
 | 
				
			||||||
 | 
					                    name: tool_calls[0]?.function?.name as string,
 | 
				
			||||||
 | 
					                    arguments: args,
 | 
				
			||||||
 | 
					                  },
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					              } else {
 | 
				
			||||||
 | 
					                // @ts-ignore
 | 
				
			||||||
 | 
					                runTools[index]["function"]["arguments"] += args;
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            const reasoning = choices[0]?.delta?.reasoning_content;
 | 
				
			||||||
 | 
					            const content = choices[0]?.delta?.content;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Skip if both content and reasoning_content are empty or null
 | 
				
			||||||
 | 
					            if (
 | 
				
			||||||
 | 
					              (!reasoning || reasoning.length === 0) &&
 | 
				
			||||||
 | 
					              (!content || content.length === 0)
 | 
				
			||||||
 | 
					            ) {
 | 
				
			||||||
 | 
					              return {
 | 
				
			||||||
 | 
					                isThinking: false,
 | 
				
			||||||
 | 
					                content: "",
 | 
				
			||||||
 | 
					              };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (reasoning && reasoning.length > 0) {
 | 
				
			||||||
 | 
					              return {
 | 
				
			||||||
 | 
					                isThinking: true,
 | 
				
			||||||
 | 
					                content: reasoning,
 | 
				
			||||||
 | 
					              };
 | 
				
			||||||
 | 
					            } else if (content && content.length > 0) {
 | 
				
			||||||
 | 
					              return {
 | 
				
			||||||
 | 
					                isThinking: false,
 | 
				
			||||||
 | 
					                content: content,
 | 
				
			||||||
 | 
					              };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return {
 | 
				
			||||||
 | 
					              isThinking: false,
 | 
				
			||||||
 | 
					              content: "",
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          // processToolMessage, include tool_calls message and tool call results
 | 
				
			||||||
 | 
					          (
 | 
				
			||||||
 | 
					            requestPayload: RequestPayload,
 | 
				
			||||||
 | 
					            toolCallMessage: any,
 | 
				
			||||||
 | 
					            toolCallResult: any[],
 | 
				
			||||||
 | 
					          ) => {
 | 
				
			||||||
 | 
					            // @ts-ignore
 | 
				
			||||||
 | 
					            requestPayload?.messages?.splice(
 | 
				
			||||||
 | 
					              // @ts-ignore
 | 
				
			||||||
 | 
					              requestPayload?.messages?.length,
 | 
				
			||||||
 | 
					              0,
 | 
				
			||||||
 | 
					              toolCallMessage,
 | 
				
			||||||
 | 
					              ...toolCallResult,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          options,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        const res = await fetch(chatPath, chatPayload);
 | 
				
			||||||
 | 
					        clearTimeout(requestTimeoutId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const resJson = await res.json();
 | 
				
			||||||
 | 
					        const message = this.extractMessage(resJson);
 | 
				
			||||||
 | 
					        options.onFinish(message, res);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      console.log("[Request] failed to make a chat request", e);
 | 
				
			||||||
 | 
					      options.onError?.(e as Error);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  async usage() {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      used: 0,
 | 
				
			||||||
 | 
					      total: 0,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async models(): Promise<LLMModel[]> {
 | 
				
			||||||
 | 
					    if (this.disableListModels) {
 | 
				
			||||||
 | 
					      return DEFAULT_MODELS.slice();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const res = await fetch(this.path(PPIO.ListModelPath), {
 | 
				
			||||||
 | 
					      method: "GET",
 | 
				
			||||||
 | 
					      headers: {
 | 
				
			||||||
 | 
					        ...getHeaders(),
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const resJson = (await res.json()) as PPIOListModelResponse;
 | 
				
			||||||
 | 
					    const chatModels = resJson.data;
 | 
				
			||||||
 | 
					    console.log("[Models]", chatModels);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!chatModels) {
 | 
				
			||||||
 | 
					      return [];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let seq = 1000;
 | 
				
			||||||
 | 
					    return chatModels.map((m) => ({
 | 
				
			||||||
 | 
					      name: m.id,
 | 
				
			||||||
 | 
					      available: true,
 | 
				
			||||||
 | 
					      sorted: seq++,
 | 
				
			||||||
 | 
					      provider: {
 | 
				
			||||||
 | 
					        id: "ppio",
 | 
				
			||||||
 | 
					        providerName: "PPIO",
 | 
				
			||||||
 | 
					        providerType: "ppio",
 | 
				
			||||||
 | 
					        sorted: 15,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    }));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -75,6 +75,7 @@ import {
 | 
				
			|||||||
  ChatGLM,
 | 
					  ChatGLM,
 | 
				
			||||||
  DeepSeek,
 | 
					  DeepSeek,
 | 
				
			||||||
  SiliconFlow,
 | 
					  SiliconFlow,
 | 
				
			||||||
 | 
					  PPIO,
 | 
				
			||||||
} from "../constant";
 | 
					} from "../constant";
 | 
				
			||||||
import { Prompt, SearchService, usePromptStore } from "../store/prompt";
 | 
					import { Prompt, SearchService, usePromptStore } from "../store/prompt";
 | 
				
			||||||
import { ErrorBoundary } from "./error";
 | 
					import { ErrorBoundary } from "./error";
 | 
				
			||||||
@@ -1359,6 +1360,44 @@ export function Settings() {
 | 
				
			|||||||
      </ListItem>
 | 
					      </ListItem>
 | 
				
			||||||
    </>
 | 
					    </>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					  const ppioConfigComponent = accessStore.provider === ServiceProvider.PPIO && (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      <ListItem
 | 
				
			||||||
 | 
					        title={Locale.Settings.Access.PPIO.Endpoint.Title}
 | 
				
			||||||
 | 
					        subTitle={
 | 
				
			||||||
 | 
					          Locale.Settings.Access.PPIO.Endpoint.SubTitle + PPIO.ExampleEndpoint
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <input
 | 
				
			||||||
 | 
					          aria-label={Locale.Settings.Access.PPIO.Endpoint.Title}
 | 
				
			||||||
 | 
					          type="text"
 | 
				
			||||||
 | 
					          value={accessStore.ppioUrl}
 | 
				
			||||||
 | 
					          placeholder={PPIO.ExampleEndpoint}
 | 
				
			||||||
 | 
					          onChange={(e) =>
 | 
				
			||||||
 | 
					            accessStore.update(
 | 
				
			||||||
 | 
					              (access) => (access.ppioUrl = e.currentTarget.value),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ></input>
 | 
				
			||||||
 | 
					      </ListItem>
 | 
				
			||||||
 | 
					      <ListItem
 | 
				
			||||||
 | 
					        title={Locale.Settings.Access.PPIO.ApiKey.Title}
 | 
				
			||||||
 | 
					        subTitle={Locale.Settings.Access.PPIO.ApiKey.SubTitle}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <PasswordInput
 | 
				
			||||||
 | 
					          aria-label={Locale.Settings.Access.PPIO.ApiKey.Title}
 | 
				
			||||||
 | 
					          value={accessStore.ppioApiKey}
 | 
				
			||||||
 | 
					          type="text"
 | 
				
			||||||
 | 
					          placeholder={Locale.Settings.Access.PPIO.ApiKey.Placeholder}
 | 
				
			||||||
 | 
					          onChange={(e) => {
 | 
				
			||||||
 | 
					            accessStore.update(
 | 
				
			||||||
 | 
					              (access) => (access.ppioApiKey = e.currentTarget.value),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      </ListItem>
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const stabilityConfigComponent = accessStore.provider ===
 | 
					  const stabilityConfigComponent = accessStore.provider ===
 | 
				
			||||||
    ServiceProvider.Stability && (
 | 
					    ServiceProvider.Stability && (
 | 
				
			||||||
@@ -1822,6 +1861,7 @@ export function Settings() {
 | 
				
			|||||||
                  {XAIConfigComponent}
 | 
					                  {XAIConfigComponent}
 | 
				
			||||||
                  {chatglmConfigComponent}
 | 
					                  {chatglmConfigComponent}
 | 
				
			||||||
                  {siliconflowConfigComponent}
 | 
					                  {siliconflowConfigComponent}
 | 
				
			||||||
 | 
					                  {ppioConfigComponent}
 | 
				
			||||||
                </>
 | 
					                </>
 | 
				
			||||||
              )}
 | 
					              )}
 | 
				
			||||||
            </>
 | 
					            </>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -88,6 +88,10 @@ declare global {
 | 
				
			|||||||
      SILICONFLOW_URL?: string;
 | 
					      SILICONFLOW_URL?: string;
 | 
				
			||||||
      SILICONFLOW_API_KEY?: string;
 | 
					      SILICONFLOW_API_KEY?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // ppio only
 | 
				
			||||||
 | 
					      PPIO_URL?: string;
 | 
				
			||||||
 | 
					      PPIO_API_KEY?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // custom template for preprocessing user input
 | 
					      // custom template for preprocessing user input
 | 
				
			||||||
      DEFAULT_INPUT_TEMPLATE?: string;
 | 
					      DEFAULT_INPUT_TEMPLATE?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -163,6 +167,7 @@ export const getServerSideConfig = () => {
 | 
				
			|||||||
  const isXAI = !!process.env.XAI_API_KEY;
 | 
					  const isXAI = !!process.env.XAI_API_KEY;
 | 
				
			||||||
  const isChatGLM = !!process.env.CHATGLM_API_KEY;
 | 
					  const isChatGLM = !!process.env.CHATGLM_API_KEY;
 | 
				
			||||||
  const isSiliconFlow = !!process.env.SILICONFLOW_API_KEY;
 | 
					  const isSiliconFlow = !!process.env.SILICONFLOW_API_KEY;
 | 
				
			||||||
 | 
					  const isPPIO = !!process.env.PPIO_API_KEY;
 | 
				
			||||||
  // const apiKeyEnvVar = process.env.OPENAI_API_KEY ?? "";
 | 
					  // const apiKeyEnvVar = process.env.OPENAI_API_KEY ?? "";
 | 
				
			||||||
  // const apiKeys = apiKeyEnvVar.split(",").map((v) => v.trim());
 | 
					  // const apiKeys = apiKeyEnvVar.split(",").map((v) => v.trim());
 | 
				
			||||||
  // const randomIndex = Math.floor(Math.random() * apiKeys.length);
 | 
					  // const randomIndex = Math.floor(Math.random() * apiKeys.length);
 | 
				
			||||||
@@ -246,6 +251,10 @@ export const getServerSideConfig = () => {
 | 
				
			|||||||
    siliconFlowUrl: process.env.SILICONFLOW_URL,
 | 
					    siliconFlowUrl: process.env.SILICONFLOW_URL,
 | 
				
			||||||
    siliconFlowApiKey: getApiKey(process.env.SILICONFLOW_API_KEY),
 | 
					    siliconFlowApiKey: getApiKey(process.env.SILICONFLOW_API_KEY),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    isPPIO,
 | 
				
			||||||
 | 
					    ppioUrl: process.env.PPIO_URL,
 | 
				
			||||||
 | 
					    ppioApiKey: getApiKey(process.env.PPIO_API_KEY),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    gtmId: process.env.GTM_ID,
 | 
					    gtmId: process.env.GTM_ID,
 | 
				
			||||||
    gaId: process.env.GA_ID || DEFAULT_GA_ID,
 | 
					    gaId: process.env.GA_ID || DEFAULT_GA_ID,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -36,6 +36,8 @@ export const CHATGLM_BASE_URL = "https://open.bigmodel.cn";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export const SILICONFLOW_BASE_URL = "https://api.siliconflow.cn";
 | 
					export const SILICONFLOW_BASE_URL = "https://api.siliconflow.cn";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const PPIO_BASE_URL = "https://api.ppinfra.com/v3/openai";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const CACHE_URL_PREFIX = "/api/cache";
 | 
					export const CACHE_URL_PREFIX = "/api/cache";
 | 
				
			||||||
export const UPLOAD_URL = `${CACHE_URL_PREFIX}/upload`;
 | 
					export const UPLOAD_URL = `${CACHE_URL_PREFIX}/upload`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -72,6 +74,7 @@ export enum ApiPath {
 | 
				
			|||||||
  ChatGLM = "/api/chatglm",
 | 
					  ChatGLM = "/api/chatglm",
 | 
				
			||||||
  DeepSeek = "/api/deepseek",
 | 
					  DeepSeek = "/api/deepseek",
 | 
				
			||||||
  SiliconFlow = "/api/siliconflow",
 | 
					  SiliconFlow = "/api/siliconflow",
 | 
				
			||||||
 | 
					  PPIO = "/api/ppio",
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export enum SlotID {
 | 
					export enum SlotID {
 | 
				
			||||||
@@ -130,6 +133,7 @@ export enum ServiceProvider {
 | 
				
			|||||||
  ChatGLM = "ChatGLM",
 | 
					  ChatGLM = "ChatGLM",
 | 
				
			||||||
  DeepSeek = "DeepSeek",
 | 
					  DeepSeek = "DeepSeek",
 | 
				
			||||||
  SiliconFlow = "SiliconFlow",
 | 
					  SiliconFlow = "SiliconFlow",
 | 
				
			||||||
 | 
					  PPIO = "PPIO",
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Google API safety settings, see https://ai.google.dev/gemini-api/docs/safety-settings
 | 
					// Google API safety settings, see https://ai.google.dev/gemini-api/docs/safety-settings
 | 
				
			||||||
@@ -156,6 +160,7 @@ export enum ModelProvider {
 | 
				
			|||||||
  ChatGLM = "ChatGLM",
 | 
					  ChatGLM = "ChatGLM",
 | 
				
			||||||
  DeepSeek = "DeepSeek",
 | 
					  DeepSeek = "DeepSeek",
 | 
				
			||||||
  SiliconFlow = "SiliconFlow",
 | 
					  SiliconFlow = "SiliconFlow",
 | 
				
			||||||
 | 
					  PPIO = "PPIO",
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const Stability = {
 | 
					export const Stability = {
 | 
				
			||||||
@@ -266,6 +271,12 @@ export const SiliconFlow = {
 | 
				
			|||||||
  ListModelPath: "v1/models?&sub_type=chat",
 | 
					  ListModelPath: "v1/models?&sub_type=chat",
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const PPIO = {
 | 
				
			||||||
 | 
					  ExampleEndpoint: PPIO_BASE_URL,
 | 
				
			||||||
 | 
					  ChatPath: "chat/completions",
 | 
				
			||||||
 | 
					  ListModelPath: "models",
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const DEFAULT_INPUT_TEMPLATE = `{{input}}`; // input / time / model / lang
 | 
					export const DEFAULT_INPUT_TEMPLATE = `{{input}}`; // input / time / model / lang
 | 
				
			||||||
// export const DEFAULT_SYSTEM_TEMPLATE = `
 | 
					// export const DEFAULT_SYSTEM_TEMPLATE = `
 | 
				
			||||||
// You are ChatGPT, a large language model trained by {{ServiceProvider}}.
 | 
					// You are ChatGPT, a large language model trained by {{ServiceProvider}}.
 | 
				
			||||||
@@ -671,6 +682,28 @@ const siliconflowModels = [
 | 
				
			|||||||
  "Pro/deepseek-ai/DeepSeek-V3",
 | 
					  "Pro/deepseek-ai/DeepSeek-V3",
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ppioModels = [
 | 
				
			||||||
 | 
					  "deepseek/deepseek-r1/community",
 | 
				
			||||||
 | 
					  "deepseek/deepseek-v3/community",
 | 
				
			||||||
 | 
					  "deepseek/deepseek-r1",
 | 
				
			||||||
 | 
					  "deepseek/deepseek-v3",
 | 
				
			||||||
 | 
					  "deepseek/deepseek-r1-distill-llama-70b",
 | 
				
			||||||
 | 
					  "deepseek/deepseek-r1-distill-qwen-32b",
 | 
				
			||||||
 | 
					  "deepseek/deepseek-r1-distill-qwen-14b",
 | 
				
			||||||
 | 
					  "deepseek/deepseek-r1-distill-llama-8b",
 | 
				
			||||||
 | 
					  "qwen/qwen-2.5-72b-instruct",
 | 
				
			||||||
 | 
					  "qwen/qwen2.5-32b-instruct",
 | 
				
			||||||
 | 
					  "qwen/qwen-2-vl-72b-instruct",
 | 
				
			||||||
 | 
					  "meta-llama/llama-3.2-3b-instruct",
 | 
				
			||||||
 | 
					  "meta-llama/llama-3.1-70b-instruct",
 | 
				
			||||||
 | 
					  "meta-llama/llama-3.1-8b-instruct",
 | 
				
			||||||
 | 
					  "baichuan/baichuan2-13b-chat",
 | 
				
			||||||
 | 
					  "01-ai/yi-1.5-34b-chat",
 | 
				
			||||||
 | 
					  "01-ai/yi-1.5-9b-chat",
 | 
				
			||||||
 | 
					  "thudm/glm-4-9b-chat",
 | 
				
			||||||
 | 
					  "qwen/qwen-2-7b-instruct",
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let seq = 1000; // 内置的模型序号生成器从1000开始
 | 
					let seq = 1000; // 内置的模型序号生成器从1000开始
 | 
				
			||||||
export const DEFAULT_MODELS = [
 | 
					export const DEFAULT_MODELS = [
 | 
				
			||||||
  ...openaiModels.map((name) => ({
 | 
					  ...openaiModels.map((name) => ({
 | 
				
			||||||
@@ -827,6 +860,17 @@ export const DEFAULT_MODELS = [
 | 
				
			|||||||
      sorted: 14,
 | 
					      sorted: 14,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  })),
 | 
					  })),
 | 
				
			||||||
 | 
					  ...ppioModels.map((name) => ({
 | 
				
			||||||
 | 
					    name,
 | 
				
			||||||
 | 
					    available: true,
 | 
				
			||||||
 | 
					    sorted: seq++,
 | 
				
			||||||
 | 
					    provider: {
 | 
				
			||||||
 | 
					      id: "ppio",
 | 
				
			||||||
 | 
					      providerName: "PPIO",
 | 
				
			||||||
 | 
					      providerType: "ppio",
 | 
				
			||||||
 | 
					      sorted: 15,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  })),
 | 
				
			||||||
] as const;
 | 
					] as const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const CHAT_PAGE_SIZE = 15;
 | 
					export const CHAT_PAGE_SIZE = 15;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -507,6 +507,17 @@ const cn = {
 | 
				
			|||||||
          SubTitle: "样例:",
 | 
					          SubTitle: "样例:",
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
 | 
					      PPIO: {
 | 
				
			||||||
 | 
					        ApiKey: {
 | 
				
			||||||
 | 
					          Title: "接口密钥",
 | 
				
			||||||
 | 
					          SubTitle: "使用自定义PPIO API Key",
 | 
				
			||||||
 | 
					          Placeholder: "PPIO API Key",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        Endpoint: {
 | 
				
			||||||
 | 
					          Title: "接口地址",
 | 
				
			||||||
 | 
					          SubTitle: "样例:",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
      Stability: {
 | 
					      Stability: {
 | 
				
			||||||
        ApiKey: {
 | 
					        ApiKey: {
 | 
				
			||||||
          Title: "接口密钥",
 | 
					          Title: "接口密钥",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -491,6 +491,17 @@ const en: LocaleType = {
 | 
				
			|||||||
          SubTitle: "Example: ",
 | 
					          SubTitle: "Example: ",
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
 | 
					      PPIO: {
 | 
				
			||||||
 | 
					        ApiKey: {
 | 
				
			||||||
 | 
					          Title: "PPIO API Key",
 | 
				
			||||||
 | 
					          SubTitle: "Use a custom PPIO API Key",
 | 
				
			||||||
 | 
					          Placeholder: "PPIO API Key",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        Endpoint: {
 | 
				
			||||||
 | 
					          Title: "Endpoint Address",
 | 
				
			||||||
 | 
					          SubTitle: "Example: ",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
      Stability: {
 | 
					      Stability: {
 | 
				
			||||||
        ApiKey: {
 | 
					        ApiKey: {
 | 
				
			||||||
          Title: "Stability API Key",
 | 
					          Title: "Stability API Key",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,6 +17,7 @@ import {
 | 
				
			|||||||
  XAI_BASE_URL,
 | 
					  XAI_BASE_URL,
 | 
				
			||||||
  CHATGLM_BASE_URL,
 | 
					  CHATGLM_BASE_URL,
 | 
				
			||||||
  SILICONFLOW_BASE_URL,
 | 
					  SILICONFLOW_BASE_URL,
 | 
				
			||||||
 | 
					  PPIO_BASE_URL,
 | 
				
			||||||
} from "../constant";
 | 
					} from "../constant";
 | 
				
			||||||
import { getHeaders } from "../client/api";
 | 
					import { getHeaders } from "../client/api";
 | 
				
			||||||
import { getClientConfig } from "../config/client";
 | 
					import { getClientConfig } from "../config/client";
 | 
				
			||||||
@@ -59,6 +60,8 @@ const DEFAULT_SILICONFLOW_URL = isApp
 | 
				
			|||||||
  ? SILICONFLOW_BASE_URL
 | 
					  ? SILICONFLOW_BASE_URL
 | 
				
			||||||
  : ApiPath.SiliconFlow;
 | 
					  : ApiPath.SiliconFlow;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DEFAULT_PPIO_URL = isApp ? PPIO_BASE_URL : ApiPath.PPIO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DEFAULT_ACCESS_STATE = {
 | 
					const DEFAULT_ACCESS_STATE = {
 | 
				
			||||||
  accessCode: "",
 | 
					  accessCode: "",
 | 
				
			||||||
  useCustomConfig: false,
 | 
					  useCustomConfig: false,
 | 
				
			||||||
@@ -132,6 +135,10 @@ const DEFAULT_ACCESS_STATE = {
 | 
				
			|||||||
  siliconflowUrl: DEFAULT_SILICONFLOW_URL,
 | 
					  siliconflowUrl: DEFAULT_SILICONFLOW_URL,
 | 
				
			||||||
  siliconflowApiKey: "",
 | 
					  siliconflowApiKey: "",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // ppio
 | 
				
			||||||
 | 
					  ppioUrl: DEFAULT_PPIO_URL,
 | 
				
			||||||
 | 
					  ppioApiKey: "",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // server config
 | 
					  // server config
 | 
				
			||||||
  needCode: true,
 | 
					  needCode: true,
 | 
				
			||||||
  hideUserApiKey: false,
 | 
					  hideUserApiKey: false,
 | 
				
			||||||
@@ -219,6 +226,10 @@ export const useAccessStore = createPersistStore(
 | 
				
			|||||||
      return ensure(get(), ["siliconflowApiKey"]);
 | 
					      return ensure(get(), ["siliconflowApiKey"]);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    isValidPPIO() {
 | 
				
			||||||
 | 
					      return ensure(get(), ["ppioApiKey"]);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    isAuthorized() {
 | 
					    isAuthorized() {
 | 
				
			||||||
      this.fetch();
 | 
					      this.fetch();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -238,6 +249,7 @@ export const useAccessStore = createPersistStore(
 | 
				
			|||||||
        this.isValidXAI() ||
 | 
					        this.isValidXAI() ||
 | 
				
			||||||
        this.isValidChatGLM() ||
 | 
					        this.isValidChatGLM() ||
 | 
				
			||||||
        this.isValidSiliconFlow() ||
 | 
					        this.isValidSiliconFlow() ||
 | 
				
			||||||
 | 
					        this.isValidPPIO() ||
 | 
				
			||||||
        !this.enabledAccessControl() ||
 | 
					        !this.enabledAccessControl() ||
 | 
				
			||||||
        (this.enabledAccessControl() && ensure(get(), ["accessCode"]))
 | 
					        (this.enabledAccessControl() && ensure(get(), ["accessCode"]))
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -714,6 +714,12 @@ export const useChatStore = createPersistStore(
 | 
				
			|||||||
            },
 | 
					            },
 | 
				
			||||||
            onFinish(message, responseRes) {
 | 
					            onFinish(message, responseRes) {
 | 
				
			||||||
              if (responseRes?.status === 200) {
 | 
					              if (responseRes?.status === 200) {
 | 
				
			||||||
 | 
					                // deal with <think> and </think> tags
 | 
				
			||||||
 | 
					                if (message.startsWith("<think>")) {
 | 
				
			||||||
 | 
					                  message = message
 | 
				
			||||||
 | 
					                    .slice(message.indexOf("</think>") + 8)
 | 
				
			||||||
 | 
					                    .trim();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
                get().updateTargetSession(
 | 
					                get().updateTargetSession(
 | 
				
			||||||
                  session,
 | 
					                  session,
 | 
				
			||||||
                  (session) =>
 | 
					                  (session) =>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -633,7 +633,8 @@ export function streamWithThink(
 | 
				
			|||||||
              if (chunk.content.includes("\n\n")) {
 | 
					              if (chunk.content.includes("\n\n")) {
 | 
				
			||||||
                const lines = chunk.content.split("\n\n");
 | 
					                const lines = chunk.content.split("\n\n");
 | 
				
			||||||
                remainText += lines.join("\n\n> ");
 | 
					                remainText += lines.join("\n\n> ");
 | 
				
			||||||
              } else {
 | 
					              } else if (chunk.content != "\n") {
 | 
				
			||||||
 | 
					                // deal with single newline after <think> tag
 | 
				
			||||||
                remainText += chunk.content;
 | 
					                remainText += chunk.content;
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user