From 40b673054b66703763f8441a55fb9c6720433599 Mon Sep 17 00:00:00 2001 From: Hk-Gosuto Date: Mon, 28 Oct 2024 13:09:30 +0800 Subject: [PATCH] fix: #314 --- .env.template | 2 +- README.md | 2 +- README_CN.md | 2 +- README_JA.md | 2 +- app/api/artifacts/route.ts | 73 +++++++++++++++++++++++++++++++++++++ app/components/markdown.tsx | 20 +++++++++- app/config/server.ts | 58 +++++++++++++++++++++++++---- app/constant.ts | 2 + 8 files changed, 148 insertions(+), 13 deletions(-) create mode 100644 app/api/artifacts/route.ts diff --git a/.env.template b/.env.template index 85e9f766c..0187e1cdd 100644 --- a/.env.template +++ b/.env.template @@ -97,7 +97,7 @@ ANTHROPIC_API_VERSION= ANTHROPIC_URL= ### (optional) -WHITE_WEBDEV_ENDPOINTS= +WHITE_WEBDAV_ENDPOINTS= # (optional) # Default: zh-CN-YunxiNeural diff --git a/README.md b/README.md index 233bd9822..0b229d93d 100644 --- a/README.md +++ b/README.md @@ -309,7 +309,7 @@ For ByteDance: use `modelName@bytedance=deploymentName` to customize model name Change default model -### `WHITE_WEBDEV_ENDPOINTS` (optional) +### `WHITE_WEBDAV_ENDPOINTS` (optional) 如果你想增加允许访问的webdav服务地址,可以使用该选项,格式要求: - 每一个地址必须是一个完整的 endpoint diff --git a/README_CN.md b/README_CN.md index ec348698c..35e8b525b 100644 --- a/README_CN.md +++ b/README_CN.md @@ -188,7 +188,7 @@ ByteDance Api Url. 如果你想禁用从链接解析预制设置,将此环境变量设置为 1 即可。 -### `WHITE_WEBDEV_ENDPOINTS` (可选) +### `WHITE_WEBDAV_ENDPOINTS` (可选) 如果你想增加允许访问的webdav服务地址,可以使用该选项,格式要求: - 每一个地址必须是一个完整的 endpoint diff --git a/README_JA.md b/README_JA.md index 6b8caadae..1716089af 100644 --- a/README_JA.md +++ b/README_JA.md @@ -193,7 +193,7 @@ ByteDance API の URL。 リンクからのプリセット設定解析を無効にしたい場合は、この環境変数を 1 に設定します。 -### `WHITE_WEBDEV_ENDPOINTS` (オプション) +### `WHITE_WEBDAV_ENDPOINTS` (オプション) アクセス許可を与える WebDAV サービスのアドレスを追加したい場合、このオプションを使用します。フォーマット要件: - 各アドレスは完全なエンドポイントでなければなりません。 diff --git a/app/api/artifacts/route.ts b/app/api/artifacts/route.ts new file mode 100644 index 000000000..4707e795f --- /dev/null +++ b/app/api/artifacts/route.ts @@ -0,0 +1,73 @@ +import md5 from "spark-md5"; +import { NextRequest, NextResponse } from "next/server"; +import { getServerSideConfig } from "@/app/config/server"; + +async function handle(req: NextRequest, res: NextResponse) { + const serverConfig = getServerSideConfig(); + const storeUrl = () => + `https://api.cloudflare.com/client/v4/accounts/${serverConfig.cloudflareAccountId}/storage/kv/namespaces/${serverConfig.cloudflareKVNamespaceId}`; + const storeHeaders = () => ({ + Authorization: `Bearer ${serverConfig.cloudflareKVApiKey}`, + }); + if (req.method === "POST") { + const clonedBody = await req.text(); + const hashedCode = md5.hash(clonedBody).trim(); + const body: { + key: string; + value: string; + expiration_ttl?: number; + } = { + key: hashedCode, + value: clonedBody, + }; + try { + const ttl = parseInt(serverConfig.cloudflareKVTTL as string); + if (ttl > 60) { + body["expiration_ttl"] = ttl; + } + } catch (e) { + console.error(e); + } + const res = await fetch(`${storeUrl()}/bulk`, { + headers: { + ...storeHeaders(), + "Content-Type": "application/json", + }, + method: "PUT", + body: JSON.stringify([body]), + }); + const result = await res.json(); + console.log("save data", result); + if (result?.success) { + return NextResponse.json( + { code: 0, id: hashedCode, result }, + { status: res.status }, + ); + } + return NextResponse.json( + { error: true, msg: "Save data error" }, + { status: 400 }, + ); + } + if (req.method === "GET") { + const id = req?.nextUrl?.searchParams?.get("id"); + const res = await fetch(`${storeUrl()}/values/${id}`, { + headers: storeHeaders(), + method: "GET", + }); + return new Response(res.body, { + status: res.status, + statusText: res.statusText, + headers: res.headers, + }); + } + return NextResponse.json( + { error: true, msg: "Invalid request" }, + { status: 400 }, + ); +} + +export const POST = handle; +export const GET = handle; + +export const runtime = "edge"; diff --git a/app/components/markdown.tsx b/app/components/markdown.tsx index 48b3536fe..a047f63ec 100644 --- a/app/components/markdown.tsx +++ b/app/components/markdown.tsx @@ -121,7 +121,25 @@ export function PreCode(props: { children: any }) { codeElement.style.whiteSpace = "pre-wrap"; } }); - setTimeout(renderArtifacts, 1); + + const observer = new MutationObserver((mutations) => { + for (const mutation of mutations) { + if ( + mutation.type === "characterData" || + mutation.type === "childList" + ) { + renderArtifacts(); + break; + } + } + }); + observer.observe(ref.current, { + childList: true, + subtree: true, + characterData: true, + }); + renderArtifacts(); + return () => observer.disconnect(); } }, []); diff --git a/app/config/server.ts b/app/config/server.ts index 2a7c8fa42..c628a928c 100644 --- a/app/config/server.ts +++ b/app/config/server.ts @@ -1,5 +1,5 @@ import md5 from "spark-md5"; -import { DEFAULT_MODELS } from "../constant"; +import { DEFAULT_MODELS, DEFAULT_GA_ID } from "../constant"; declare global { namespace NodeJS { @@ -101,19 +101,34 @@ export const getServerSideConfig = () => { if (disableGPT4) { if (customModels) customModels += ","; - customModels += DEFAULT_MODELS.filter((m) => m.name.startsWith("gpt-4")) + customModels += DEFAULT_MODELS.filter( + (m) => + (m.name.startsWith("gpt-4") || m.name.startsWith("chatgpt-4o")) && + !m.name.startsWith("gpt-4o-mini"), + ) .map((m) => "-" + m.name) .join(","); - if (defaultModel.startsWith("gpt-4")) defaultModel = ""; + if ( + (defaultModel.startsWith("gpt-4") || + defaultModel.startsWith("chatgpt-4o")) && + !defaultModel.startsWith("gpt-4o-mini") + ) + defaultModel = ""; } + const isStability = !!process.env.STABILITY_API_KEY; + const isAzure = !!process.env.AZURE_URL; const isGoogle = !!process.env.GOOGLE_API_KEY; const isAnthropic = !!process.env.ANTHROPIC_API_KEY; + const isTencent = !!process.env.TENCENT_API_KEY; const isBaidu = !!process.env.BAIDU_API_KEY; const isBytedance = !!process.env.BYTEDANCE_API_KEY; const isAlibaba = !!process.env.ALIBABA_API_KEY; + const isMoonshot = !!process.env.MOONSHOT_API_KEY; + const isIflytek = !!process.env.IFLYTEK_API_KEY; + const isXAI = !!process.env.XAI_API_KEY; // const apiKeyEnvVar = process.env.OPENAI_API_KEY ?? ""; // const apiKeys = apiKeyEnvVar.split(",").map((v) => v.trim()); // const randomIndex = Math.floor(Math.random() * apiKeys.length); @@ -122,8 +137,8 @@ export const getServerSideConfig = () => { // `[Server Config] using ${randomIndex + 1} of ${apiKeys.length} api key`, // ); - const allowedWebDevEndpoints = ( - process.env.WHITE_WEBDEV_ENDPOINTS ?? "" + const allowedWebDavEndpoints = ( + process.env.WHITE_WEBDAV_ENDPOINTS ?? "" ).split(","); return { @@ -131,6 +146,10 @@ export const getServerSideConfig = () => { apiKey: getApiKey(process.env.OPENAI_API_KEY), openaiOrgId: process.env.OPENAI_ORG_ID, + isStability, + stabilityUrl: process.env.STABILITY_URL, + stabilityApiKey: getApiKey(process.env.STABILITY_API_KEY), + isAzure, azureUrl: process.env.AZURE_URL, azureApiKey: getApiKey(process.env.AZURE_API_KEY), @@ -158,7 +177,31 @@ export const getServerSideConfig = () => { alibabaUrl: process.env.ALIBABA_URL, alibabaApiKey: getApiKey(process.env.ALIBABA_API_KEY), + isTencent, + tencentUrl: process.env.TENCENT_URL, + tencentSecretKey: getApiKey(process.env.TENCENT_SECRET_KEY), + tencentSecretId: process.env.TENCENT_SECRET_ID, + + isMoonshot, + moonshotUrl: process.env.MOONSHOT_URL, + moonshotApiKey: getApiKey(process.env.MOONSHOT_API_KEY), + + isIflytek, + iflytekUrl: process.env.IFLYTEK_URL, + iflytekApiKey: process.env.IFLYTEK_API_KEY, + iflytekApiSecret: process.env.IFLYTEK_API_SECRET, + + isXAI, + xaiUrl: process.env.XAI_URL, + xaiApiKey: getApiKey(process.env.XAI_API_KEY), + + cloudflareAccountId: process.env.CLOUDFLARE_ACCOUNT_ID, + cloudflareKVNamespaceId: process.env.CLOUDFLARE_KV_NAMESPACE_ID, + cloudflareKVApiKey: getApiKey(process.env.CLOUDFLARE_KV_API_KEY), + cloudflareKVTTL: process.env.CLOUDFLARE_KV_TTL, + gtmId: process.env.GTM_ID, + gaId: process.env.GA_ID || DEFAULT_GA_ID, needCode: ACCESS_CODES.size > 0, code: process.env.CODE, @@ -173,19 +216,18 @@ export const getServerSideConfig = () => { disableFastLink: !!process.env.DISABLE_FAST_LINK, customModels, defaultModel, + allowedWebDavEndpoints, + isStoreFileToLocal: !!process.env.NEXT_PUBLIC_ENABLE_NODEJS_PLUGIN && !process.env.R2_ACCOUNT_ID && !process.env.S3_ENDPOINT, - isEnableRAG: !!process.env.ENABLE_RAG, ragEmbeddingModel: process.env.RAG_EMBEDDING_MODEL ?? "text-embedding-3-large", ragChunkSize: process.env.RAG_CHUNK_SIZE ?? "2000", ragChunkOverlap: process.env.RAG_CHUNK_OVERLAP ?? "200", ragReturnCount: process.env.RAG_RETURN_COUNT ?? "4", - allowedWebDevEndpoints, - edgeTTSVoiceName: process.env.EDGE_TTS_VOICE_NAME ?? "zh-CN-YunxiNeural", isUseOpenAIEndpointForAllModels: !!process.env.USE_OPENAI_ENDPOINT_FOR_ALL_MODELS, diff --git a/app/constant.ts b/app/constant.ts index c6ce41314..4de1f633e 100644 --- a/app/constant.ts +++ b/app/constant.ts @@ -388,6 +388,8 @@ export const internalAllowedWebDavEndpoints = [ "https://app.koofr.net/dav/Koofr", ]; +export const DEFAULT_GA_ID = "G-89WN60ZK2E"; + export const MYFILES_BROWSER_TOOLS_SYSTEM_PROMPT = ` # Tools