mirror of
				https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
				synced 2025-11-04 08:13:43 +08:00 
			
		
		
		
	Compare commits
	
		
			18 Commits
		
	
	
		
			Leizhenpen
			...
			ae39778786
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					ae39778786 | ||
| 
						 | 
					b6f5d75656 | ||
| 
						 | 
					0d41a17ef6 | ||
| 
						 | 
					f7cde17919 | ||
| 
						 | 
					570cbb34b6 | ||
| 
						 | 
					7aa9ae0a3e | ||
| 
						 | 
					2d4180f5be | ||
| 
						 | 
					9f0182b55e | ||
| 
						 | 
					ad6666eeaf | ||
| 
						 | 
					a2c4e468a0 | ||
| 
						 | 
					2167076652 | ||
| 
						 | 
					e123076250 | ||
| 
						 | 
					ebcb4db245 | ||
| 
						 | 
					0a25a1a8cb | ||
| 
						 | 
					f3154b20a5 | ||
| 
						 | 
					b709ee3983 | ||
| 
						 | 
					4c0daf40c0 | ||
| 
						 | 
					f5f3ce94f6 | 
@@ -22,7 +22,6 @@ English / [简体中文](./README_CN.md)
 | 
			
		||||
[![MacOS][MacOS-image]][download-url]
 | 
			
		||||
[![Linux][Linux-image]][download-url]
 | 
			
		||||
 | 
			
		||||
[NextChatAI](https://nextchat.dev/chat?utm_source=readme) / [Web App Demo](https://app.nextchat.dev) / [Desktop App](https://github.com/Yidadaa/ChatGPT-Next-Web/releases) 
 | 
			
		||||
[NextChatAI](https://nextchat.club?utm_source=readme) / [Web App Demo](https://app.nextchat.dev) / [Desktop App](https://github.com/Yidadaa/ChatGPT-Next-Web/releases) / [Discord](https://discord.gg/YCkeafCafC) / [Enterprise Edition](#enterprise-edition) / [Twitter](https://twitter.com/NextChatDev)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -56,7 +55,7 @@ Will you come and do something together? 😎
 | 
			
		||||
 | 
			
		||||
https://github.com/ChatGPTNextWeb/NextChat/issues/6269
 | 
			
		||||
 | 
			
		||||
#Seeking for talents is thirsty #lack of people"
 | 
			
		||||
#Seeking for talents is thirsty #lack of people
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 🥳 Cheer for DeepSeek, China's AI star!
 | 
			
		||||
 
 | 
			
		||||
@@ -40,6 +40,11 @@ export interface MultimodalContent {
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface MultimodalContentForAlibaba {
 | 
			
		||||
  text?: string;
 | 
			
		||||
  image?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface RequestMessage {
 | 
			
		||||
  role: MessageRole;
 | 
			
		||||
  content: string | MultimodalContent[];
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,10 @@ import {
 | 
			
		||||
  ChatMessageTool,
 | 
			
		||||
  usePluginStore,
 | 
			
		||||
} from "@/app/store";
 | 
			
		||||
import { streamWithThink } from "@/app/utils/chat";
 | 
			
		||||
import {
 | 
			
		||||
  preProcessImageContentForAlibabaDashScope,
 | 
			
		||||
  streamWithThink,
 | 
			
		||||
} from "@/app/utils/chat";
 | 
			
		||||
import {
 | 
			
		||||
  ChatOptions,
 | 
			
		||||
  getHeaders,
 | 
			
		||||
@@ -15,12 +18,14 @@ import {
 | 
			
		||||
  LLMModel,
 | 
			
		||||
  SpeechOptions,
 | 
			
		||||
  MultimodalContent,
 | 
			
		||||
  MultimodalContentForAlibaba,
 | 
			
		||||
} from "../api";
 | 
			
		||||
import { getClientConfig } from "@/app/config/client";
 | 
			
		||||
import {
 | 
			
		||||
  getMessageTextContent,
 | 
			
		||||
  getMessageTextContentWithoutThinking,
 | 
			
		||||
  getTimeoutMSByModel,
 | 
			
		||||
  isVisionModel,
 | 
			
		||||
} from "@/app/utils";
 | 
			
		||||
import { fetch } from "@/app/utils/stream";
 | 
			
		||||
 | 
			
		||||
@@ -89,14 +94,6 @@ export class QwenApi implements LLMApi {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async chat(options: ChatOptions) {
 | 
			
		||||
    const messages = options.messages.map((v) => ({
 | 
			
		||||
      role: v.role,
 | 
			
		||||
      content:
 | 
			
		||||
        v.role === "assistant"
 | 
			
		||||
          ? getMessageTextContentWithoutThinking(v)
 | 
			
		||||
          : getMessageTextContent(v),
 | 
			
		||||
    }));
 | 
			
		||||
 | 
			
		||||
    const modelConfig = {
 | 
			
		||||
      ...useAppConfig.getState().modelConfig,
 | 
			
		||||
      ...useChatStore.getState().currentSession().mask.modelConfig,
 | 
			
		||||
@@ -105,6 +102,21 @@ export class QwenApi implements LLMApi {
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const visionModel = isVisionModel(options.config.model);
 | 
			
		||||
 | 
			
		||||
    const messages: ChatOptions["messages"] = [];
 | 
			
		||||
    for (const v of options.messages) {
 | 
			
		||||
      const content = (
 | 
			
		||||
        visionModel
 | 
			
		||||
          ? await preProcessImageContentForAlibabaDashScope(v.content)
 | 
			
		||||
          : v.role === "assistant"
 | 
			
		||||
          ? getMessageTextContentWithoutThinking(v)
 | 
			
		||||
          : getMessageTextContent(v)
 | 
			
		||||
      ) as any;
 | 
			
		||||
 | 
			
		||||
      messages.push({ role: v.role, content });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const shouldStream = !!options.config.stream;
 | 
			
		||||
    const requestPayload: RequestPayload = {
 | 
			
		||||
      model: modelConfig.model,
 | 
			
		||||
@@ -129,7 +141,7 @@ export class QwenApi implements LLMApi {
 | 
			
		||||
        "X-DashScope-SSE": shouldStream ? "enable" : "disable",
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      const chatPath = this.path(Alibaba.ChatPath);
 | 
			
		||||
      const chatPath = this.path(Alibaba.ChatPath(modelConfig.model));
 | 
			
		||||
      const chatPayload = {
 | 
			
		||||
        method: "POST",
 | 
			
		||||
        body: JSON.stringify(requestPayload),
 | 
			
		||||
@@ -162,7 +174,7 @@ export class QwenApi implements LLMApi {
 | 
			
		||||
            const json = JSON.parse(text);
 | 
			
		||||
            const choices = json.output.choices as Array<{
 | 
			
		||||
              message: {
 | 
			
		||||
                content: string | null;
 | 
			
		||||
                content: string | null | MultimodalContentForAlibaba[];
 | 
			
		||||
                tool_calls: ChatMessageTool[];
 | 
			
		||||
                reasoning_content: string | null;
 | 
			
		||||
              };
 | 
			
		||||
@@ -212,7 +224,9 @@ export class QwenApi implements LLMApi {
 | 
			
		||||
            } else if (content && content.length > 0) {
 | 
			
		||||
              return {
 | 
			
		||||
                isThinking: false,
 | 
			
		||||
                content: content,
 | 
			
		||||
                content: Array.isArray(content)
 | 
			
		||||
                  ? content.map((item) => item.text).join(",")
 | 
			
		||||
                  : content,
 | 
			
		||||
              };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -75,6 +75,25 @@ export class DeepSeekApi implements LLMApi {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 检测并修复消息顺序,确保除system外的第一个消息是user
 | 
			
		||||
    const filteredMessages: ChatOptions["messages"] = [];
 | 
			
		||||
    let hasFoundFirstUser = false;
 | 
			
		||||
 | 
			
		||||
    for (const msg of messages) {
 | 
			
		||||
      if (msg.role === "system") {
 | 
			
		||||
        // Keep all system messages
 | 
			
		||||
        filteredMessages.push(msg);
 | 
			
		||||
      } else if (msg.role === "user") {
 | 
			
		||||
        // User message directly added
 | 
			
		||||
        filteredMessages.push(msg);
 | 
			
		||||
        hasFoundFirstUser = true;
 | 
			
		||||
      } else if (hasFoundFirstUser) {
 | 
			
		||||
        // After finding the first user message, all subsequent non-system messages are retained.
 | 
			
		||||
        filteredMessages.push(msg);
 | 
			
		||||
      }
 | 
			
		||||
      // If hasFoundFirstUser is false and it is not a system message, it will be skipped.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const modelConfig = {
 | 
			
		||||
      ...useAppConfig.getState().modelConfig,
 | 
			
		||||
      ...useChatStore.getState().currentSession().mask.modelConfig,
 | 
			
		||||
@@ -85,7 +104,7 @@ export class DeepSeekApi implements LLMApi {
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const requestPayload: RequestPayload = {
 | 
			
		||||
      messages,
 | 
			
		||||
      messages: filteredMessages,
 | 
			
		||||
      stream: options.config.stream,
 | 
			
		||||
      model: modelConfig.model,
 | 
			
		||||
      temperature: modelConfig.temperature,
 | 
			
		||||
 
 | 
			
		||||
@@ -66,11 +66,11 @@ export function Avatar(props: { model?: ModelType; avatar?: string }) {
 | 
			
		||||
      LlmIcon = BotIconGemma;
 | 
			
		||||
    } else if (modelName.startsWith("claude")) {
 | 
			
		||||
      LlmIcon = BotIconClaude;
 | 
			
		||||
    } else if (modelName.toLowerCase().includes("llama")) {
 | 
			
		||||
    } else if (modelName.includes("llama")) {
 | 
			
		||||
      LlmIcon = BotIconMeta;
 | 
			
		||||
    } else if (modelName.startsWith("mixtral")) {
 | 
			
		||||
    } else if (modelName.startsWith("mixtral") || modelName.startsWith("codestral")) {
 | 
			
		||||
      LlmIcon = BotIconMistral;
 | 
			
		||||
    } else if (modelName.toLowerCase().includes("deepseek")) {
 | 
			
		||||
    } else if (modelName.includes("deepseek")) {
 | 
			
		||||
      LlmIcon = BotIconDeepseek;
 | 
			
		||||
    } else if (modelName.startsWith("moonshot")) {
 | 
			
		||||
      LlmIcon = BotIconMoonshot;
 | 
			
		||||
@@ -85,7 +85,7 @@ export function Avatar(props: { model?: ModelType; avatar?: string }) {
 | 
			
		||||
    } else if (modelName.startsWith("doubao") || modelName.startsWith("ep-")) {
 | 
			
		||||
      LlmIcon = BotIconDoubao;
 | 
			
		||||
    } else if (
 | 
			
		||||
      modelName.toLowerCase().includes("glm") ||
 | 
			
		||||
      modelName.includes("glm") ||
 | 
			
		||||
      modelName.startsWith("cogview-") ||
 | 
			
		||||
      modelName.startsWith("cogvideox-")
 | 
			
		||||
    ) {
 | 
			
		||||
 
 | 
			
		||||
@@ -52,13 +52,10 @@
 | 
			
		||||
  .sidebar-header-bar {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    margin-bottom: 20px;
 | 
			
		||||
    gap: 10px;
 | 
			
		||||
 | 
			
		||||
    .sidebar-bar-button {
 | 
			
		||||
      flex-grow: 1;
 | 
			
		||||
 | 
			
		||||
      &:not(:last-child) {
 | 
			
		||||
        margin-right: 10px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -252,13 +249,6 @@
 | 
			
		||||
 | 
			
		||||
  .sidebar-header-bar {
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
 | 
			
		||||
    .sidebar-bar-button {
 | 
			
		||||
      &:not(:last-child) {
 | 
			
		||||
        margin-right: 0;
 | 
			
		||||
        margin-bottom: 10px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .chat-item {
 | 
			
		||||
@@ -308,15 +298,10 @@
 | 
			
		||||
  .sidebar-tail {
 | 
			
		||||
    flex-direction: column-reverse;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    gap: 15px;
 | 
			
		||||
 | 
			
		||||
    .sidebar-actions {
 | 
			
		||||
      flex-direction: column-reverse;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
 | 
			
		||||
      .sidebar-action {
 | 
			
		||||
        margin-right: 0;
 | 
			
		||||
        margin-top: 15px;
 | 
			
		||||
      }
 | 
			
		||||
      display: contents;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -329,10 +314,7 @@
 | 
			
		||||
 | 
			
		||||
.sidebar-actions {
 | 
			
		||||
  display: inline-flex;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.sidebar-action:not(:last-child) {
 | 
			
		||||
  margin-right: 15px;
 | 
			
		||||
  gap: 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.loading-content {
 | 
			
		||||
@@ -346,4 +328,9 @@
 | 
			
		||||
 | 
			
		||||
.rtl-screen {
 | 
			
		||||
  direction: rtl;
 | 
			
		||||
 | 
			
		||||
  .sidebar-drag {
 | 
			
		||||
    right: unset;
 | 
			
		||||
    left: 0;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@ import McpIcon from "../icons/mcp.svg";
 | 
			
		||||
import DragIcon from "../icons/drag.svg";
 | 
			
		||||
import DiscoveryIcon from "../icons/discovery.svg";
 | 
			
		||||
 | 
			
		||||
import Locale from "../locales";
 | 
			
		||||
import Locale, { getLang } from "../locales";
 | 
			
		||||
 | 
			
		||||
import { useAppConfig, useChatStore } from "../store";
 | 
			
		||||
 | 
			
		||||
@@ -91,7 +91,11 @@ export function useDragSideBar() {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      lastUpdateTime.current = Date.now();
 | 
			
		||||
      const d = e.clientX - startX.current;
 | 
			
		||||
      let d = e.clientX - startX.current;
 | 
			
		||||
      // If text and other elements go from right to left
 | 
			
		||||
      if (getLang() === "ar") {
 | 
			
		||||
        d = -1 * d;
 | 
			
		||||
      }
 | 
			
		||||
      const nextWidth = limit(startDragWidth.current + d);
 | 
			
		||||
      config.update((config) => {
 | 
			
		||||
        if (nextWidth < MIN_SIDEBAR_WIDTH) {
 | 
			
		||||
 
 | 
			
		||||
@@ -221,7 +221,12 @@ export const ByteDance = {
 | 
			
		||||
 | 
			
		||||
export const Alibaba = {
 | 
			
		||||
  ExampleEndpoint: ALIBABA_BASE_URL,
 | 
			
		||||
  ChatPath: "v1/services/aigc/text-generation/generation",
 | 
			
		||||
  ChatPath: (modelName: string) => {
 | 
			
		||||
    if (modelName.includes("vl") || modelName.includes("omni")) {
 | 
			
		||||
      return "v1/services/aigc/multimodal-generation/generation";
 | 
			
		||||
    }
 | 
			
		||||
    return `v1/services/aigc/text-generation/generation`;
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const Tencent = {
 | 
			
		||||
@@ -535,6 +540,8 @@ const anthropicModels = [
 | 
			
		||||
  "claude-3-5-sonnet-20240620",
 | 
			
		||||
  "claude-3-5-sonnet-20241022",
 | 
			
		||||
  "claude-3-5-sonnet-latest",
 | 
			
		||||
  "claude-3-7-sonnet-20250219",
 | 
			
		||||
  "claude-3-7-sonnet-latest",
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const baiduModels = [
 | 
			
		||||
@@ -568,6 +575,9 @@ const alibabaModes = [
 | 
			
		||||
  "qwen-max-0403",
 | 
			
		||||
  "qwen-max-0107",
 | 
			
		||||
  "qwen-max-longcontext",
 | 
			
		||||
  "qwen-omni-turbo",
 | 
			
		||||
  "qwen-vl-plus",
 | 
			
		||||
  "qwen-vl-max",
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const tencentModels = [
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ import {
 | 
			
		||||
  UPLOAD_URL,
 | 
			
		||||
  REQUEST_TIMEOUT_MS,
 | 
			
		||||
} from "@/app/constant";
 | 
			
		||||
import { RequestMessage } from "@/app/client/api";
 | 
			
		||||
import { MultimodalContent, RequestMessage } from "@/app/client/api";
 | 
			
		||||
import Locale from "@/app/locales";
 | 
			
		||||
import {
 | 
			
		||||
  EventStreamContentType,
 | 
			
		||||
@@ -70,8 +70,9 @@ export function compressImage(file: Blob, maxSize: number): Promise<string> {
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function preProcessImageContent(
 | 
			
		||||
export async function preProcessImageContentBase(
 | 
			
		||||
  content: RequestMessage["content"],
 | 
			
		||||
  transformImageUrl: (url: string) => Promise<{ [key: string]: any }>,
 | 
			
		||||
) {
 | 
			
		||||
  if (typeof content === "string") {
 | 
			
		||||
    return content;
 | 
			
		||||
@@ -81,7 +82,7 @@ export async function preProcessImageContent(
 | 
			
		||||
    if (part?.type == "image_url" && part?.image_url?.url) {
 | 
			
		||||
      try {
 | 
			
		||||
        const url = await cacheImageToBase64Image(part?.image_url?.url);
 | 
			
		||||
        result.push({ type: part.type, image_url: { url } });
 | 
			
		||||
        result.push(await transformImageUrl(url));
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
        console.error("Error processing image URL:", error);
 | 
			
		||||
      }
 | 
			
		||||
@@ -92,6 +93,23 @@ export async function preProcessImageContent(
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function preProcessImageContent(
 | 
			
		||||
  content: RequestMessage["content"],
 | 
			
		||||
) {
 | 
			
		||||
  return preProcessImageContentBase(content, async (url) => ({
 | 
			
		||||
    type: "image_url",
 | 
			
		||||
    image_url: { url },
 | 
			
		||||
  })) as Promise<MultimodalContent[] | string>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function preProcessImageContentForAlibabaDashScope(
 | 
			
		||||
  content: RequestMessage["content"],
 | 
			
		||||
) {
 | 
			
		||||
  return preProcessImageContentBase(content, async (url) => ({
 | 
			
		||||
    image: url,
 | 
			
		||||
  }));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const imageCaches: Record<string, string> = {};
 | 
			
		||||
export function cacheImageToBase64Image(imageUrl: string) {
 | 
			
		||||
  if (imageUrl.includes(CACHE_URL_PREFIX)) {
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,8 @@ const config: Config = {
 | 
			
		||||
  moduleNameMapper: {
 | 
			
		||||
    "^@/(.*)$": "<rootDir>/$1",
 | 
			
		||||
  },
 | 
			
		||||
  extensionsToTreatAsEsm: [".ts", ".tsx"],
 | 
			
		||||
  injectGlobals: true,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
 | 
			
		||||
 
 | 
			
		||||
@@ -1,24 +1,22 @@
 | 
			
		||||
// Learn more: https://github.com/testing-library/jest-dom
 | 
			
		||||
import "@testing-library/jest-dom";
 | 
			
		||||
import { jest } from "@jest/globals";
 | 
			
		||||
 | 
			
		||||
global.fetch = jest.fn(() =>
 | 
			
		||||
  Promise.resolve({
 | 
			
		||||
    ok: true,
 | 
			
		||||
    status: 200,
 | 
			
		||||
    json: () => Promise.resolve({}),
 | 
			
		||||
    json: () => Promise.resolve([]),
 | 
			
		||||
    headers: new Headers(),
 | 
			
		||||
    redirected: false,
 | 
			
		||||
    statusText: "OK",
 | 
			
		||||
    type: "basic",
 | 
			
		||||
    url: "",
 | 
			
		||||
    clone: function () {
 | 
			
		||||
      return this;
 | 
			
		||||
    },
 | 
			
		||||
    body: null,
 | 
			
		||||
    bodyUsed: false,
 | 
			
		||||
    arrayBuffer: () => Promise.resolve(new ArrayBuffer(0)),
 | 
			
		||||
    blob: () => Promise.resolve(new Blob()),
 | 
			
		||||
    formData: () => Promise.resolve(new FormData()),
 | 
			
		||||
    text: () => Promise.resolve(""),
 | 
			
		||||
  }),
 | 
			
		||||
  } as Response),
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -17,8 +17,8 @@
 | 
			
		||||
    "prompts": "node ./scripts/fetch-prompts.mjs",
 | 
			
		||||
    "prepare": "husky install",
 | 
			
		||||
    "proxy-dev": "sh ./scripts/init-proxy.sh && proxychains -f ./scripts/proxychains.conf yarn dev",
 | 
			
		||||
    "test": "jest --watch",
 | 
			
		||||
    "test:ci": "jest --ci"
 | 
			
		||||
    "test": "node --no-warnings --experimental-vm-modules $(yarn bin jest) --watch",
 | 
			
		||||
    "test:ci": "node --no-warnings --experimental-vm-modules $(yarn bin jest) --ci"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@fortaine/fetch-event-source": "^3.0.6",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
import { jest } from "@jest/globals";
 | 
			
		||||
import { isVisionModel } from "../app/utils";
 | 
			
		||||
 | 
			
		||||
describe("isVisionModel", () => {
 | 
			
		||||
@@ -50,7 +51,7 @@ describe("isVisionModel", () => {
 | 
			
		||||
 | 
			
		||||
  test("should identify models from VISION_MODELS env var", () => {
 | 
			
		||||
    process.env.VISION_MODELS = "custom-vision-model,another-vision-model";
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    expect(isVisionModel("custom-vision-model")).toBe(true);
 | 
			
		||||
    expect(isVisionModel("another-vision-model")).toBe(true);
 | 
			
		||||
    expect(isVisionModel("unrelated-model")).toBe(false);
 | 
			
		||||
@@ -64,4 +65,4 @@ describe("isVisionModel", () => {
 | 
			
		||||
    expect(isVisionModel("unrelated-model")).toBe(false);
 | 
			
		||||
    expect(isVisionModel("gpt-4-vision")).toBe(true);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user