From 801dc412f99937dfd64a895309d9304429d94cac Mon Sep 17 00:00:00 2001 From: lloydzhou Date: Thu, 24 Oct 2024 15:28:05 +0800 Subject: [PATCH 01/57] add claude-3.5-haiku --- app/constant.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/constant.ts b/app/constant.ts index 9774bb594dd..00b8d9dae95 100644 --- a/app/constant.ts +++ b/app/constant.ts @@ -319,6 +319,7 @@ const anthropicModels = [ "claude-3-opus-20240229", "claude-3-haiku-20240307", "claude-3-5-sonnet-20240620", + "claude-3-5-haiku-latest", ]; const baiduModels = [ From e49466fa054c702898780967812abe2dabd4ba6b Mon Sep 17 00:00:00 2001 From: Dogtiti <499960698@qq.com> Date: Mon, 4 Nov 2024 21:25:56 +0800 Subject: [PATCH 02/57] feat: update real 'currentSession' --- app/components/chat.tsx | 2 +- app/store/chat.ts | 31 ++++++++++++++++++++++--------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 3d5b6a4f2c4..a62021db3d2 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -1607,7 +1607,7 @@ function _Chat() { title={Locale.Chat.Actions.RefreshTitle} onClick={() => { showToast(Locale.Chat.Actions.RefreshToast); - chatStore.summarizeSession(true); + chatStore.summarizeSession(true, session); }} /> diff --git a/app/store/chat.ts b/app/store/chat.ts index 1bf2e13677b..4208692e270 100644 --- a/app/store/chat.ts +++ b/app/store/chat.ts @@ -352,13 +352,13 @@ export const useChatStore = createPersistStore( return session; }, - onNewMessage(message: ChatMessage) { - get().updateCurrentSession((session) => { + onNewMessage(message: ChatMessage, targetSession: ChatSession) { + get().updateTargetSession(targetSession, (session) => { session.messages = session.messages.concat(); session.lastUpdate = Date.now(); }); get().updateStat(message); - get().summarizeSession(); + get().summarizeSession(false, targetSession); }, async onUserInput(content: string, attachImages?: string[]) { @@ -428,7 +428,7 @@ export const useChatStore = createPersistStore( botMessage.streaming = false; if (message) { botMessage.content = message; - get().onNewMessage(botMessage); + get().onNewMessage(botMessage, session); } ChatControllerPool.remove(session.id, botMessage.id); }, @@ -598,9 +598,12 @@ export const useChatStore = createPersistStore( }); }, - summarizeSession(refreshTitle: boolean = false) { + summarizeSession( + refreshTitle: boolean = false, + targetSession: ChatSession, + ) { const config = useAppConfig.getState(); - const session = get().currentSession(); + const session = targetSession; const modelConfig = session.mask.modelConfig; // skip summarize when using dalle3? if (isDalle3(modelConfig.model)) { @@ -651,7 +654,8 @@ export const useChatStore = createPersistStore( }, onFinish(message, responseRes) { if (responseRes?.status === 200) { - get().updateCurrentSession( + get().updateTargetSession( + session, (session) => (session.topic = message.length > 0 ? trimTopic(message) : DEFAULT_TOPIC), @@ -719,7 +723,7 @@ export const useChatStore = createPersistStore( onFinish(message, responseRes) { if (responseRes?.status === 200) { console.log("[Memory] ", message); - get().updateCurrentSession((session) => { + get().updateTargetSession(session, (session) => { session.lastSummarizeIndex = lastSummarizeIndex; session.memoryPrompt = message; // Update the memory prompt for stored it in local storage }); @@ -745,7 +749,16 @@ export const useChatStore = createPersistStore( updater(sessions[index]); set(() => ({ sessions })); }, - + updateTargetSession( + targetSession: ChatSession, + updater: (session: ChatSession) => void, + ) { + const sessions = get().sessions; + const index = sessions.findIndex((s) => s.id === targetSession.id); + if (index < 0) return; + updater(sessions[index]); + set(() => ({ sessions })); + }, async clearAllData() { await indexedDBStorage.clear(); localStorage.clear(); From 0ec423389fa08e4e4b046db5ad147194622b6218 Mon Sep 17 00:00:00 2001 From: DDMeaqua Date: Tue, 5 Nov 2024 11:06:20 +0800 Subject: [PATCH 03/57] chore: update readme --- README.md | 8 ++++++++ README_CN.md | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/README.md b/README.md index dd2c5b1eedc..fce62ba3750 100644 --- a/README.md +++ b/README.md @@ -301,6 +301,14 @@ iflytek Api Key. iflytek Api Secret. +### `CHATGLM_API_KEY` (optional) + +ChatGLM Api Key. + +### `CHATGLM_URL` (optional) + +ChatGLM Api Url. + ### `HIDE_USER_API_KEY` (optional) > Default: Empty diff --git a/README_CN.md b/README_CN.md index ccdcf28ffe9..d4da8b9da13 100644 --- a/README_CN.md +++ b/README_CN.md @@ -184,6 +184,13 @@ ByteDance Api Url. 讯飞星火Api Secret. +### `CHATGLM_API_KEY` (可选) + +ChatGLM Api Key. + +### `CHATGLM_URL` (可选) + +ChatGLM Api Url. ### `HIDE_USER_API_KEY` (可选) From b844045d231658b9e40fa0582936c6746e7a7ef4 Mon Sep 17 00:00:00 2001 From: ryanhex53 Date: Tue, 5 Nov 2024 07:44:12 +0000 Subject: [PATCH 04/57] Custom model names can include the `@` symbol by itself. To specify the model's provider, append it after the model name using `@` as before. This format supports cases like `google vertex ai` with a model name like `claude-3-5-sonnet@20240620`. For instance, `claude-3-5-sonnet@20240620@vertex-ai` will be split by `split(/@(?!.*@)/)` into: `[ 'claude-3-5-sonnet@20240620', 'vertex-ai' ]`, where the former is the model name and the latter is the custom provider. --- app/api/common.ts | 2 +- app/components/chat.tsx | 2 +- app/components/model-config.tsx | 6 ++++-- app/store/access.ts | 2 +- app/utils/model.ts | 8 ++++---- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/app/api/common.ts b/app/api/common.ts index b4c792d6ff0..322dedeedfe 100644 --- a/app/api/common.ts +++ b/app/api/common.ts @@ -71,7 +71,7 @@ export async function requestOpenai(req: NextRequest) { .filter((v) => !!v && !v.startsWith("-") && v.includes(modelName)) .forEach((m) => { const [fullName, displayName] = m.split("="); - const [_, providerName] = fullName.split("@"); + const [_, providerName] = fullName.split(/@(?!.*@)/); if (providerName === "azure" && !displayName) { const [_, deployId] = (serverConfig?.azureUrl ?? "").split( "deployments/", diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 3d5b6a4f2c4..2ff08253a1c 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -645,7 +645,7 @@ export function ChatActions(props: { onClose={() => setShowModelSelector(false)} onSelection={(s) => { if (s.length === 0) return; - const [model, providerName] = s[0].split("@"); + const [model, providerName] = s[0].split(/@(?!.*@)/); chatStore.updateCurrentSession((session) => { session.mask.modelConfig.model = model as ModelType; session.mask.modelConfig.providerName = diff --git a/app/components/model-config.tsx b/app/components/model-config.tsx index f2297e10b49..0eac916eb3e 100644 --- a/app/components/model-config.tsx +++ b/app/components/model-config.tsx @@ -28,7 +28,8 @@ export function ModelConfigList(props: { value={value} align="left" onChange={(e) => { - const [model, providerName] = e.currentTarget.value.split("@"); + const [model, providerName] = + e.currentTarget.value.split(/@(?!.*@)/); props.updateConfig((config) => { config.model = ModalConfigValidator.model(model); config.providerName = providerName as ServiceProvider; @@ -247,7 +248,8 @@ export function ModelConfigList(props: { aria-label={Locale.Settings.CompressModel.Title} value={compressModelValue} onChange={(e) => { - const [model, providerName] = e.currentTarget.value.split("@"); + const [model, providerName] = + e.currentTarget.value.split(/@(?!.*@)/); props.updateConfig((config) => { config.compressModel = ModalConfigValidator.model(model); config.compressProviderName = providerName as ServiceProvider; diff --git a/app/store/access.ts b/app/store/access.ts index 3b0e6357bc1..4e2cb160362 100644 --- a/app/store/access.ts +++ b/app/store/access.ts @@ -226,7 +226,7 @@ export const useAccessStore = createPersistStore( .then((res) => { const defaultModel = res.defaultModel ?? ""; if (defaultModel !== "") { - const [model, providerName] = defaultModel.split("@"); + const [model, providerName] = defaultModel.split(/@(?!.*@)/); DEFAULT_CONFIG.modelConfig.model = model; DEFAULT_CONFIG.modelConfig.providerName = providerName; } diff --git a/app/utils/model.ts b/app/utils/model.ts index 0b62b53be09..0b95713e1c3 100644 --- a/app/utils/model.ts +++ b/app/utils/model.ts @@ -79,10 +79,10 @@ export function collectModelTable( ); } else { // 1. find model by name, and set available value - const [customModelName, customProviderName] = name.split("@"); + const [customModelName, customProviderName] = name.split(/@(?!.*@)/); let count = 0; for (const fullName in modelTable) { - const [modelName, providerName] = fullName.split("@"); + const [modelName, providerName] = fullName.split(/@(?!.*@)/); if ( customModelName == modelName && (customProviderName === undefined || @@ -102,7 +102,7 @@ export function collectModelTable( } // 2. if model not exists, create new model with available value if (count === 0) { - let [customModelName, customProviderName] = name.split("@"); + let [customModelName, customProviderName] = name.split(/@(?!.*@)/); const provider = customProvider( customProviderName || customModelName, ); @@ -139,7 +139,7 @@ export function collectModelTableWithDefaultModel( for (const key of Object.keys(modelTable)) { if ( modelTable[key].available && - key.split("@").shift() == defaultModel + key.split(/@(?!.*@)/).shift() == defaultModel ) { modelTable[key].isDefault = true; break; From 00d6cb27f719caffd24518db3dd656b7380a9062 Mon Sep 17 00:00:00 2001 From: lloydzhou Date: Tue, 5 Nov 2024 17:42:55 +0800 Subject: [PATCH 05/57] update version --- src-tauri/tauri.conf.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 415825b13f2..7e08d9070e5 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -9,7 +9,7 @@ }, "package": { "productName": "NextChat", - "version": "2.15.6" + "version": "2.15.7" }, "tauri": { "allowlist": { From 8e2484fcdf476a1248ae91541d6d491e5881b49b Mon Sep 17 00:00:00 2001 From: ryanhex53 Date: Tue, 5 Nov 2024 13:52:54 +0000 Subject: [PATCH 06/57] Refactor: Replace all provider split occurrences with getModelProvider utility method --- app/api/common.ts | 4 ++-- app/components/chat.tsx | 3 ++- app/components/model-config.tsx | 11 +++++++---- app/store/access.ts | 5 +++-- app/utils/model.ts | 19 +++++++++++++++---- test/model-provider.test.ts | 31 +++++++++++++++++++++++++++++++ 6 files changed, 60 insertions(+), 13 deletions(-) create mode 100644 test/model-provider.test.ts diff --git a/app/api/common.ts b/app/api/common.ts index 322dedeedfe..495a12ccdbb 100644 --- a/app/api/common.ts +++ b/app/api/common.ts @@ -1,8 +1,8 @@ import { NextRequest, NextResponse } from "next/server"; import { getServerSideConfig } from "../config/server"; import { OPENAI_BASE_URL, ServiceProvider } from "../constant"; -import { isModelAvailableInServer } from "../utils/model"; import { cloudflareAIGatewayUrl } from "../utils/cloudflare"; +import { getModelProvider, isModelAvailableInServer } from "../utils/model"; const serverConfig = getServerSideConfig(); @@ -71,7 +71,7 @@ export async function requestOpenai(req: NextRequest) { .filter((v) => !!v && !v.startsWith("-") && v.includes(modelName)) .forEach((m) => { const [fullName, displayName] = m.split("="); - const [_, providerName] = fullName.split(/@(?!.*@)/); + const [_, providerName] = getModelProvider(fullName); if (providerName === "azure" && !displayName) { const [_, deployId] = (serverConfig?.azureUrl ?? "").split( "deployments/", diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 2ff08253a1c..cee54d8914f 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -120,6 +120,7 @@ import { createTTSPlayer } from "../utils/audio"; import { MsEdgeTTS, OUTPUT_FORMAT } from "../utils/ms_edge_tts"; import { isEmpty } from "lodash-es"; +import { getModelProvider } from "../utils/model"; const localStorage = safeLocalStorage(); @@ -645,7 +646,7 @@ export function ChatActions(props: { onClose={() => setShowModelSelector(false)} onSelection={(s) => { if (s.length === 0) return; - const [model, providerName] = s[0].split(/@(?!.*@)/); + const [model, providerName] = getModelProvider(s[0]); chatStore.updateCurrentSession((session) => { session.mask.modelConfig.model = model as ModelType; session.mask.modelConfig.providerName = diff --git a/app/components/model-config.tsx b/app/components/model-config.tsx index 0eac916eb3e..e845bfeac7a 100644 --- a/app/components/model-config.tsx +++ b/app/components/model-config.tsx @@ -7,6 +7,7 @@ import { ListItem, Select } from "./ui-lib"; import { useAllModels } from "../utils/hooks"; import { groupBy } from "lodash-es"; import styles from "./model-config.module.scss"; +import { getModelProvider } from "../utils/model"; export function ModelConfigList(props: { modelConfig: ModelConfig; @@ -28,8 +29,9 @@ export function ModelConfigList(props: { value={value} align="left" onChange={(e) => { - const [model, providerName] = - e.currentTarget.value.split(/@(?!.*@)/); + const [model, providerName] = getModelProvider( + e.currentTarget.value, + ); props.updateConfig((config) => { config.model = ModalConfigValidator.model(model); config.providerName = providerName as ServiceProvider; @@ -248,8 +250,9 @@ export function ModelConfigList(props: { aria-label={Locale.Settings.CompressModel.Title} value={compressModelValue} onChange={(e) => { - const [model, providerName] = - e.currentTarget.value.split(/@(?!.*@)/); + const [model, providerName] = getModelProvider( + e.currentTarget.value, + ); props.updateConfig((config) => { config.compressModel = ModalConfigValidator.model(model); config.compressProviderName = providerName as ServiceProvider; diff --git a/app/store/access.ts b/app/store/access.ts index 4e2cb160362..4796b2fe84e 100644 --- a/app/store/access.ts +++ b/app/store/access.ts @@ -21,6 +21,7 @@ import { getClientConfig } from "../config/client"; import { createPersistStore } from "../utils/store"; import { ensure } from "../utils/clone"; import { DEFAULT_CONFIG } from "./config"; +import { getModelProvider } from "../utils/model"; let fetchState = 0; // 0 not fetch, 1 fetching, 2 done @@ -226,9 +227,9 @@ export const useAccessStore = createPersistStore( .then((res) => { const defaultModel = res.defaultModel ?? ""; if (defaultModel !== "") { - const [model, providerName] = defaultModel.split(/@(?!.*@)/); + const [model, providerName] = getModelProvider(defaultModel); DEFAULT_CONFIG.modelConfig.model = model; - DEFAULT_CONFIG.modelConfig.providerName = providerName; + DEFAULT_CONFIG.modelConfig.providerName = providerName as any; } return res; diff --git a/app/utils/model.ts b/app/utils/model.ts index 0b95713e1c3..a1b7df1b61e 100644 --- a/app/utils/model.ts +++ b/app/utils/model.ts @@ -37,6 +37,17 @@ const sortModelTable = (models: ReturnType) => } }); +/** + * get model name and provider from a formatted string, + * e.g. `gpt-4@OpenAi` or `claude-3-5-sonnet@20240620@Google` + * @param modelWithProvider model name with provider separated by last `@` char, + * @returns [model, provider] tuple, if no `@` char found, provider is undefined + */ +export function getModelProvider(modelWithProvider: string): [string, string?] { + const [model, provider] = modelWithProvider.split(/@(?!.*@)/); + return [model, provider]; +} + export function collectModelTable( models: readonly LLMModel[], customModels: string, @@ -79,10 +90,10 @@ export function collectModelTable( ); } else { // 1. find model by name, and set available value - const [customModelName, customProviderName] = name.split(/@(?!.*@)/); + const [customModelName, customProviderName] = getModelProvider(name); let count = 0; for (const fullName in modelTable) { - const [modelName, providerName] = fullName.split(/@(?!.*@)/); + const [modelName, providerName] = getModelProvider(fullName); if ( customModelName == modelName && (customProviderName === undefined || @@ -102,7 +113,7 @@ export function collectModelTable( } // 2. if model not exists, create new model with available value if (count === 0) { - let [customModelName, customProviderName] = name.split(/@(?!.*@)/); + let [customModelName, customProviderName] = getModelProvider(name); const provider = customProvider( customProviderName || customModelName, ); @@ -139,7 +150,7 @@ export function collectModelTableWithDefaultModel( for (const key of Object.keys(modelTable)) { if ( modelTable[key].available && - key.split(/@(?!.*@)/).shift() == defaultModel + getModelProvider(key)[0] == defaultModel ) { modelTable[key].isDefault = true; break; diff --git a/test/model-provider.test.ts b/test/model-provider.test.ts new file mode 100644 index 00000000000..41f14be026c --- /dev/null +++ b/test/model-provider.test.ts @@ -0,0 +1,31 @@ +import { getModelProvider } from "../app/utils/model"; + +describe("getModelProvider", () => { + test("should return model and provider when input contains '@'", () => { + const input = "model@provider"; + const [model, provider] = getModelProvider(input); + expect(model).toBe("model"); + expect(provider).toBe("provider"); + }); + + test("should return model and undefined provider when input does not contain '@'", () => { + const input = "model"; + const [model, provider] = getModelProvider(input); + expect(model).toBe("model"); + expect(provider).toBeUndefined(); + }); + + test("should handle multiple '@' characters correctly", () => { + const input = "model@provider@extra"; + const [model, provider] = getModelProvider(input); + expect(model).toBe("model@provider"); + expect(provider).toBe("extra"); + }); + + test("should return empty strings when input is empty", () => { + const input = ""; + const [model, provider] = getModelProvider(input); + expect(model).toBe(""); + expect(provider).toBeUndefined(); + }); +}); From c4e19dbc59e59b71c81cf33600f7a2be235b0ccc Mon Sep 17 00:00:00 2001 From: Dogtiti <499960698@qq.com> Date: Wed, 6 Nov 2024 11:06:18 +0800 Subject: [PATCH 07/57] fix: updateCurrentSession => updateTargetSession --- app/components/chat.tsx | 80 +++++++++++++++++++++++------------------ app/store/chat.ts | 29 ++++++--------- 2 files changed, 56 insertions(+), 53 deletions(-) diff --git a/app/components/chat.tsx b/app/components/chat.tsx index a62021db3d2..c5deeefa5c4 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -148,7 +148,8 @@ export function SessionConfigModel(props: { onClose: () => void }) { text={Locale.Chat.Config.Reset} onClick={async () => { if (await showConfirm(Locale.Memory.ResetConfirm)) { - chatStore.updateCurrentSession( + chatStore.updateTargetSession( + session, (session) => (session.memoryPrompt = ""), ); } @@ -173,7 +174,10 @@ export function SessionConfigModel(props: { onClose: () => void }) { updateMask={(updater) => { const mask = { ...session.mask }; updater(mask); - chatStore.updateCurrentSession((session) => (session.mask = mask)); + chatStore.updateTargetSession( + session, + (session) => (session.mask = mask), + ); }} shouldSyncFromGlobal extraListItems={ @@ -345,12 +349,14 @@ export function PromptHints(props: { function ClearContextDivider() { const chatStore = useChatStore(); + const session = chatStore.currentSession(); return (
- chatStore.updateCurrentSession( + chatStore.updateTargetSession( + session, (session) => (session.clearContextIndex = undefined), ) } @@ -460,6 +466,7 @@ export function ChatActions(props: { const navigate = useNavigate(); const chatStore = useChatStore(); const pluginStore = usePluginStore(); + const session = chatStore.currentSession(); // switch themes const theme = config.theme; @@ -476,10 +483,9 @@ export function ChatActions(props: { const stopAll = () => ChatControllerPool.stopAll(); // switch model - const currentModel = chatStore.currentSession().mask.modelConfig.model; + const currentModel = session.mask.modelConfig.model; const currentProviderName = - chatStore.currentSession().mask.modelConfig?.providerName || - ServiceProvider.OpenAI; + session.mask.modelConfig?.providerName || ServiceProvider.OpenAI; const allModels = useAllModels(); const models = useMemo(() => { const filteredModels = allModels.filter((m) => m.available); @@ -513,12 +519,9 @@ export function ChatActions(props: { const dalle3Sizes: DalleSize[] = ["1024x1024", "1792x1024", "1024x1792"]; const dalle3Qualitys: DalleQuality[] = ["standard", "hd"]; const dalle3Styles: DalleStyle[] = ["vivid", "natural"]; - const currentSize = - chatStore.currentSession().mask.modelConfig?.size ?? "1024x1024"; - const currentQuality = - chatStore.currentSession().mask.modelConfig?.quality ?? "standard"; - const currentStyle = - chatStore.currentSession().mask.modelConfig?.style ?? "vivid"; + const currentSize = session.mask.modelConfig?.size ?? "1024x1024"; + const currentQuality = session.mask.modelConfig?.quality ?? "standard"; + const currentStyle = session.mask.modelConfig?.style ?? "vivid"; const isMobileScreen = useMobileScreen(); @@ -536,7 +539,7 @@ export function ChatActions(props: { if (isUnavailableModel && models.length > 0) { // show next model to default model if exist let nextModel = models.find((model) => model.isDefault) || models[0]; - chatStore.updateCurrentSession((session) => { + chatStore.updateTargetSession(session, (session) => { session.mask.modelConfig.model = nextModel.name; session.mask.modelConfig.providerName = nextModel?.provider ?.providerName as ServiceProvider; @@ -547,7 +550,7 @@ export function ChatActions(props: { : nextModel.name, ); } - }, [chatStore, currentModel, models]); + }, [chatStore, currentModel, models, session]); return (
@@ -614,7 +617,7 @@ export function ChatActions(props: { text={Locale.Chat.InputActions.Clear} icon={} onClick={() => { - chatStore.updateCurrentSession((session) => { + chatStore.updateTargetSession(session, (session) => { if (session.clearContextIndex === session.messages.length) { session.clearContextIndex = undefined; } else { @@ -646,7 +649,7 @@ export function ChatActions(props: { onSelection={(s) => { if (s.length === 0) return; const [model, providerName] = s[0].split("@"); - chatStore.updateCurrentSession((session) => { + chatStore.updateTargetSession(session, (session) => { session.mask.modelConfig.model = model as ModelType; session.mask.modelConfig.providerName = providerName as ServiceProvider; @@ -684,7 +687,7 @@ export function ChatActions(props: { onSelection={(s) => { if (s.length === 0) return; const size = s[0]; - chatStore.updateCurrentSession((session) => { + chatStore.updateTargetSession(session, (session) => { session.mask.modelConfig.size = size; }); showToast(size); @@ -711,7 +714,7 @@ export function ChatActions(props: { onSelection={(q) => { if (q.length === 0) return; const quality = q[0]; - chatStore.updateCurrentSession((session) => { + chatStore.updateTargetSession(session, (session) => { session.mask.modelConfig.quality = quality; }); showToast(quality); @@ -738,7 +741,7 @@ export function ChatActions(props: { onSelection={(s) => { if (s.length === 0) return; const style = s[0]; - chatStore.updateCurrentSession((session) => { + chatStore.updateTargetSession(session, (session) => { session.mask.modelConfig.style = style; }); showToast(style); @@ -769,7 +772,7 @@ export function ChatActions(props: { }))} onClose={() => setShowPluginSelector(false)} onSelection={(s) => { - chatStore.updateCurrentSession((session) => { + chatStore.updateTargetSession(session, (session) => { session.mask.plugin = s as string[]; }); }} @@ -812,7 +815,8 @@ export function EditMessageModal(props: { onClose: () => void }) { icon={} key="ok" onClick={() => { - chatStore.updateCurrentSession( + chatStore.updateTargetSession( + session, (session) => (session.messages = messages), ); props.onClose(); @@ -829,7 +833,8 @@ export function EditMessageModal(props: { onClose: () => void }) { type="text" value={session.topic} onInput={(e) => - chatStore.updateCurrentSession( + chatStore.updateTargetSession( + session, (session) => (session.topic = e.currentTarget.value), ) } @@ -990,7 +995,8 @@ function _Chat() { prev: () => chatStore.nextSession(-1), next: () => chatStore.nextSession(1), clear: () => - chatStore.updateCurrentSession( + chatStore.updateTargetSession( + session, (session) => (session.clearContextIndex = session.messages.length), ), fork: () => chatStore.forkSession(), @@ -1061,7 +1067,7 @@ function _Chat() { }; useEffect(() => { - chatStore.updateCurrentSession((session) => { + chatStore.updateTargetSession(session, (session) => { const stopTiming = Date.now() - REQUEST_TIMEOUT_MS; session.messages.forEach((m) => { // check if should stop all stale messages @@ -1087,7 +1093,7 @@ function _Chat() { } }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [session]); // check if should send message const onInputKeyDown = (e: React.KeyboardEvent) => { @@ -1118,7 +1124,8 @@ function _Chat() { }; const deleteMessage = (msgId?: string) => { - chatStore.updateCurrentSession( + chatStore.updateTargetSession( + session, (session) => (session.messages = session.messages.filter((m) => m.id !== msgId)), ); @@ -1185,7 +1192,7 @@ function _Chat() { }; const onPinMessage = (message: ChatMessage) => { - chatStore.updateCurrentSession((session) => + chatStore.updateTargetSession(session, (session) => session.mask.context.push(message), ); @@ -1711,14 +1718,17 @@ function _Chat() { }); } } - chatStore.updateCurrentSession((session) => { - const m = session.mask.context - .concat(session.messages) - .find((m) => m.id === message.id); - if (m) { - m.content = newContent; - } - }); + chatStore.updateTargetSession( + session, + (session) => { + const m = session.mask.context + .concat(session.messages) + .find((m) => m.id === message.id); + if (m) { + m.content = newContent; + } + }, + ); }} >
diff --git a/app/store/chat.ts b/app/store/chat.ts index 4208692e270..e2dbe90b026 100644 --- a/app/store/chat.ts +++ b/app/store/chat.ts @@ -357,7 +357,7 @@ export const useChatStore = createPersistStore( session.messages = session.messages.concat(); session.lastUpdate = Date.now(); }); - get().updateStat(message); + get().updateStat(message, targetSession); get().summarizeSession(false, targetSession); }, @@ -396,10 +396,10 @@ export const useChatStore = createPersistStore( // get recent messages const recentMessages = get().getMessagesWithMemory(); const sendMessages = recentMessages.concat(userMessage); - const messageIndex = get().currentSession().messages.length + 1; + const messageIndex = session.messages.length + 1; // save user's and bot's message - get().updateCurrentSession((session) => { + get().updateTargetSession(session, (session) => { const savedUserMessage = { ...userMessage, content: mContent, @@ -420,7 +420,7 @@ export const useChatStore = createPersistStore( if (message) { botMessage.content = message; } - get().updateCurrentSession((session) => { + get().updateTargetSession(session, (session) => { session.messages = session.messages.concat(); }); }, @@ -434,7 +434,7 @@ export const useChatStore = createPersistStore( }, onBeforeTool(tool: ChatMessageTool) { (botMessage.tools = botMessage?.tools || []).push(tool); - get().updateCurrentSession((session) => { + get().updateTargetSession(session, (session) => { session.messages = session.messages.concat(); }); }, @@ -444,7 +444,7 @@ export const useChatStore = createPersistStore( tools[i] = { ...tool }; } }); - get().updateCurrentSession((session) => { + get().updateTargetSession(session, (session) => { session.messages = session.messages.concat(); }); }, @@ -459,7 +459,7 @@ export const useChatStore = createPersistStore( botMessage.streaming = false; userMessage.isError = !isAborted; botMessage.isError = !isAborted; - get().updateCurrentSession((session) => { + get().updateTargetSession(session, (session) => { session.messages = session.messages.concat(); }); ChatControllerPool.remove( @@ -591,8 +591,8 @@ export const useChatStore = createPersistStore( set(() => ({ sessions })); }, - resetSession() { - get().updateCurrentSession((session) => { + resetSession(session: ChatSession) { + get().updateTargetSession(session, (session) => { session.messages = []; session.memoryPrompt = ""; }); @@ -736,19 +736,12 @@ export const useChatStore = createPersistStore( } }, - updateStat(message: ChatMessage) { - get().updateCurrentSession((session) => { + updateStat(message: ChatMessage, session: ChatSession) { + get().updateTargetSession(session, (session) => { session.stat.charCount += message.content.length; // TODO: should update chat count and word count }); }, - - updateCurrentSession(updater: (session: ChatSession) => void) { - const sessions = get().sessions; - const index = get().currentSessionIndex; - updater(sessions[index]); - set(() => ({ sessions })); - }, updateTargetSession( targetSession: ChatSession, updater: (session: ChatSession) => void, From 3086a2fa77d2815af05236bae4a13a4da387730b Mon Sep 17 00:00:00 2001 From: opchips Date: Wed, 6 Nov 2024 12:56:24 +0800 Subject: [PATCH 08/57] add claude35haiku not vision --- app/constant.ts | 1 + app/utils.ts | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/constant.ts b/app/constant.ts index 1d60e1ec663..d10e624cbf4 100644 --- a/app/constant.ts +++ b/app/constant.ts @@ -328,6 +328,7 @@ const anthropicModels = [ "claude-3-sonnet-20240229", "claude-3-opus-20240229", "claude-3-haiku-20240307", + "claude-3-5-haiku-20241022", "claude-3-5-sonnet-20240620", "claude-3-5-sonnet-20241022", "claude-3-5-sonnet-latest", diff --git a/app/utils.ts b/app/utils.ts index 2e1f9401607..2dd80b8a314 100644 --- a/app/utils.ts +++ b/app/utils.ts @@ -254,6 +254,7 @@ 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 excludeKeywords = ["claude-3-5-haiku-20241022"]; const visionKeywords = [ "vision", "claude-3", @@ -266,9 +267,10 @@ export function isVisionModel(model: string) { model.includes("gpt-4-turbo") && !model.includes("preview"); return ( - visionKeywords.some((keyword) => model.includes(keyword)) || - isGpt4Turbo || - isDalle3(model) + !excludeKeywords.some((keyword) => model.includes(keyword)) && + (visionKeywords.some((keyword) => model.includes(keyword)) || + isGpt4Turbo || + isDalle3(model)) ); } From adf7d8200b63ba9e389c3df2b801b82a272a85bf Mon Sep 17 00:00:00 2001 From: DDMeaqua Date: Wed, 6 Nov 2024 13:55:57 +0800 Subject: [PATCH 09/57] fix: glm chatpath --- app/constant.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/constant.ts b/app/constant.ts index 1d60e1ec663..406e95609a6 100644 --- a/app/constant.ts +++ b/app/constant.ts @@ -232,7 +232,7 @@ export const XAI = { export const ChatGLM = { ExampleEndpoint: CHATGLM_BASE_URL, - ChatPath: "/api/paas/v4/chat/completions", + ChatPath: "api/paas/v4/chat/completions", }; export const DEFAULT_INPUT_TEMPLATE = `{{input}}`; // input / time / model / lang From 85cdcab850cadbbd346d38b34603e3eb00e3e715 Mon Sep 17 00:00:00 2001 From: Dogtiti <499960698@qq.com> Date: Wed, 6 Nov 2024 14:53:08 +0800 Subject: [PATCH 10/57] fix: botMessage reply date --- app/store/chat.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/store/chat.ts b/app/store/chat.ts index e2dbe90b026..af4993d12ab 100644 --- a/app/store/chat.ts +++ b/app/store/chat.ts @@ -428,6 +428,7 @@ export const useChatStore = createPersistStore( botMessage.streaming = false; if (message) { botMessage.content = message; + botMessage.date = new Date().toLocaleString(); get().onNewMessage(botMessage, session); } ChatControllerPool.remove(session.id, botMessage.id); From e0bbb8bb68429d160c50af512eaa5181b50dc2c3 Mon Sep 17 00:00:00 2001 From: Dogtiti <499960698@qq.com> Date: Wed, 6 Nov 2024 16:58:26 +0800 Subject: [PATCH 11/57] style: improve classname by clsx --- app/components/auth.tsx | 6 +++-- app/components/button.tsx | 24 ++++++++++-------- app/components/chat-list.tsx | 13 +++++----- app/components/chat.tsx | 30 +++++++++++----------- app/components/exporter.tsx | 14 ++++++----- app/components/home.tsx | 17 ++++++++----- app/components/input-range.tsx | 3 ++- app/components/markdown.tsx | 12 ++++++--- app/components/mask.tsx | 3 ++- app/components/message-selector.tsx | 14 +++++------ app/components/new-chat.tsx | 5 +++- app/components/plugin.tsx | 8 ++++-- app/components/sd/sd-panel.tsx | 3 ++- app/components/sd/sd.tsx | 8 ++++-- app/components/sidebar.tsx | 17 +++++++------ app/components/ui-lib.tsx | 39 +++++++++++++++++------------ 16 files changed, 130 insertions(+), 86 deletions(-) diff --git a/app/components/auth.tsx b/app/components/auth.tsx index 539a52eecc9..5375bda3f70 100644 --- a/app/components/auth.tsx +++ b/app/components/auth.tsx @@ -18,6 +18,8 @@ import { trackSettingsPageGuideToCPaymentClick, trackAuthorizationPageButtonToCPaymentClick, } from "../utils/auth-settings-events"; +import clsx from "clsx"; + const storage = safeLocalStorage(); export function AuthPage() { @@ -54,7 +56,7 @@ export function AuthPage() { onClick={() => navigate(Path.Home)} >
-
+
@@ -163,7 +165,7 @@ function TopBanner() { onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} > -
+
{Locale.Auth.TopTips} diff --git a/app/components/button.tsx b/app/components/button.tsx index 87b4abd30f9..157d5d73da5 100644 --- a/app/components/button.tsx +++ b/app/components/button.tsx @@ -2,6 +2,7 @@ import * as React from "react"; import styles from "./button.module.scss"; import { CSSProperties } from "react"; +import clsx from "clsx"; export type ButtonType = "primary" | "danger" | null; @@ -22,12 +23,16 @@ export function IconButton(props: { }) { return (
diff --git a/app/components/chat-list.tsx b/app/components/chat-list.tsx index 03b1a5c8803..63dc4d5ff30 100644 --- a/app/components/chat-list.tsx +++ b/app/components/chat-list.tsx @@ -18,6 +18,7 @@ import { Mask } from "../store/mask"; import { useRef, useEffect } from "react"; import { showConfirm } from "./ui-lib"; import { useMobileScreen } from "../utils"; +import clsx from "clsx"; export function ChatItem(props: { onClick?: () => void; @@ -45,11 +46,11 @@ export function ChatItem(props: { {(provided) => (
{ draggableRef.current = ele; @@ -63,7 +64,7 @@ export function ChatItem(props: { > {props.narrow ? (
-
+
{props.showToast && context.length > 0 && (
props.setShowModal(true)} > @@ -332,10 +333,9 @@ export function PromptHints(props: { {props.prompts.map((prompt, i) => (
props.onPromptSelect(prompt)} onMouseEnter={() => setSelectIndex(i)} @@ -395,7 +395,7 @@ export function ChatAction(props: { return (
{ props.onClick(); setTimeout(updateWidth, 1); @@ -1596,9 +1596,12 @@ function _Chat() {
)} -
+
setIsEditingMessage(true)} > {!session.topic ? DEFAULT_TOPIC : session.topic} @@ -1872,7 +1875,7 @@ function _Chat() { )} {getMessageImages(message).length > 1 && (