mirror of
				https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
				synced 2025-11-04 08:13:43 +08:00 
			
		
		
		
	feat: merge main
This commit is contained in:
		@@ -245,13 +245,17 @@ To control custom models, use `+` to add a custom model, use `-` to hide a model
 | 
			
		||||
 | 
			
		||||
User `-all` to disable all default models, `+all` to enable all default models.
 | 
			
		||||
 | 
			
		||||
### `WHITE_WEBDEV_ENDPOINTS` (可选)
 | 
			
		||||
### `WHITE_WEBDEV_ENDPOINTS` (optional)
 | 
			
		||||
 | 
			
		||||
You can use this option if you want to increase the number of webdav service addresses you are allowed to access, as required by the format:
 | 
			
		||||
- Each address must be a complete endpoint 
 | 
			
		||||
> `https://xxxx/yyy`
 | 
			
		||||
- Multiple addresses are connected by ', '
 | 
			
		||||
 | 
			
		||||
### `DEFAULT_INPUT_TEMPLATE` (optional)
 | 
			
		||||
 | 
			
		||||
Customize the default template used to initialize the User Input Preprocessing configuration item in Settings.
 | 
			
		||||
 | 
			
		||||
## Requirements
 | 
			
		||||
 | 
			
		||||
NodeJS >= 18, Docker >= 20
 | 
			
		||||
 
 | 
			
		||||
@@ -156,6 +156,9 @@ anthropic claude Api Url.
 | 
			
		||||
 | 
			
		||||
用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名` 来自定义模型的展示名,用英文逗号隔开。
 | 
			
		||||
 | 
			
		||||
### `DEFAULT_INPUT_TEMPLATE` (可选)
 | 
			
		||||
自定义默认的 template,用于初始化『设置』中的『用户输入预处理』配置项
 | 
			
		||||
 | 
			
		||||
## 开发
 | 
			
		||||
 | 
			
		||||
点击下方按钮,开始二次开发:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,12 @@
 | 
			
		||||
import { NextRequest, NextResponse } from "next/server";
 | 
			
		||||
import { STORAGE_KEY, internalWhiteWebDavEndpoints } from "../../../constant";
 | 
			
		||||
import { STORAGE_KEY, internalAllowedWebDavEndpoints } from "../../../constant";
 | 
			
		||||
import { getServerSideConfig } from "@/app/config/server";
 | 
			
		||||
 | 
			
		||||
const config = getServerSideConfig();
 | 
			
		||||
 | 
			
		||||
const mergedWhiteWebDavEndpoints = [
 | 
			
		||||
  ...internalWhiteWebDavEndpoints,
 | 
			
		||||
  ...config.whiteWebDevEndpoints,
 | 
			
		||||
const mergedAllowedWebDavEndpoints = [
 | 
			
		||||
  ...internalAllowedWebDavEndpoints,
 | 
			
		||||
  ...config.allowedWebDevEndpoints,
 | 
			
		||||
].filter((domain) => Boolean(domain.trim()));
 | 
			
		||||
 | 
			
		||||
async function handle(
 | 
			
		||||
@@ -24,7 +24,9 @@ async function handle(
 | 
			
		||||
 | 
			
		||||
  // Validate the endpoint to prevent potential SSRF attacks
 | 
			
		||||
  if (
 | 
			
		||||
    !mergedWhiteWebDavEndpoints.some((white) => endpoint?.startsWith(white))
 | 
			
		||||
    !mergedAllowedWebDavEndpoints.some(
 | 
			
		||||
      (allowedEndpoint) => endpoint?.startsWith(allowedEndpoint),
 | 
			
		||||
    )
 | 
			
		||||
  ) {
 | 
			
		||||
    return NextResponse.json(
 | 
			
		||||
      {
 | 
			
		||||
 
 | 
			
		||||
@@ -161,6 +161,13 @@ export class ClaudeApi implements LLMApi {
 | 
			
		||||
        };
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    if (prompt[0]?.role === "assistant") {
 | 
			
		||||
      prompt.unshift({
 | 
			
		||||
        role: "user",
 | 
			
		||||
        content: ";",
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const requestBody: AnthropicChatRequest = {
 | 
			
		||||
      messages: prompt,
 | 
			
		||||
      stream: shouldStream,
 | 
			
		||||
 
 | 
			
		||||
@@ -21,11 +21,10 @@ export class GeminiProApi implements LLMApi {
 | 
			
		||||
  }
 | 
			
		||||
  async chat(options: ChatOptions): Promise<void> {
 | 
			
		||||
    // const apiClient = this;
 | 
			
		||||
    const visionModel = isVisionModel(options.config.model);
 | 
			
		||||
    let multimodal = false;
 | 
			
		||||
    const messages = options.messages.map((v) => {
 | 
			
		||||
      let parts: any[] = [{ text: getMessageTextContent(v) }];
 | 
			
		||||
      if (visionModel) {
 | 
			
		||||
      if (isVisionModel(options.config.model)) {
 | 
			
		||||
        const images = getMessageImages(v);
 | 
			
		||||
        if (images.length > 0) {
 | 
			
		||||
          multimodal = true;
 | 
			
		||||
@@ -117,17 +116,14 @@ export class GeminiProApi implements LLMApi {
 | 
			
		||||
    const controller = new AbortController();
 | 
			
		||||
    options.onController?.(controller);
 | 
			
		||||
    try {
 | 
			
		||||
      let googleChatPath = visionModel
 | 
			
		||||
        ? Google.VisionChatPath(modelConfig.model)
 | 
			
		||||
        : Google.ChatPath(modelConfig.model);
 | 
			
		||||
      let chatPath = this.path(googleChatPath);
 | 
			
		||||
 | 
			
		||||
      // let baseUrl = accessStore.googleUrl;
 | 
			
		||||
 | 
			
		||||
      if (!baseUrl) {
 | 
			
		||||
        baseUrl = isApp
 | 
			
		||||
          ? DEFAULT_API_HOST + "/api/proxy/google/" + googleChatPath
 | 
			
		||||
          : chatPath;
 | 
			
		||||
          ? DEFAULT_API_HOST +
 | 
			
		||||
            "/api/proxy/google/" +
 | 
			
		||||
            Google.ChatPath(modelConfig.model)
 | 
			
		||||
          : this.path(Google.ChatPath(modelConfig.model));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (isApp) {
 | 
			
		||||
@@ -145,6 +141,7 @@ export class GeminiProApi implements LLMApi {
 | 
			
		||||
        () => controller.abort(),
 | 
			
		||||
        REQUEST_TIMEOUT_MS,
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      if (shouldStream) {
 | 
			
		||||
        let responseText = "";
 | 
			
		||||
        let remainText = "";
 | 
			
		||||
 
 | 
			
		||||
@@ -129,7 +129,7 @@ export class ChatGPTApi implements LLMApi {
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // add max_tokens to vision model
 | 
			
		||||
    if (visionModel) {
 | 
			
		||||
    if (visionModel && modelConfig.model.includes("preview")) {
 | 
			
		||||
      requestPayload["max_tokens"] = Math.max(modelConfig.max_tokens, 4000);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -59,9 +59,10 @@ import {
 | 
			
		||||
  getMessageTextContent,
 | 
			
		||||
  getMessageImages,
 | 
			
		||||
  isVisionModel,
 | 
			
		||||
  compressImage,
 | 
			
		||||
} from "../utils";
 | 
			
		||||
 | 
			
		||||
import { compressImage } from "@/app/utils/chat";
 | 
			
		||||
 | 
			
		||||
import dynamic from "next/dynamic";
 | 
			
		||||
 | 
			
		||||
import { ChatControllerPool } from "../client/controller";
 | 
			
		||||
@@ -1088,6 +1089,7 @@ function _Chat() {
 | 
			
		||||
            if (payload.url) {
 | 
			
		||||
              accessStore.update((access) => (access.openaiUrl = payload.url!));
 | 
			
		||||
            }
 | 
			
		||||
            accessStore.update((access) => (access.useCustomConfig = true));
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
      } catch {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
import tauriConfig from "../../src-tauri/tauri.conf.json";
 | 
			
		||||
import { DEFAULT_INPUT_TEMPLATE } from "../constant";
 | 
			
		||||
 | 
			
		||||
export const getBuildConfig = () => {
 | 
			
		||||
  if (typeof process === "undefined") {
 | 
			
		||||
@@ -38,6 +39,7 @@ export const getBuildConfig = () => {
 | 
			
		||||
    ...commitInfo,
 | 
			
		||||
    buildMode,
 | 
			
		||||
    isApp,
 | 
			
		||||
    template: process.env.DEFAULT_INPUT_TEMPLATE ?? DEFAULT_INPUT_TEMPLATE,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -34,6 +34,9 @@ declare global {
 | 
			
		||||
 | 
			
		||||
      // google tag manager
 | 
			
		||||
      GTM_ID?: string;
 | 
			
		||||
 | 
			
		||||
      // custom template for preprocessing user input
 | 
			
		||||
      DEFAULT_INPUT_TEMPLATE?: string;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -51,6 +54,22 @@ const ACCESS_CODES = (function getAccessCodes(): Set<string> {
 | 
			
		||||
  }
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
function getApiKey(keys?: string) {
 | 
			
		||||
  const apiKeyEnvVar = keys ?? "";
 | 
			
		||||
  const apiKeys = apiKeyEnvVar.split(",").map((v) => v.trim());
 | 
			
		||||
  const randomIndex = Math.floor(Math.random() * apiKeys.length);
 | 
			
		||||
  const apiKey = apiKeys[randomIndex];
 | 
			
		||||
  if (apiKey) {
 | 
			
		||||
    console.log(
 | 
			
		||||
      `[Server Config] using ${randomIndex + 1} of ${
 | 
			
		||||
        apiKeys.length
 | 
			
		||||
      } api key - ${apiKey}`,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return apiKey;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const getServerSideConfig = () => {
 | 
			
		||||
  if (typeof process === "undefined") {
 | 
			
		||||
    throw Error(
 | 
			
		||||
@@ -74,34 +93,34 @@ export const getServerSideConfig = () => {
 | 
			
		||||
  const isGoogle = !!process.env.GOOGLE_API_KEY;
 | 
			
		||||
  const isAnthropic = !!process.env.ANTHROPIC_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);
 | 
			
		||||
  const apiKey = apiKeys[randomIndex];
 | 
			
		||||
  console.log(
 | 
			
		||||
    `[Server Config] using ${randomIndex + 1} of ${apiKeys.length} 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);
 | 
			
		||||
  // const apiKey = apiKeys[randomIndex];
 | 
			
		||||
  // console.log(
 | 
			
		||||
  //   `[Server Config] using ${randomIndex + 1} of ${apiKeys.length} api key`,
 | 
			
		||||
  // );
 | 
			
		||||
 | 
			
		||||
  const whiteWebDevEndpoints = (process.env.WHITE_WEBDEV_ENDPOINTS ?? "").split(
 | 
			
		||||
    ",",
 | 
			
		||||
  );
 | 
			
		||||
  const allowedWebDevEndpoints = (
 | 
			
		||||
    process.env.WHITE_WEBDEV_ENDPOINTS ?? ""
 | 
			
		||||
  ).split(",");
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    baseUrl: process.env.BASE_URL,
 | 
			
		||||
    apiKey,
 | 
			
		||||
    apiKey: getApiKey(process.env.OPENAI_API_KEY),
 | 
			
		||||
    openaiOrgId: process.env.OPENAI_ORG_ID,
 | 
			
		||||
 | 
			
		||||
    isAzure,
 | 
			
		||||
    azureUrl: process.env.AZURE_URL,
 | 
			
		||||
    azureApiKey: process.env.AZURE_API_KEY,
 | 
			
		||||
    azureApiKey: getApiKey(process.env.AZURE_API_KEY),
 | 
			
		||||
    azureApiVersion: process.env.AZURE_API_VERSION,
 | 
			
		||||
 | 
			
		||||
    isGoogle,
 | 
			
		||||
    googleApiKey: process.env.GOOGLE_API_KEY,
 | 
			
		||||
    googleApiKey: getApiKey(process.env.GOOGLE_API_KEY),
 | 
			
		||||
    googleUrl: process.env.GOOGLE_URL,
 | 
			
		||||
 | 
			
		||||
    isAnthropic,
 | 
			
		||||
    anthropicApiKey: process.env.ANTHROPIC_API_KEY,
 | 
			
		||||
    anthropicApiKey: getApiKey(process.env.ANTHROPIC_API_KEY),
 | 
			
		||||
    anthropicApiVersion: process.env.ANTHROPIC_API_VERSION,
 | 
			
		||||
    anthropicUrl: process.env.ANTHROPIC_URL,
 | 
			
		||||
 | 
			
		||||
@@ -120,6 +139,6 @@ export const getServerSideConfig = () => {
 | 
			
		||||
    disableFastLink: !!process.env.DISABLE_FAST_LINK,
 | 
			
		||||
    customModels,
 | 
			
		||||
    defaultModel,
 | 
			
		||||
    whiteWebDevEndpoints,
 | 
			
		||||
    allowedWebDevEndpoints,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -107,8 +107,6 @@ export const Azure = {
 | 
			
		||||
export const Google = {
 | 
			
		||||
  ExampleEndpoint: "https://generativelanguage.googleapis.com/",
 | 
			
		||||
  ChatPath: (modelName: string) => `v1beta/models/${modelName}:generateContent`,
 | 
			
		||||
  VisionChatPath: (modelName: string) =>
 | 
			
		||||
    `v1beta/models/${modelName}:generateContent`,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const DEFAULT_INPUT_TEMPLATE = `{{input}}`; // input / time / model / lang
 | 
			
		||||
@@ -137,8 +135,8 @@ export const KnowledgeCutOffDate: Record<string, string> = {
 | 
			
		||||
  "gpt-4-turbo": "2023-12",
 | 
			
		||||
  "gpt-4-turbo-2024-04-09": "2023-12",
 | 
			
		||||
  "gpt-4-turbo-preview": "2023-12",
 | 
			
		||||
  "gpt-4-1106-preview": "2023-04",
 | 
			
		||||
  "gpt-4-0125-preview": "2023-12",
 | 
			
		||||
  "gpt-4o": "2023-10",
 | 
			
		||||
  "gpt-4o-2024-05-13": "2023-10",
 | 
			
		||||
  "gpt-4-vision-preview": "2023-04",
 | 
			
		||||
  // After improvements,
 | 
			
		||||
  // it's now easier to add "KnowledgeCutOffDate" instead of stupid hardcoding it, as was done previously.
 | 
			
		||||
@@ -148,22 +146,16 @@ export const KnowledgeCutOffDate: Record<string, string> = {
 | 
			
		||||
 | 
			
		||||
const openaiModels = [
 | 
			
		||||
  "gpt-3.5-turbo",
 | 
			
		||||
  "gpt-3.5-turbo-0301",
 | 
			
		||||
  "gpt-3.5-turbo-0613",
 | 
			
		||||
  "gpt-3.5-turbo-1106",
 | 
			
		||||
  "gpt-3.5-turbo-0125",
 | 
			
		||||
  "gpt-3.5-turbo-16k",
 | 
			
		||||
  "gpt-3.5-turbo-16k-0613",
 | 
			
		||||
  "gpt-4",
 | 
			
		||||
  "gpt-4-0314",
 | 
			
		||||
  "gpt-4-0613",
 | 
			
		||||
  "gpt-4-1106-preview",
 | 
			
		||||
  "gpt-4-0125-preview",
 | 
			
		||||
  "gpt-4-32k",
 | 
			
		||||
  "gpt-4-32k-0314",
 | 
			
		||||
  "gpt-4-32k-0613",
 | 
			
		||||
  "gpt-4-turbo",
 | 
			
		||||
  "gpt-4-turbo-preview",
 | 
			
		||||
  "gpt-4o",
 | 
			
		||||
  "gpt-4o-2024-05-13",
 | 
			
		||||
  "gpt-4-vision-preview",
 | 
			
		||||
  "gpt-4-turbo-2024-04-09",
 | 
			
		||||
];
 | 
			
		||||
@@ -171,6 +163,7 @@ const openaiModels = [
 | 
			
		||||
const googleModels = [
 | 
			
		||||
  "gemini-1.0-pro",
 | 
			
		||||
  "gemini-1.5-pro-latest",
 | 
			
		||||
  "gemini-1.5-flash-latest",
 | 
			
		||||
  "gemini-pro-vision",
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@@ -217,7 +210,7 @@ export const CHAT_PAGE_SIZE = 15;
 | 
			
		||||
export const MAX_RENDER_MSG_COUNT = 45;
 | 
			
		||||
 | 
			
		||||
// some famous webdav endpoints
 | 
			
		||||
export const internalWhiteWebDavEndpoints = [
 | 
			
		||||
export const internalAllowedWebDavEndpoints = [
 | 
			
		||||
  "https://dav.jianguoyun.com/dav/",
 | 
			
		||||
  "https://dav.dropdav.com/",
 | 
			
		||||
  "https://dav.box.com/dav",
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ import {
 | 
			
		||||
  ModelType,
 | 
			
		||||
} from "@/app/store";
 | 
			
		||||
import Locale from "@/app/locales";
 | 
			
		||||
import { Selector, showConfirm, showToast } from "@/app/components/ui-lib";
 | 
			
		||||
import { showConfirm } from "@/app/components/ui-lib";
 | 
			
		||||
import {
 | 
			
		||||
  CHAT_PAGE_SIZE,
 | 
			
		||||
  REQUEST_TIMEOUT_MS,
 | 
			
		||||
@@ -25,7 +25,6 @@ import ChatInputPanel, {
 | 
			
		||||
  ChatInputPanelInstance,
 | 
			
		||||
} from "./components/ChatInputPanel";
 | 
			
		||||
import ChatMessagePanel, { RenderMessage } from "./components/ChatMessagePanel";
 | 
			
		||||
import { useAllModels } from "@/app/utils/hooks";
 | 
			
		||||
import useRows from "@/app/hooks/useRows";
 | 
			
		||||
import SessionConfigModel from "./components/SessionConfigModal";
 | 
			
		||||
import useScrollToBottom from "@/app/hooks/useScrollToBottom";
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
import { compressImage, isVisionModel } from "@/app/utils";
 | 
			
		||||
import { isVisionModel } from "@/app/utils";
 | 
			
		||||
import { compressImage } from "@/app/utils/chat";
 | 
			
		||||
import { useCallback, useRef } from "react";
 | 
			
		||||
import { useChatStore } from "../store/chat";
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import { compressImage } from "@/app/utils";
 | 
			
		||||
import { compressImage } from "@/app/utils/chat";
 | 
			
		||||
import { useCallback, useRef } from "react";
 | 
			
		||||
 | 
			
		||||
interface UseUploadImageOptions {
 | 
			
		||||
 
 | 
			
		||||
@@ -317,7 +317,7 @@ const en: LocaleType = {
 | 
			
		||||
 | 
			
		||||
        Endpoint: {
 | 
			
		||||
          Title: "OpenAI Endpoint",
 | 
			
		||||
          SubTitle: "Must starts with http(s):// or use /api/openai as default",
 | 
			
		||||
          SubTitle: "Must start with http(s):// or use /api/openai as default",
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      Azure: {
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,8 @@ import { estimateTokenLength } from "../utils/token";
 | 
			
		||||
import { nanoid } from "nanoid";
 | 
			
		||||
import { createPersistStore } from "../utils/store";
 | 
			
		||||
import { identifyDefaultClaudeModel } from "../utils/checkers";
 | 
			
		||||
import { collectModelsWithDefaultModel } from "../utils/model";
 | 
			
		||||
import { useAccessStore } from "./access";
 | 
			
		||||
 | 
			
		||||
export type ChatMessage = RequestMessage & {
 | 
			
		||||
  date: string;
 | 
			
		||||
@@ -104,9 +106,19 @@ function createEmptySession(): ChatSession {
 | 
			
		||||
function getSummarizeModel(currentModel: string) {
 | 
			
		||||
  // if it is using gpt-* models, force to use 3.5 to summarize
 | 
			
		||||
  if (currentModel.startsWith("gpt")) {
 | 
			
		||||
    return SUMMARIZE_MODEL;
 | 
			
		||||
    const configStore = useAppConfig.getState();
 | 
			
		||||
    const accessStore = useAccessStore.getState();
 | 
			
		||||
    const allModel = collectModelsWithDefaultModel(
 | 
			
		||||
      configStore.models,
 | 
			
		||||
      [configStore.customModels, accessStore.customModels].join(","),
 | 
			
		||||
      accessStore.defaultModel,
 | 
			
		||||
    );
 | 
			
		||||
    const summarizeModel = allModel.find(
 | 
			
		||||
      (m) => m.name === SUMMARIZE_MODEL && m.available,
 | 
			
		||||
    );
 | 
			
		||||
    return summarizeModel?.name ?? currentModel;
 | 
			
		||||
  }
 | 
			
		||||
  if (currentModel.startsWith("gemini-pro")) {
 | 
			
		||||
  if (currentModel.startsWith("gemini")) {
 | 
			
		||||
    return GEMINI_SUMMARIZE_MODEL;
 | 
			
		||||
  }
 | 
			
		||||
  return currentModel;
 | 
			
		||||
@@ -433,14 +445,13 @@ export const useChatStore = createPersistStore(
 | 
			
		||||
      getMemoryPrompt() {
 | 
			
		||||
        const session = get().currentSession();
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
          role: "system",
 | 
			
		||||
          content:
 | 
			
		||||
            session.memoryPrompt.length > 0
 | 
			
		||||
              ? Locale.Store.Prompt.History(session.memoryPrompt)
 | 
			
		||||
              : "",
 | 
			
		||||
          date: "",
 | 
			
		||||
        } as ChatMessage;
 | 
			
		||||
        if (session.memoryPrompt.length) {
 | 
			
		||||
          return {
 | 
			
		||||
            role: "system",
 | 
			
		||||
            content: Locale.Store.Prompt.History(session.memoryPrompt),
 | 
			
		||||
            date: "",
 | 
			
		||||
          } as ChatMessage;
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      getMessagesWithMemory() {
 | 
			
		||||
@@ -476,16 +487,15 @@ export const useChatStore = createPersistStore(
 | 
			
		||||
            systemPrompts.at(0)?.content ?? "empty",
 | 
			
		||||
          );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const memoryPrompt = get().getMemoryPrompt();
 | 
			
		||||
        // long term memory
 | 
			
		||||
        const shouldSendLongTermMemory =
 | 
			
		||||
          modelConfig.sendMemory &&
 | 
			
		||||
          session.memoryPrompt &&
 | 
			
		||||
          session.memoryPrompt.length > 0 &&
 | 
			
		||||
          session.lastSummarizeIndex > clearContextIndex;
 | 
			
		||||
        const longTermMemoryPrompts = shouldSendLongTermMemory
 | 
			
		||||
          ? [get().getMemoryPrompt()]
 | 
			
		||||
          : [];
 | 
			
		||||
        const longTermMemoryPrompts =
 | 
			
		||||
          shouldSendLongTermMemory && memoryPrompt ? [memoryPrompt] : [];
 | 
			
		||||
        const longTermMemoryStartIndex = session.lastSummarizeIndex;
 | 
			
		||||
 | 
			
		||||
        // short term memory
 | 
			
		||||
@@ -610,9 +620,11 @@ export const useChatStore = createPersistStore(
 | 
			
		||||
            Math.max(0, n - modelConfig.historyMessageCount),
 | 
			
		||||
          );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // add memory prompt
 | 
			
		||||
        toBeSummarizedMsgs.unshift(get().getMemoryPrompt());
 | 
			
		||||
        const memoryPrompt = get().getMemoryPrompt();
 | 
			
		||||
        if (memoryPrompt) {
 | 
			
		||||
          // add memory prompt
 | 
			
		||||
          toBeSummarizedMsgs.unshift(memoryPrompt);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const lastSummarizeIndex = session.messages.length;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -41,6 +41,7 @@ export const ThemeConfig = {
 | 
			
		||||
    title: "Dark model",
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
const config = getClientConfig();
 | 
			
		||||
 | 
			
		||||
export const DEFAULT_CONFIG = {
 | 
			
		||||
  lastUpdate: Date.now(), // timestamp, to merge state
 | 
			
		||||
@@ -49,7 +50,7 @@ export const DEFAULT_CONFIG = {
 | 
			
		||||
  avatar: "1f603",
 | 
			
		||||
  fontSize: 14,
 | 
			
		||||
  theme: Theme.Auto as Theme,
 | 
			
		||||
  tightBorder: !!getClientConfig()?.isApp,
 | 
			
		||||
  tightBorder: !!config?.isApp,
 | 
			
		||||
  sendPreviewBubble: true,
 | 
			
		||||
  enableAutoGenerateTitle: true,
 | 
			
		||||
  sidebarWidth: DEFAULT_SIDEBAR_WIDTH,
 | 
			
		||||
@@ -75,7 +76,7 @@ export const DEFAULT_CONFIG = {
 | 
			
		||||
    historyMessageCount: 4,
 | 
			
		||||
    compressMessageLengthThreshold: 1000,
 | 
			
		||||
    enableInjectSystemPrompts: true,
 | 
			
		||||
    template: DEFAULT_INPUT_TEMPLATE,
 | 
			
		||||
    template: config?.template ?? DEFAULT_INPUT_TEMPLATE,
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -151,7 +152,7 @@ export const useAppConfig = createPersistStore(
 | 
			
		||||
  }),
 | 
			
		||||
  {
 | 
			
		||||
    name: StoreKey.Config,
 | 
			
		||||
    version: 3.8,
 | 
			
		||||
    version: 3.9,
 | 
			
		||||
    migrate(persistedState, version) {
 | 
			
		||||
      const state = persistedState as ChatConfig;
 | 
			
		||||
 | 
			
		||||
@@ -182,6 +183,13 @@ export const useAppConfig = createPersistStore(
 | 
			
		||||
        state.lastUpdate = Date.now();
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (version < 3.9) {
 | 
			
		||||
        state.modelConfig.template =
 | 
			
		||||
          state.modelConfig.template !== DEFAULT_INPUT_TEMPLATE
 | 
			
		||||
            ? state.modelConfig.template
 | 
			
		||||
            : config?.template ?? DEFAULT_INPUT_TEMPLATE;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return state as any;
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
 
 | 
			
		||||
@@ -97,11 +97,20 @@ export const useSyncStore = createPersistStore(
 | 
			
		||||
      const client = this.getClient();
 | 
			
		||||
 | 
			
		||||
      try {
 | 
			
		||||
        const remoteState = JSON.parse(
 | 
			
		||||
          await client.get(config.username),
 | 
			
		||||
        ) as AppState;
 | 
			
		||||
        mergeAppState(localState, remoteState);
 | 
			
		||||
        setLocalAppState(localState);
 | 
			
		||||
        const remoteState = await client.get(config.username);
 | 
			
		||||
        if (!remoteState || remoteState === "") {
 | 
			
		||||
          await client.set(config.username, JSON.stringify(localState));
 | 
			
		||||
          console.log(
 | 
			
		||||
            "[Sync] Remote state is empty, using local state instead.",
 | 
			
		||||
          );
 | 
			
		||||
          return;
 | 
			
		||||
        } else {
 | 
			
		||||
          const parsedRemoteState = JSON.parse(
 | 
			
		||||
            await client.get(config.username),
 | 
			
		||||
          ) as AppState;
 | 
			
		||||
          mergeAppState(localState, parsedRemoteState);
 | 
			
		||||
          setLocalAppState(localState);
 | 
			
		||||
        }
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
        console.log("[Sync] failed to get remote state", e);
 | 
			
		||||
        throw e;
 | 
			
		||||
 
 | 
			
		||||
@@ -82,6 +82,7 @@
 | 
			
		||||
    @include dark;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
html {
 | 
			
		||||
  height: var(--full-height);
 | 
			
		||||
 | 
			
		||||
@@ -106,6 +107,10 @@ body {
 | 
			
		||||
  @media only screen and (max-width: 600px) {
 | 
			
		||||
    background-color: var(--second);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  *:focus-visible {
 | 
			
		||||
    outline: none;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
::-webkit-scrollbar {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										53
									
								
								app/utils.ts
									
									
									
									
									
								
							
							
						
						
									
										53
									
								
								app/utils.ts
									
									
									
									
									
								
							@@ -84,48 +84,6 @@ export async function downloadAs(text: string, filename: string) {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function compressImage(file: File, maxSize: number): Promise<string> {
 | 
			
		||||
  return new Promise((resolve, reject) => {
 | 
			
		||||
    const reader = new FileReader();
 | 
			
		||||
    reader.onload = (readerEvent: any) => {
 | 
			
		||||
      const image = new Image();
 | 
			
		||||
      image.onload = () => {
 | 
			
		||||
        let canvas = document.createElement("canvas");
 | 
			
		||||
        let ctx = canvas.getContext("2d");
 | 
			
		||||
        let width = image.width;
 | 
			
		||||
        let height = image.height;
 | 
			
		||||
        let quality = 0.9;
 | 
			
		||||
        let dataUrl;
 | 
			
		||||
 | 
			
		||||
        do {
 | 
			
		||||
          canvas.width = width;
 | 
			
		||||
          canvas.height = height;
 | 
			
		||||
          ctx?.clearRect(0, 0, canvas.width, canvas.height);
 | 
			
		||||
          ctx?.drawImage(image, 0, 0, width, height);
 | 
			
		||||
          dataUrl = canvas.toDataURL("image/jpeg", quality);
 | 
			
		||||
 | 
			
		||||
          if (dataUrl.length < maxSize) break;
 | 
			
		||||
 | 
			
		||||
          if (quality > 0.5) {
 | 
			
		||||
            // Prioritize quality reduction
 | 
			
		||||
            quality -= 0.1;
 | 
			
		||||
          } else {
 | 
			
		||||
            // Then reduce the size
 | 
			
		||||
            width *= 0.9;
 | 
			
		||||
            height *= 0.9;
 | 
			
		||||
          }
 | 
			
		||||
        } while (dataUrl.length > maxSize);
 | 
			
		||||
 | 
			
		||||
        resolve(dataUrl);
 | 
			
		||||
      };
 | 
			
		||||
      image.onerror = reject;
 | 
			
		||||
      image.src = readerEvent.target.result;
 | 
			
		||||
    };
 | 
			
		||||
    reader.onerror = reject;
 | 
			
		||||
    reader.readAsDataURL(file);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function readFromFile() {
 | 
			
		||||
  return new Promise<string>((res, rej) => {
 | 
			
		||||
    const fileInput = document.createElement("input");
 | 
			
		||||
@@ -291,18 +249,21 @@ export function getMessageImages(message: RequestMessage): string[] {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function isVisionModel(model: string) {
 | 
			
		||||
  
 | 
			
		||||
  // Note: This is a better way using the TypeScript feature instead of `&&` or `||` (ts v5.5.0-dev.20240314 I've been using)
 | 
			
		||||
 | 
			
		||||
  const visionKeywords = [
 | 
			
		||||
    "vision",
 | 
			
		||||
    "claude-3",
 | 
			
		||||
    "gemini-1.5-pro",
 | 
			
		||||
    "gemini-1.5-flash",
 | 
			
		||||
    "gpt-4o",
 | 
			
		||||
  ];
 | 
			
		||||
  const isGpt4Turbo =
 | 
			
		||||
    model.includes("gpt-4-turbo") && !model.includes("preview");
 | 
			
		||||
 | 
			
		||||
  const isGpt4Turbo = model.includes("gpt-4-turbo") && !model.includes("preview");
 | 
			
		||||
 | 
			
		||||
  return visionKeywords.some((keyword) => model.includes(keyword)) || isGpt4Turbo;
 | 
			
		||||
  return (
 | 
			
		||||
    visionKeywords.some((keyword) => model.includes(keyword)) || isGpt4Turbo
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getTime(dateTime: string) {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										54
									
								
								app/utils/chat.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								app/utils/chat.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
			
		||||
import heic2any from "heic2any";
 | 
			
		||||
 | 
			
		||||
export function compressImage(file: File, maxSize: number): Promise<string> {
 | 
			
		||||
  return new Promise((resolve, reject) => {
 | 
			
		||||
    const reader = new FileReader();
 | 
			
		||||
    reader.onload = (readerEvent: any) => {
 | 
			
		||||
      const image = new Image();
 | 
			
		||||
      image.onload = () => {
 | 
			
		||||
        let canvas = document.createElement("canvas");
 | 
			
		||||
        let ctx = canvas.getContext("2d");
 | 
			
		||||
        let width = image.width;
 | 
			
		||||
        let height = image.height;
 | 
			
		||||
        let quality = 0.9;
 | 
			
		||||
        let dataUrl;
 | 
			
		||||
 | 
			
		||||
        do {
 | 
			
		||||
          canvas.width = width;
 | 
			
		||||
          canvas.height = height;
 | 
			
		||||
          ctx?.clearRect(0, 0, canvas.width, canvas.height);
 | 
			
		||||
          ctx?.drawImage(image, 0, 0, width, height);
 | 
			
		||||
          dataUrl = canvas.toDataURL("image/jpeg", quality);
 | 
			
		||||
 | 
			
		||||
          if (dataUrl.length < maxSize) break;
 | 
			
		||||
 | 
			
		||||
          if (quality > 0.5) {
 | 
			
		||||
            // Prioritize quality reduction
 | 
			
		||||
            quality -= 0.1;
 | 
			
		||||
          } else {
 | 
			
		||||
            // Then reduce the size
 | 
			
		||||
            width *= 0.9;
 | 
			
		||||
            height *= 0.9;
 | 
			
		||||
          }
 | 
			
		||||
        } while (dataUrl.length > maxSize);
 | 
			
		||||
 | 
			
		||||
        resolve(dataUrl);
 | 
			
		||||
      };
 | 
			
		||||
      image.onerror = reject;
 | 
			
		||||
      image.src = readerEvent.target.result;
 | 
			
		||||
    };
 | 
			
		||||
    reader.onerror = reject;
 | 
			
		||||
 | 
			
		||||
    if (file.type.includes("heic")) {
 | 
			
		||||
      heic2any({ blob: file, toType: "image/jpeg" })
 | 
			
		||||
        .then((blob) => {
 | 
			
		||||
          reader.readAsDataURL(blob as Blob);
 | 
			
		||||
        })
 | 
			
		||||
        .catch((e) => {
 | 
			
		||||
          reject(e);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    reader.readAsDataURL(file);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
@@ -93,14 +93,17 @@ export function createUpstashClient(store: SyncStore) {
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      let url;
 | 
			
		||||
      if (proxyUrl.length > 0 || proxyUrl === "/") {
 | 
			
		||||
        let u = new URL(proxyUrl + "/api/upstash/" + path);
 | 
			
		||||
      const pathPrefix = "/api/upstash/";
 | 
			
		||||
 | 
			
		||||
      try {
 | 
			
		||||
        let u = new URL(proxyUrl + pathPrefix + path);
 | 
			
		||||
        // add query params
 | 
			
		||||
        u.searchParams.append("endpoint", config.endpoint);
 | 
			
		||||
        url = u.toString();
 | 
			
		||||
      } else {
 | 
			
		||||
        url = "/api/upstash/" + path + "?endpoint=" + config.endpoint;
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
        url = pathPrefix + path + "?endpoint=" + config.endpoint;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return url;
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
 
 | 
			
		||||
@@ -64,13 +64,10 @@ export function collectModelTableWithDefaultModel(
 | 
			
		||||
) {
 | 
			
		||||
  let modelTable = collectModelTable(models, customModels);
 | 
			
		||||
  if (defaultModel && defaultModel !== "") {
 | 
			
		||||
    delete modelTable[defaultModel];
 | 
			
		||||
    modelTable[defaultModel] = {
 | 
			
		||||
      ...modelTable[defaultModel],
 | 
			
		||||
      name: defaultModel,
 | 
			
		||||
      displayName: defaultModel,
 | 
			
		||||
      available: true,
 | 
			
		||||
      provider:
 | 
			
		||||
        modelTable[defaultModel]?.provider ?? customProvider(defaultModel),
 | 
			
		||||
      isDefault: true,
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -29,12 +29,13 @@
 | 
			
		||||
    "dayjs": "^1.11.10",
 | 
			
		||||
    "emoji-picker-react": "^4.9.2",
 | 
			
		||||
    "fuse.js": "^7.0.0",
 | 
			
		||||
    "heic2any": "^0.0.4",
 | 
			
		||||
    "html-to-image": "^1.11.11",
 | 
			
		||||
    "install": "^0.13.0",
 | 
			
		||||
    "lodash-es": "^4.17.21",
 | 
			
		||||
    "mermaid": "^10.6.1",
 | 
			
		||||
    "nanoid": "^5.0.3",
 | 
			
		||||
    "next": "^13.4.9",
 | 
			
		||||
    "next": "^14.1.1",
 | 
			
		||||
    "node-fetch": "^3.3.1",
 | 
			
		||||
    "react": "^18.2.0",
 | 
			
		||||
    "react-dom": "^18.2.0",
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
  },
 | 
			
		||||
  "package": {
 | 
			
		||||
    "productName": "NextChat",
 | 
			
		||||
    "version": "2.11.3"
 | 
			
		||||
    "version": "2.12.3"
 | 
			
		||||
  },
 | 
			
		||||
  "tauri": {
 | 
			
		||||
    "allowlist": {
 | 
			
		||||
@@ -116,4 +116,4 @@
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										165
									
								
								yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										165
									
								
								yarn.lock
									
									
									
									
									
								
							@@ -1269,10 +1269,10 @@
 | 
			
		||||
    "@jridgewell/resolve-uri" "3.1.0"
 | 
			
		||||
    "@jridgewell/sourcemap-codec" "1.4.14"
 | 
			
		||||
 | 
			
		||||
"@next/env@13.4.9":
 | 
			
		||||
  version "13.4.9"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@next/env/-/env-13.4.9.tgz#b77759514dd56bfa9791770755a2482f4d6ca93e"
 | 
			
		||||
  integrity sha512-vuDRK05BOKfmoBYLNi2cujG2jrYbEod/ubSSyqgmEx9n/W3eZaJQdRNhTfumO+qmq/QTzLurW487n/PM/fHOkw==
 | 
			
		||||
"@next/env@14.1.1":
 | 
			
		||||
  version "14.1.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@next/env/-/env-14.1.1.tgz#80150a8440eb0022a73ba353c6088d419b908bac"
 | 
			
		||||
  integrity sha512-7CnQyD5G8shHxQIIg3c7/pSeYFeMhsNbpU/bmvH7ZnDql7mNRgg8O2JZrhrc/soFnfBnKP4/xXNiiSIPn2w8gA==
 | 
			
		||||
 | 
			
		||||
"@next/eslint-plugin-next@13.4.19":
 | 
			
		||||
  version "13.4.19"
 | 
			
		||||
@@ -1281,50 +1281,50 @@
 | 
			
		||||
  dependencies:
 | 
			
		||||
    glob "7.1.7"
 | 
			
		||||
 | 
			
		||||
"@next/swc-darwin-arm64@13.4.9":
 | 
			
		||||
  version "13.4.9"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.9.tgz#0ed408d444bbc6b0a20f3506a9b4222684585677"
 | 
			
		||||
  integrity sha512-TVzGHpZoVBk3iDsTOQA/R6MGmFp0+17SWXMEWd6zG30AfuELmSSMe2SdPqxwXU0gbpWkJL1KgfLzy5ReN0crqQ==
 | 
			
		||||
"@next/swc-darwin-arm64@14.1.1":
 | 
			
		||||
  version "14.1.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.1.1.tgz#b74ba7c14af7d05fa2848bdeb8ee87716c939b64"
 | 
			
		||||
  integrity sha512-yDjSFKQKTIjyT7cFv+DqQfW5jsD+tVxXTckSe1KIouKk75t1qZmj/mV3wzdmFb0XHVGtyRjDMulfVG8uCKemOQ==
 | 
			
		||||
 | 
			
		||||
"@next/swc-darwin-x64@13.4.9":
 | 
			
		||||
  version "13.4.9"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.9.tgz#a08fccdee68201522fe6618ec81f832084b222f8"
 | 
			
		||||
  integrity sha512-aSfF1fhv28N2e7vrDZ6zOQ+IIthocfaxuMWGReB5GDriF0caTqtHttAvzOMgJgXQtQx6XhyaJMozLTSEXeNN+A==
 | 
			
		||||
"@next/swc-darwin-x64@14.1.1":
 | 
			
		||||
  version "14.1.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.1.1.tgz#82c3e67775e40094c66e76845d1a36cc29c9e78b"
 | 
			
		||||
  integrity sha512-KCQmBL0CmFmN8D64FHIZVD9I4ugQsDBBEJKiblXGgwn7wBCSe8N4Dx47sdzl4JAg39IkSN5NNrr8AniXLMb3aw==
 | 
			
		||||
 | 
			
		||||
"@next/swc-linux-arm64-gnu@13.4.9":
 | 
			
		||||
  version "13.4.9"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.9.tgz#1798c2341bb841e96521433eed00892fb24abbd1"
 | 
			
		||||
  integrity sha512-JhKoX5ECzYoTVyIy/7KykeO4Z2lVKq7HGQqvAH+Ip9UFn1MOJkOnkPRB7v4nmzqAoY+Je05Aj5wNABR1N18DMg==
 | 
			
		||||
"@next/swc-linux-arm64-gnu@14.1.1":
 | 
			
		||||
  version "14.1.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.1.1.tgz#4f4134457b90adc5c3d167d07dfb713c632c0caa"
 | 
			
		||||
  integrity sha512-YDQfbWyW0JMKhJf/T4eyFr4b3tceTorQ5w2n7I0mNVTFOvu6CGEzfwT3RSAQGTi/FFMTFcuspPec/7dFHuP7Eg==
 | 
			
		||||
 | 
			
		||||
"@next/swc-linux-arm64-musl@13.4.9":
 | 
			
		||||
  version "13.4.9"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.9.tgz#cee04c51610eddd3638ce2499205083656531ea0"
 | 
			
		||||
  integrity sha512-OOn6zZBIVkm/4j5gkPdGn4yqQt+gmXaLaSjRSO434WplV8vo2YaBNbSHaTM9wJpZTHVDYyjzuIYVEzy9/5RVZw==
 | 
			
		||||
"@next/swc-linux-arm64-musl@14.1.1":
 | 
			
		||||
  version "14.1.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.1.1.tgz#594bedafaeba4a56db23a48ffed2cef7cd09c31a"
 | 
			
		||||
  integrity sha512-fiuN/OG6sNGRN/bRFxRvV5LyzLB8gaL8cbDH5o3mEiVwfcMzyE5T//ilMmaTrnA8HLMS6hoz4cHOu6Qcp9vxgQ==
 | 
			
		||||
 | 
			
		||||
"@next/swc-linux-x64-gnu@13.4.9":
 | 
			
		||||
  version "13.4.9"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.9.tgz#1932d0367916adbc6844b244cda1d4182bd11f7a"
 | 
			
		||||
  integrity sha512-iA+fJXFPpW0SwGmx/pivVU+2t4zQHNOOAr5T378PfxPHY6JtjV6/0s1vlAJUdIHeVpX98CLp9k5VuKgxiRHUpg==
 | 
			
		||||
"@next/swc-linux-x64-gnu@14.1.1":
 | 
			
		||||
  version "14.1.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.1.1.tgz#cb4e75f1ff2b9bcadf2a50684605928ddfc58528"
 | 
			
		||||
  integrity sha512-rv6AAdEXoezjbdfp3ouMuVqeLjE1Bin0AuE6qxE6V9g3Giz5/R3xpocHoAi7CufRR+lnkuUjRBn05SYJ83oKNQ==
 | 
			
		||||
 | 
			
		||||
"@next/swc-linux-x64-musl@13.4.9":
 | 
			
		||||
  version "13.4.9"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.9.tgz#a66aa8c1383b16299b72482f6360facd5cde3c7a"
 | 
			
		||||
  integrity sha512-rlNf2WUtMM+GAQrZ9gMNdSapkVi3koSW3a+dmBVp42lfugWVvnyzca/xJlN48/7AGx8qu62WyO0ya1ikgOxh6A==
 | 
			
		||||
"@next/swc-linux-x64-musl@14.1.1":
 | 
			
		||||
  version "14.1.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.1.1.tgz#15f26800df941b94d06327f674819ab64b272e25"
 | 
			
		||||
  integrity sha512-YAZLGsaNeChSrpz/G7MxO3TIBLaMN8QWMr3X8bt6rCvKovwU7GqQlDu99WdvF33kI8ZahvcdbFsy4jAFzFX7og==
 | 
			
		||||
 | 
			
		||||
"@next/swc-win32-arm64-msvc@13.4.9":
 | 
			
		||||
  version "13.4.9"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.9.tgz#39482ee856c867177a612a30b6861c75e0736a4a"
 | 
			
		||||
  integrity sha512-5T9ybSugXP77nw03vlgKZxD99AFTHaX8eT1ayKYYnGO9nmYhJjRPxcjU5FyYI+TdkQgEpIcH7p/guPLPR0EbKA==
 | 
			
		||||
"@next/swc-win32-arm64-msvc@14.1.1":
 | 
			
		||||
  version "14.1.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.1.1.tgz#060c134fa7fa843666e3e8574972b2b723773dd9"
 | 
			
		||||
  integrity sha512-1L4mUYPBMvVDMZg1inUYyPvFSduot0g73hgfD9CODgbr4xiTYe0VOMTZzaRqYJYBA9mana0x4eaAaypmWo1r5A==
 | 
			
		||||
 | 
			
		||||
"@next/swc-win32-ia32-msvc@13.4.9":
 | 
			
		||||
  version "13.4.9"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.9.tgz#29db85e34b597ade1a918235d16a760a9213c190"
 | 
			
		||||
  integrity sha512-ojZTCt1lP2ucgpoiFgrFj07uq4CZsq4crVXpLGgQfoFq00jPKRPgesuGPaz8lg1yLfvafkU3Jd1i8snKwYR3LA==
 | 
			
		||||
"@next/swc-win32-ia32-msvc@14.1.1":
 | 
			
		||||
  version "14.1.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.1.1.tgz#5c06889352b1f77e3807834a0d0afd7e2d2d1da2"
 | 
			
		||||
  integrity sha512-jvIE9tsuj9vpbbXlR5YxrghRfMuG0Qm/nZ/1KDHc+y6FpnZ/apsgh+G6t15vefU0zp3WSpTMIdXRUsNl/7RSuw==
 | 
			
		||||
 | 
			
		||||
"@next/swc-win32-x64-msvc@13.4.9":
 | 
			
		||||
  version "13.4.9"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.9.tgz#0c2758164cccd61bc5a1c6cd8284fe66173e4a2b"
 | 
			
		||||
  integrity sha512-QbT03FXRNdpuL+e9pLnu+XajZdm/TtIXVYY4lA9t+9l0fLZbHXDYEKitAqxrOj37o3Vx5ufxiRAniaIebYDCgw==
 | 
			
		||||
"@next/swc-win32-x64-msvc@14.1.1":
 | 
			
		||||
  version "14.1.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.1.1.tgz#d38c63a8f9b7f36c1470872797d3735b4a9c5c52"
 | 
			
		||||
  integrity sha512-S6K6EHDU5+1KrBDLko7/c1MNy/Ya73pIAmvKeFwsF4RmBFJSO7/7YeD4FnZ4iBdzE69PpQ4sOMU9ORKeNuxe8A==
 | 
			
		||||
 | 
			
		||||
"@next/third-parties@^14.1.0":
 | 
			
		||||
  version "14.1.0"
 | 
			
		||||
@@ -1720,10 +1720,10 @@
 | 
			
		||||
    "@svgr/plugin-jsx" "^6.5.1"
 | 
			
		||||
    "@svgr/plugin-svgo" "^6.5.1"
 | 
			
		||||
 | 
			
		||||
"@swc/helpers@0.5.1":
 | 
			
		||||
  version "0.5.1"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/@swc/helpers/-/helpers-0.5.1.tgz#e9031491aa3f26bfcc974a67f48bd456c8a5357a"
 | 
			
		||||
  integrity sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==
 | 
			
		||||
"@swc/helpers@0.5.2":
 | 
			
		||||
  version "0.5.2"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.2.tgz#85ea0c76450b61ad7d10a37050289eded783c27d"
 | 
			
		||||
  integrity sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    tslib "^2.4.0"
 | 
			
		||||
 | 
			
		||||
@@ -2494,10 +2494,10 @@ camelcase@^6.2.0:
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
 | 
			
		||||
  integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
 | 
			
		||||
 | 
			
		||||
caniuse-lite@^1.0.30001406, caniuse-lite@^1.0.30001449, caniuse-lite@^1.0.30001503:
 | 
			
		||||
  version "1.0.30001509"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001509.tgz#2b7ad5265392d6d2de25cd8776d1ab3899570d14"
 | 
			
		||||
  integrity sha512-2uDDk+TRiTX5hMcUYT/7CSyzMZxjfGu0vAUjS2g0LSD8UoXOv0LtpH4LxGMemsiPq6LCVIUjNwVM0erkOkGCDA==
 | 
			
		||||
caniuse-lite@^1.0.30001449, caniuse-lite@^1.0.30001503, caniuse-lite@^1.0.30001579:
 | 
			
		||||
  version "1.0.30001617"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001617.tgz#809bc25f3f5027ceb33142a7d6c40759d7a901eb"
 | 
			
		||||
  integrity sha512-mLyjzNI9I+Pix8zwcrpxEbGlfqOkF9kM3ptzmKNw5tizSyYwMe+nGLTqMK9cO+0E+Bh6TsBxNAaHWEM8xwSsmA==
 | 
			
		||||
 | 
			
		||||
caniuse-lite@^1.0.30001587, caniuse-lite@^1.0.30001599:
 | 
			
		||||
  version "1.0.30001608"
 | 
			
		||||
@@ -3999,7 +3999,7 @@ gopd@^1.0.1:
 | 
			
		||||
  dependencies:
 | 
			
		||||
    get-intrinsic "^1.1.3"
 | 
			
		||||
 | 
			
		||||
graceful-fs@^4.1.2, graceful-fs@^4.2.4, graceful-fs@^4.2.9:
 | 
			
		||||
graceful-fs@^4.1.2, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.9:
 | 
			
		||||
  version "4.2.11"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
 | 
			
		||||
  integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
 | 
			
		||||
@@ -4150,6 +4150,11 @@ heap@^0.2.6:
 | 
			
		||||
  resolved "https://registry.npmmirror.com/heap/-/heap-0.2.7.tgz#1e6adf711d3f27ce35a81fe3b7bd576c2260a8fc"
 | 
			
		||||
  integrity sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==
 | 
			
		||||
 | 
			
		||||
heic2any@^0.0.4:
 | 
			
		||||
  version "0.0.4"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/heic2any/-/heic2any-0.0.4.tgz#eddb8e6fec53c8583a6e18b65069bb5e8d19028a"
 | 
			
		||||
  integrity sha512-3lLnZiDELfabVH87htnRolZ2iehX9zwpRyGNz22GKXIu0fznlblf0/ftppXKNqS26dqFSeqfIBhAmAj/uSp0cA==
 | 
			
		||||
 | 
			
		||||
highlight.js@~11.7.0:
 | 
			
		||||
  version "11.7.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.7.0.tgz#3ff0165bc843f8c9bce1fd89e2fda9143d24b11e"
 | 
			
		||||
@@ -5307,10 +5312,10 @@ mz@^2.7.0:
 | 
			
		||||
    object-assign "^4.0.1"
 | 
			
		||||
    thenify-all "^1.0.0"
 | 
			
		||||
 | 
			
		||||
nanoid@^3.3.4:
 | 
			
		||||
  version "3.3.6"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
 | 
			
		||||
  integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
 | 
			
		||||
nanoid@^3.3.6:
 | 
			
		||||
  version "3.3.7"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
 | 
			
		||||
  integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
 | 
			
		||||
 | 
			
		||||
nanoid@^3.3.7:
 | 
			
		||||
  version "3.3.7"
 | 
			
		||||
@@ -5332,29 +5337,28 @@ neo-async@^2.6.2:
 | 
			
		||||
  resolved "https://registry.npmmirror.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
 | 
			
		||||
  integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
 | 
			
		||||
 | 
			
		||||
next@^13.4.9:
 | 
			
		||||
  version "13.4.9"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/next/-/next-13.4.9.tgz#473de5997cb4c5d7a4fb195f566952a1cbffbeba"
 | 
			
		||||
  integrity sha512-vtefFm/BWIi/eWOqf1GsmKG3cjKw1k3LjuefKRcL3iiLl3zWzFdPG3as6xtxrGO6gwTzzaO1ktL4oiHt/uvTjA==
 | 
			
		||||
next@^14.1.1:
 | 
			
		||||
  version "14.1.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/next/-/next-14.1.1.tgz#92bd603996c050422a738e90362dff758459a171"
 | 
			
		||||
  integrity sha512-McrGJqlGSHeaz2yTRPkEucxQKe5Zq7uPwyeHNmJaZNY4wx9E9QdxmTp310agFRoMuIYgQrCrT3petg13fSVOww==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@next/env" "13.4.9"
 | 
			
		||||
    "@swc/helpers" "0.5.1"
 | 
			
		||||
    "@next/env" "14.1.1"
 | 
			
		||||
    "@swc/helpers" "0.5.2"
 | 
			
		||||
    busboy "1.6.0"
 | 
			
		||||
    caniuse-lite "^1.0.30001406"
 | 
			
		||||
    postcss "8.4.14"
 | 
			
		||||
    caniuse-lite "^1.0.30001579"
 | 
			
		||||
    graceful-fs "^4.2.11"
 | 
			
		||||
    postcss "8.4.31"
 | 
			
		||||
    styled-jsx "5.1.1"
 | 
			
		||||
    watchpack "2.4.0"
 | 
			
		||||
    zod "3.21.4"
 | 
			
		||||
  optionalDependencies:
 | 
			
		||||
    "@next/swc-darwin-arm64" "13.4.9"
 | 
			
		||||
    "@next/swc-darwin-x64" "13.4.9"
 | 
			
		||||
    "@next/swc-linux-arm64-gnu" "13.4.9"
 | 
			
		||||
    "@next/swc-linux-arm64-musl" "13.4.9"
 | 
			
		||||
    "@next/swc-linux-x64-gnu" "13.4.9"
 | 
			
		||||
    "@next/swc-linux-x64-musl" "13.4.9"
 | 
			
		||||
    "@next/swc-win32-arm64-msvc" "13.4.9"
 | 
			
		||||
    "@next/swc-win32-ia32-msvc" "13.4.9"
 | 
			
		||||
    "@next/swc-win32-x64-msvc" "13.4.9"
 | 
			
		||||
    "@next/swc-darwin-arm64" "14.1.1"
 | 
			
		||||
    "@next/swc-darwin-x64" "14.1.1"
 | 
			
		||||
    "@next/swc-linux-arm64-gnu" "14.1.1"
 | 
			
		||||
    "@next/swc-linux-arm64-musl" "14.1.1"
 | 
			
		||||
    "@next/swc-linux-x64-gnu" "14.1.1"
 | 
			
		||||
    "@next/swc-linux-x64-musl" "14.1.1"
 | 
			
		||||
    "@next/swc-win32-arm64-msvc" "14.1.1"
 | 
			
		||||
    "@next/swc-win32-ia32-msvc" "14.1.1"
 | 
			
		||||
    "@next/swc-win32-x64-msvc" "14.1.1"
 | 
			
		||||
 | 
			
		||||
node-domexception@^1.0.0:
 | 
			
		||||
  version "1.0.0"
 | 
			
		||||
@@ -5672,12 +5676,12 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0:
 | 
			
		||||
  resolved "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
 | 
			
		||||
  integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
 | 
			
		||||
 | 
			
		||||
postcss@8.4.14:
 | 
			
		||||
  version "8.4.14"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.14.tgz#ee9274d5622b4858c1007a74d76e42e56fd21caf"
 | 
			
		||||
  integrity sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==
 | 
			
		||||
postcss@8.4.31:
 | 
			
		||||
  version "8.4.31"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d"
 | 
			
		||||
  integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    nanoid "^3.3.4"
 | 
			
		||||
    nanoid "^3.3.6"
 | 
			
		||||
    picocolors "^1.0.0"
 | 
			
		||||
    source-map-js "^1.0.2"
 | 
			
		||||
 | 
			
		||||
@@ -6858,7 +6862,7 @@ vfile@^5.0.0:
 | 
			
		||||
    unist-util-stringify-position "^3.0.0"
 | 
			
		||||
    vfile-message "^3.0.0"
 | 
			
		||||
 | 
			
		||||
watchpack@2.4.0, watchpack@^2.4.0:
 | 
			
		||||
watchpack@^2.4.0:
 | 
			
		||||
  version "2.4.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"
 | 
			
		||||
  integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==
 | 
			
		||||
@@ -7027,11 +7031,6 @@ yocto-queue@^0.1.0:
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
 | 
			
		||||
  integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
 | 
			
		||||
 | 
			
		||||
zod@3.21.4:
 | 
			
		||||
  version "3.21.4"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/zod/-/zod-3.21.4.tgz#10882231d992519f0a10b5dd58a38c9dabbb64db"
 | 
			
		||||
  integrity sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==
 | 
			
		||||
 | 
			
		||||
zustand@^4.3.8:
 | 
			
		||||
  version "4.3.8"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.3.8.tgz#37113df8e9e1421b0be1b2dca02b49b76210e7c4"
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user