Compare commits

...

15 Commits

Author SHA1 Message Date
Fred
27ac18d9d7 feat: init support for deepseek 2024-05-15 14:47:43 +08:00
DeanYao
3513c6801e Merge pull request #4626 from ChatGPTNextWeb/chore-fix
feat: googleApiKey & anthropicApiKey support setting multi-key
2024-05-07 15:06:02 +08:00
Dean-YZG
864529cbf6 feat: googleApiKey & anthropicApiKey support setting multi-key 2024-05-06 21:14:53 +08:00
DeanYao
58c0d3e12d Merge pull request #4625 from ChatGPTNextWeb/chore-fix
feat: fix 1)the property named 'role' of the first message must be 'u…
2024-05-06 20:48:29 +08:00
Dean-YZG
a1493bfb4e feat: bugfix 2024-05-06 20:46:53 +08:00
butterfly
b3e856df1d feat: fix 1)the property named 'role' of the first message must be 'user' 2)if default summarize model 'gpt-3.5-turbo' is blocked, use currentModel instead 3)if apiurl&apikey set by location, useCustomConfig would be opened 2024-05-06 19:26:39 +08:00
fred-bf
52312dbd23 Merge pull request #4595 from ChatGPTNextWeb/feat/bump-version
feat: bump version code
2024-04-30 13:28:30 +08:00
Fred
b2e8a1eaa2 feat: bump version code 2024-04-30 13:27:07 +08:00
DeanYao
506c17a093 Merge pull request #4564 from MrrDrr/gpt4v_remove_max_tokens
remove max_tokens from the official version of gpt4-turbo
2024-04-25 13:01:21 +08:00
DeanYao
69642fba52 Merge pull request #4557 from RoyRao2333/dev/no-fucos-outline
chore: No outline when element is in `:focus-visible` state
2024-04-25 12:58:19 +08:00
DeanYao
7d647c981f Merge pull request #4535 from RubuJam/main
Refer to OpenAI documentation to delete some models.
2024-04-25 11:44:01 +08:00
l.tingting
dd4648ed9a remove max_tokens from the official version of gpt4-turbo 2024-04-24 22:59:14 +08:00
Roy
1cd0beb231 chore: No outline when element is in :focus-visible state 2024-04-23 11:48:54 +08:00
黑云白土
b7aab3c102 Update google.ts 2024-04-17 17:16:31 +08:00
黑云白土
fcb1a657e3 Update constant.ts 2024-04-17 16:24:11 +08:00
17 changed files with 126 additions and 73 deletions

View File

@@ -54,10 +54,11 @@ ANTHROPIC_API_KEY=
### anthropic claude Api version. (optional) ### anthropic claude Api version. (optional)
ANTHROPIC_API_VERSION= ANTHROPIC_API_VERSION=
# deepseek api key (optional)
DEEPSEEK_API_KEY=
### anthropic claude Api url (optional) ### anthropic claude Api url (optional)
ANTHROPIC_URL= ANTHROPIC_URL=
### (optional) ### (optional)
WHITE_WEBDEV_ENDPOINTS= WEBDEV_ENDPOINTS_WHITELIST=

View File

@@ -212,6 +212,10 @@ anthropic claude Api version.
anthropic claude Api Url. anthropic claude Api Url.
### `DEEPSEEK_API_KEY` (optional)
deepseek Api Key.
### `HIDE_USER_API_KEY` (optional) ### `HIDE_USER_API_KEY` (optional)
> Default: Empty > Default: Empty
@@ -245,11 +249,12 @@ 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. User `-all` to disable all default models, `+all` to enable all default models.
### `WHITE_WEBDEV_ENDPOINTS` (可选) ### `WEBDEV_ENDPOINTS_WHITELIST` (可选)
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 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` - Each address must be a complete endpoint
> `https://xxxx/yyy`
- Multiple addresses are connected by ', ' - Multiple addresses are connected by ', '
## Requirements ## Requirements

View File

@@ -126,6 +126,10 @@ anthropic claude Api version.
anthropic claude Api Url. anthropic claude Api Url.
### `DEEPSEEK_API_KEY` (optional)
deepseek Api Key.
### `HIDE_USER_API_KEY` (可选) ### `HIDE_USER_API_KEY` (可选)
如果你不想让用户自行填入 API Key将此环境变量设置为 1 即可。 如果你不想让用户自行填入 API Key将此环境变量设置为 1 即可。
@@ -142,11 +146,12 @@ anthropic claude Api Url.
如果你想禁用从链接解析预制设置,将此环境变量设置为 1 即可。 如果你想禁用从链接解析预制设置,将此环境变量设置为 1 即可。
### `WHITE_WEBDEV_ENDPOINTS` (可选) ### `WEBDEV_ENDPOINTS_WHITELIST` (可选)
如果你想增加允许访问的webdav服务地址可以使用该选项格式要求 如果你想增加允许访问的webdav服务地址可以使用该选项格式要求
- 每一个地址必须是一个完整的 endpoint - 每一个地址必须是一个完整的 endpoint
> `https://xxxx/xxx` > `https://xxxx/xxx`
- 多个地址以`,`相连 - 多个地址以`,`相连
### `CUSTOM_MODELS` (可选) ### `CUSTOM_MODELS` (可选)

View File

@@ -73,6 +73,10 @@ export function auth(req: NextRequest, modelProvider: ModelProvider) {
case ModelProvider.Claude: case ModelProvider.Claude:
systemApiKey = serverConfig.anthropicApiKey; systemApiKey = serverConfig.anthropicApiKey;
break; break;
case ModelProvider.Deepseek:
systemApiKey = serverConfig.deepseekApiKey;
break;
case ModelProvider.GPT: case ModelProvider.GPT:
default: default:
if (serverConfig.isAzure) { if (serverConfig.isAzure) {

View File

@@ -87,6 +87,8 @@ export async function requestOpenai(req: NextRequest) {
DEFAULT_MODELS, DEFAULT_MODELS,
serverConfig.customModels, serverConfig.customModels,
); );
// check if deepseek model
const clonedBody = await req.text(); const clonedBody = await req.text();
fetchOptions.body = clonedBody; fetchOptions.body = clonedBody;
@@ -112,16 +114,16 @@ export async function requestOpenai(req: NextRequest) {
try { try {
const res = await fetch(fetchUrl, fetchOptions); const res = await fetch(fetchUrl, fetchOptions);
// Extract the OpenAI-Organization header from the response // Extract the OpenAI-Organization header from the response
const openaiOrganizationHeader = res.headers.get("OpenAI-Organization"); const openaiOrganizationHeader = res.headers.get("OpenAI-Organization");
// Check if serverConfig.openaiOrgId is defined and not an empty string // Check if serverConfig.openaiOrgId is defined and not an empty string
if (serverConfig.openaiOrgId && serverConfig.openaiOrgId.trim() !== "") { if (serverConfig.openaiOrgId && serverConfig.openaiOrgId.trim() !== "") {
// If openaiOrganizationHeader is present, log it; otherwise, log that the header is not present // If openaiOrganizationHeader is present, log it; otherwise, log that the header is not present
console.log("[Org ID]", openaiOrganizationHeader); console.log("[Org ID]", openaiOrganizationHeader);
} else { } else {
console.log("[Org ID] is not set up."); console.log("[Org ID] is not set up.");
} }
// to prevent browser prompt for credentials // to prevent browser prompt for credentials
const newHeaders = new Headers(res.headers); const newHeaders = new Headers(res.headers);
@@ -129,7 +131,6 @@ export async function requestOpenai(req: NextRequest) {
// to disable nginx buffering // to disable nginx buffering
newHeaders.set("X-Accel-Buffering", "no"); newHeaders.set("X-Accel-Buffering", "no");
// Conditionally delete the OpenAI-Organization header from the response if [Org ID] is undefined or empty (not setup in ENV) // Conditionally delete the OpenAI-Organization header from the response if [Org ID] is undefined or empty (not setup in ENV)
// Also, this is to prevent the header from being sent to the client // Also, this is to prevent the header from being sent to the client
if (!serverConfig.openaiOrgId || serverConfig.openaiOrgId.trim() === "") { if (!serverConfig.openaiOrgId || serverConfig.openaiOrgId.trim() === "") {
@@ -142,7 +143,6 @@ export async function requestOpenai(req: NextRequest) {
// The browser will try to decode the response with brotli and fail // The browser will try to decode the response with brotli and fail
newHeaders.delete("content-encoding"); newHeaders.delete("content-encoding");
return new Response(res.body, { return new Response(res.body, {
status: res.status, status: res.status,
statusText: res.statusText, statusText: res.statusText,

View File

@@ -1,12 +1,12 @@
import { NextRequest, NextResponse } from "next/server"; import { NextRequest, NextResponse } from "next/server";
import { STORAGE_KEY, internalWhiteWebDavEndpoints } from "../../../constant"; import { STORAGE_KEY, internalAllowedWebDavEndpoints } from "../../../constant";
import { getServerSideConfig } from "@/app/config/server"; import { getServerSideConfig } from "@/app/config/server";
const config = getServerSideConfig(); const config = getServerSideConfig();
const mergedWhiteWebDavEndpoints = [ const mergedAllowedWebDavEndpoints = [
...internalWhiteWebDavEndpoints, ...internalAllowedWebDavEndpoints,
...config.whiteWebDevEndpoints, ...config.allowedWebDevEndpoints,
].filter((domain) => Boolean(domain.trim())); ].filter((domain) => Boolean(domain.trim()));
async function handle( async function handle(
@@ -24,7 +24,9 @@ async function handle(
// Validate the endpoint to prevent potential SSRF attacks // Validate the endpoint to prevent potential SSRF attacks
if ( if (
!mergedWhiteWebDavEndpoints.some((white) => endpoint?.startsWith(white)) !mergedAllowedWebDavEndpoints.some(
(allowedEndpoint) => endpoint?.startsWith(allowedEndpoint),
)
) { ) {
return NextResponse.json( return NextResponse.json(
{ {

View File

@@ -70,7 +70,7 @@ export abstract class LLMApi {
abstract models(): Promise<LLMModel[]>; abstract models(): Promise<LLMModel[]>;
} }
type ProviderName = "openai" | "azure" | "claude" | "palm"; type ProviderName = "openai" | "azure" | "claude" | "palm" | "deepseek";
interface Model { interface Model {
name: string; name: string;
@@ -162,6 +162,7 @@ export function getHeaders() {
const modelConfig = useChatStore.getState().currentSession().mask.modelConfig; const modelConfig = useChatStore.getState().currentSession().mask.modelConfig;
const isGoogle = modelConfig.model.startsWith("gemini"); const isGoogle = modelConfig.model.startsWith("gemini");
const isAzure = accessStore.provider === ServiceProvider.Azure; const isAzure = accessStore.provider === ServiceProvider.Azure;
const isDeepSeek = accessStore.provider === ServiceProvider.DeepSeek;
const authHeader = isAzure ? "api-key" : "Authorization"; const authHeader = isAzure ? "api-key" : "Authorization";
const apiKey = isGoogle const apiKey = isGoogle
? accessStore.googleApiKey ? accessStore.googleApiKey

View File

@@ -161,6 +161,13 @@ export class ClaudeApi implements LLMApi {
}; };
}); });
if (prompt[0]?.role === "assistant") {
prompt.unshift({
role: "user",
content: ";",
});
}
const requestBody: AnthropicChatRequest = { const requestBody: AnthropicChatRequest = {
messages: prompt, messages: prompt,
stream: shouldStream, stream: shouldStream,

View File

@@ -21,11 +21,10 @@ export class GeminiProApi implements LLMApi {
} }
async chat(options: ChatOptions): Promise<void> { async chat(options: ChatOptions): Promise<void> {
// const apiClient = this; // const apiClient = this;
const visionModel = isVisionModel(options.config.model);
let multimodal = false; let multimodal = false;
const messages = options.messages.map((v) => { const messages = options.messages.map((v) => {
let parts: any[] = [{ text: getMessageTextContent(v) }]; let parts: any[] = [{ text: getMessageTextContent(v) }];
if (visionModel) { if (isVisionModel(options.config.model)) {
const images = getMessageImages(v); const images = getMessageImages(v);
if (images.length > 0) { if (images.length > 0) {
multimodal = true; multimodal = true;
@@ -117,17 +116,12 @@ export class GeminiProApi implements LLMApi {
const controller = new AbortController(); const controller = new AbortController();
options.onController?.(controller); options.onController?.(controller);
try { try {
let googleChatPath = visionModel
? Google.VisionChatPath(modelConfig.model)
: Google.ChatPath(modelConfig.model);
let chatPath = this.path(googleChatPath);
// let baseUrl = accessStore.googleUrl; // let baseUrl = accessStore.googleUrl;
if (!baseUrl) { if (!baseUrl) {
baseUrl = isApp baseUrl = isApp
? DEFAULT_API_HOST + "/api/proxy/google/" + googleChatPath ? DEFAULT_API_HOST + "/api/proxy/google/" + Google.ChatPath(modelConfig.model)
: chatPath; : this.path(Google.ChatPath(modelConfig.model));
} }
if (isApp) { if (isApp) {
@@ -145,6 +139,7 @@ export class GeminiProApi implements LLMApi {
() => controller.abort(), () => controller.abort(),
REQUEST_TIMEOUT_MS, REQUEST_TIMEOUT_MS,
); );
if (shouldStream) { if (shouldStream) {
let responseText = ""; let responseText = "";
let remainText = ""; let remainText = "";

View File

@@ -129,7 +129,7 @@ export class ChatGPTApi implements LLMApi {
}; };
// add max_tokens to vision model // add max_tokens to vision model
if (visionModel) { if (visionModel && modelConfig.model.includes("preview")) {
requestPayload["max_tokens"] = Math.max(modelConfig.max_tokens, 4000); requestPayload["max_tokens"] = Math.max(modelConfig.max_tokens, 4000);
} }

View File

@@ -1088,6 +1088,7 @@ function _Chat() {
if (payload.url) { if (payload.url) {
accessStore.update((access) => (access.openaiUrl = payload.url!)); accessStore.update((access) => (access.openaiUrl = payload.url!));
} }
accessStore.update((access) => (access.useCustomConfig = true));
}); });
} }
} catch { } catch {

View File

@@ -51,6 +51,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 = () => { export const getServerSideConfig = () => {
if (typeof process === "undefined") { if (typeof process === "undefined") {
throw Error( throw Error(
@@ -73,38 +89,41 @@ export const getServerSideConfig = () => {
const isAzure = !!process.env.AZURE_URL; const isAzure = !!process.env.AZURE_URL;
const isGoogle = !!process.env.GOOGLE_API_KEY; const isGoogle = !!process.env.GOOGLE_API_KEY;
const isAnthropic = !!process.env.ANTHROPIC_API_KEY; const isAnthropic = !!process.env.ANTHROPIC_API_KEY;
const isDeepSeek = !!process.env.DEEPSEEK_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);
const apiKey = apiKeys[randomIndex]; // const apiKey = apiKeys[randomIndex];
console.log( // console.log(
`[Server Config] using ${randomIndex + 1} of ${apiKeys.length} api key`, // `[Server Config] using ${randomIndex + 1} of ${apiKeys.length} api key`,
); // );
const whiteWebDevEndpoints = (process.env.WHITE_WEBDEV_ENDPOINTS ?? "").split( const allowedWebDevEndpoints = (
",", process.env.WEBDEV_ENDPOINTS_WHITELIST ?? ""
); ).split(",");
return { return {
baseUrl: process.env.BASE_URL, baseUrl: process.env.BASE_URL,
apiKey, apiKey: getApiKey(process.env.OPENAI_API_KEY),
openaiOrgId: process.env.OPENAI_ORG_ID, openaiOrgId: process.env.OPENAI_ORG_ID,
isAzure, isAzure,
azureUrl: process.env.AZURE_URL, azureUrl: process.env.AZURE_URL,
azureApiKey: process.env.AZURE_API_KEY, azureApiKey: getApiKey(process.env.AZURE_API_KEY),
azureApiVersion: process.env.AZURE_API_VERSION, azureApiVersion: process.env.AZURE_API_VERSION,
isGoogle, isGoogle,
googleApiKey: process.env.GOOGLE_API_KEY, googleApiKey: getApiKey(process.env.GOOGLE_API_KEY),
googleUrl: process.env.GOOGLE_URL, googleUrl: process.env.GOOGLE_URL,
isAnthropic, isAnthropic,
anthropicApiKey: process.env.ANTHROPIC_API_KEY, anthropicApiKey: getApiKey(process.env.ANTHROPIC_API_KEY),
anthropicApiVersion: process.env.ANTHROPIC_API_VERSION, anthropicApiVersion: process.env.ANTHROPIC_API_VERSION,
anthropicUrl: process.env.ANTHROPIC_URL, anthropicUrl: process.env.ANTHROPIC_URL,
deepseekApiKey: getApiKey(process.env.DEEPSEEK_API_KEY),
gtmId: process.env.GTM_ID, gtmId: process.env.GTM_ID,
needCode: ACCESS_CODES.size > 0, needCode: ACCESS_CODES.size > 0,
@@ -120,6 +139,6 @@ export const getServerSideConfig = () => {
disableFastLink: !!process.env.DISABLE_FAST_LINK, disableFastLink: !!process.env.DISABLE_FAST_LINK,
customModels, customModels,
defaultModel, defaultModel,
whiteWebDevEndpoints, allowedWebDevEndpoints,
}; };
}; };

View File

@@ -1,3 +1,5 @@
import { Chat } from "./components/chat";
export const OWNER = "Yidadaa"; export const OWNER = "Yidadaa";
export const REPO = "ChatGPT-Next-Web"; export const REPO = "ChatGPT-Next-Web";
export const REPO_URL = `https://github.com/${OWNER}/${REPO}`; export const REPO_URL = `https://github.com/${OWNER}/${REPO}`;
@@ -70,12 +72,14 @@ export enum ServiceProvider {
Azure = "Azure", Azure = "Azure",
Google = "Google", Google = "Google",
Anthropic = "Anthropic", Anthropic = "Anthropic",
DeepSeek = "DeepSeek",
} }
export enum ModelProvider { export enum ModelProvider {
GPT = "GPT", GPT = "GPT",
GeminiPro = "GeminiPro", GeminiPro = "GeminiPro",
Claude = "Claude", Claude = "Claude",
Deepseek = "DeepSeek",
} }
export const Anthropic = { export const Anthropic = {
@@ -99,7 +103,6 @@ export const Azure = {
export const Google = { export const Google = {
ExampleEndpoint: "https://generativelanguage.googleapis.com/", ExampleEndpoint: "https://generativelanguage.googleapis.com/",
ChatPath: (modelName: string) => `v1beta/models/${modelName}:generateContent`, ChatPath: (modelName: string) => `v1beta/models/${modelName}:generateContent`,
VisionChatPath: (modelName: string) => `v1beta/models/${modelName}:generateContent`,
}; };
export const DEFAULT_INPUT_TEMPLATE = `{{input}}`; // input / time / model / lang export const DEFAULT_INPUT_TEMPLATE = `{{input}}`; // input / time / model / lang
@@ -128,8 +131,6 @@ export const KnowledgeCutOffDate: Record<string, string> = {
"gpt-4-turbo": "2023-12", "gpt-4-turbo": "2023-12",
"gpt-4-turbo-2024-04-09": "2023-12", "gpt-4-turbo-2024-04-09": "2023-12",
"gpt-4-turbo-preview": "2023-12", "gpt-4-turbo-preview": "2023-12",
"gpt-4-1106-preview": "2023-04",
"gpt-4-0125-preview": "2023-12",
"gpt-4-vision-preview": "2023-04", "gpt-4-vision-preview": "2023-04",
// After improvements, // After improvements,
// it's now easier to add "KnowledgeCutOffDate" instead of stupid hardcoding it, as was done previously. // it's now easier to add "KnowledgeCutOffDate" instead of stupid hardcoding it, as was done previously.
@@ -139,24 +140,11 @@ export const KnowledgeCutOffDate: Record<string, string> = {
const openaiModels = [ const openaiModels = [
"gpt-3.5-turbo", "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",
"gpt-4-0314",
"gpt-4-0613",
"gpt-4-1106-preview",
"gpt-4-0125-preview",
"gpt-4-32k", "gpt-4-32k",
"gpt-4-32k-0314",
"gpt-4-32k-0613",
"gpt-4-turbo", "gpt-4-turbo",
"gpt-4-turbo-preview", "gpt-4-turbo-preview",
"gpt-4-vision-preview", "gpt-4-vision-preview",
"gpt-4-turbo-2024-04-09",
]; ];
const googleModels = [ const googleModels = [
@@ -174,6 +162,8 @@ const anthropicModels = [
"claude-3-haiku-20240307", "claude-3-haiku-20240307",
]; ];
const deepseekModels = ["deepseek-chat"];
export const DEFAULT_MODELS = [ export const DEFAULT_MODELS = [
...openaiModels.map((name) => ({ ...openaiModels.map((name) => ({
name, name,
@@ -202,13 +192,22 @@ export const DEFAULT_MODELS = [
providerType: "anthropic", providerType: "anthropic",
}, },
})), })),
...deepseekModels.map((name) => ({
name,
available: true,
provider: {
id: "deepseek",
providerName: "DeepSeek",
providerType: "deepseek",
},
})),
] as const; ] as const;
export const CHAT_PAGE_SIZE = 15; export const CHAT_PAGE_SIZE = 15;
export const MAX_RENDER_MSG_COUNT = 45; export const MAX_RENDER_MSG_COUNT = 45;
// some famous webdav endpoints // some famous webdav endpoints
export const internalWhiteWebDavEndpoints = [ export const internalAllowedWebDavEndpoints = [
"https://dav.jianguoyun.com/dav/", "https://dav.jianguoyun.com/dav/",
"https://dav.dropdav.com/", "https://dav.dropdav.com/",
"https://dav.box.com/dav", "https://dav.box.com/dav",

View File

@@ -21,6 +21,8 @@ import { estimateTokenLength } from "../utils/token";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { createPersistStore } from "../utils/store"; import { createPersistStore } from "../utils/store";
import { identifyDefaultClaudeModel } from "../utils/checkers"; import { identifyDefaultClaudeModel } from "../utils/checkers";
import { collectModelsWithDefaultModel } from "../utils/model";
import { useAccessStore } from "./access";
export type ChatMessage = RequestMessage & { export type ChatMessage = RequestMessage & {
date: string; date: string;
@@ -87,9 +89,19 @@ function createEmptySession(): ChatSession {
function getSummarizeModel(currentModel: string) { function getSummarizeModel(currentModel: string) {
// if it is using gpt-* models, force to use 3.5 to summarize // if it is using gpt-* models, force to use 3.5 to summarize
if (currentModel.startsWith("gpt")) { 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 GEMINI_SUMMARIZE_MODEL;
} }
return currentModel; return currentModel;

View File

@@ -86,6 +86,7 @@
@include dark; @include dark;
} }
} }
html { html {
height: var(--full-height); height: var(--full-height);
@@ -110,6 +111,10 @@ body {
@media only screen and (max-width: 600px) { @media only screen and (max-width: 600px) {
background-color: var(--second); background-color: var(--second);
} }
*:focus-visible {
outline: none;
}
} }
::-webkit-scrollbar { ::-webkit-scrollbar {

View File

@@ -64,13 +64,10 @@ export function collectModelTableWithDefaultModel(
) { ) {
let modelTable = collectModelTable(models, customModels); let modelTable = collectModelTable(models, customModels);
if (defaultModel && defaultModel !== "") { if (defaultModel && defaultModel !== "") {
delete modelTable[defaultModel];
modelTable[defaultModel] = { modelTable[defaultModel] = {
...modelTable[defaultModel],
name: defaultModel, name: defaultModel,
displayName: defaultModel,
available: true, available: true,
provider:
modelTable[defaultModel]?.provider ?? customProvider(defaultModel),
isDefault: true, isDefault: true,
}; };
} }

View File

@@ -9,7 +9,7 @@
}, },
"package": { "package": {
"productName": "NextChat", "productName": "NextChat",
"version": "2.11.3" "version": "2.12.2"
}, },
"tauri": { "tauri": {
"allowlist": { "allowlist": {