From be9774943bc17e30111ccf6ec1eb8242e61f3fa1 Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Sun, 12 Nov 2023 00:29:36 +0800 Subject: [PATCH 1/4] feat: #3224 auto switch to first avaliable model --- app/components/chat.tsx | 22 +++++++++++++++++++--- app/utils/hooks.ts | 2 +- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/app/components/chat.tsx b/app/components/chat.tsx index c27c3eee464..48f76e8ab48 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -431,11 +431,27 @@ export function ChatActions(props: { // switch model const currentModel = chatStore.currentSession().mask.modelConfig.model; - const models = useAllModels() - .filter((m) => m.available) - .map((m) => m.name); + const allModels = useAllModels(); + const models = useMemo( + () => allModels.filter((m) => m.available).map((m) => m.name), + [allModels], + ); const [showModelSelector, setShowModelSelector] = useState(false); + useEffect(() => { + // if current model is not available + // switch to first available model + const isUnavaliableModel = !models.includes(currentModel); + if (isUnavaliableModel && models.length > 0) { + const nextModel = models[0] as ModelType; + chatStore.updateCurrentSession( + (session) => (session.mask.modelConfig.model = nextModel), + ); + showToast(nextModel); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentModel, models]); + return (
{couldStop && ( diff --git a/app/utils/hooks.ts b/app/utils/hooks.ts index f6bfae67323..35d1f53a4c9 100644 --- a/app/utils/hooks.ts +++ b/app/utils/hooks.ts @@ -8,7 +8,7 @@ export function useAllModels() { const models = useMemo(() => { return collectModels( configStore.models, - [accessStore.customModels, configStore.customModels].join(","), + [configStore.customModels, accessStore.customModels].join(","), ); }, [accessStore.customModels, configStore.customModels, configStore.models]); From a5a1f2e8ad781e0c82a6f775746286477d806545 Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Sun, 12 Nov 2023 00:46:21 +0800 Subject: [PATCH 2/4] feat: CUSTOM_MODELS support mapper --- app/api/common.ts | 2 +- app/components/chat.tsx | 10 +++++----- app/utils/model.ts | 32 +++++++++++++++++++++----------- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/app/api/common.ts b/app/api/common.ts index adec611b2b3..dd1cc0bb80e 100644 --- a/app/api/common.ts +++ b/app/api/common.ts @@ -81,7 +81,7 @@ export async function requestOpenai(req: NextRequest) { const jsonBody = JSON.parse(clonedBody) as { model?: string }; // not undefined and is false - if (modelTable[jsonBody?.model ?? ""] === false) { + if (modelTable[jsonBody?.model ?? ""].available === false) { return NextResponse.json( { error: true, diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 48f76e8ab48..a088483e7b6 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -433,7 +433,7 @@ export function ChatActions(props: { const currentModel = chatStore.currentSession().mask.modelConfig.model; const allModels = useAllModels(); const models = useMemo( - () => allModels.filter((m) => m.available).map((m) => m.name), + () => allModels.filter((m) => m.available), [allModels], ); const [showModelSelector, setShowModelSelector] = useState(false); @@ -441,9 +441,9 @@ export function ChatActions(props: { useEffect(() => { // if current model is not available // switch to first available model - const isUnavaliableModel = !models.includes(currentModel); + const isUnavaliableModel = !models.some((m) => m.name === currentModel); if (isUnavaliableModel && models.length > 0) { - const nextModel = models[0] as ModelType; + const nextModel = models[0].name as ModelType; chatStore.updateCurrentSession( (session) => (session.mask.modelConfig.model = nextModel), ); @@ -531,8 +531,8 @@ export function ChatActions(props: { ({ - title: m, - value: m, + title: m.displayName, + value: m.name, }))} onClose={() => setShowModelSelector(false)} onSelection={(s) => { diff --git a/app/utils/model.ts b/app/utils/model.ts index 23090f9d2f3..d5c009c02d2 100644 --- a/app/utils/model.ts +++ b/app/utils/model.ts @@ -4,21 +4,34 @@ export function collectModelTable( models: readonly LLMModel[], customModels: string, ) { - const modelTable: Record = {}; + const modelTable: Record< + string, + { available: boolean; name: string; displayName: string } + > = {}; // default models - models.forEach((m) => (modelTable[m.name] = m.available)); + models.forEach( + (m) => + (modelTable[m.name] = { + ...m, + displayName: m.name, + }), + ); // server custom models customModels .split(",") .filter((v) => !!v && v.length > 0) .map((m) => { - if (m.startsWith("+")) { - modelTable[m.slice(1)] = true; - } else if (m.startsWith("-")) { - modelTable[m.slice(1)] = false; - } else modelTable[m] = true; + const available = !m.startsWith("-"); + const nameConfig = + m.startsWith("+") || m.startsWith("-") ? m.slice(1) : m; + const [name, displayName] = nameConfig.split(":"); + modelTable[name] = { + name, + displayName: displayName || name, + available, + }; }); return modelTable; } @@ -31,10 +44,7 @@ export function collectModels( customModels: string, ) { const modelTable = collectModelTable(models, customModels); - const allModels = Object.keys(modelTable).map((m) => ({ - name: m, - available: modelTable[m], - })); + const allModels = Object.values(modelTable); return allModels; } From 64647b0bb3d06b5c0ab17c96cf245753d8f4b48a Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Sun, 12 Nov 2023 00:49:58 +0800 Subject: [PATCH 3/4] chore: update doc for mapped `CUSTOM_MODELS` --- README.md | 4 ++-- README_CN.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a7c862b4099..abe93306180 100644 --- a/README.md +++ b/README.md @@ -216,9 +216,9 @@ If you want to disable parse settings from url, set this to 1. ### `CUSTOM_MODELS` (optional) > Default: Empty -> Example: `+llama,+claude-2,-gpt-3.5-turbo` means add `llama, claude-2` to model list, and remove `gpt-3.5-turbo` from list. +> Example: `+llama,+claude-2,-gpt-3.5-turbo,gpt-4-1106-preview:gpt-4-turbo` means add `llama, claude-2` to model list, and remove `gpt-3.5-turbo` from list, and display `gpt-4-1106-preview` as `gpt-4-turbo`. -To control custom models, use `+` to add a custom model, use `-` to hide a model, separated by comma. +To control custom models, use `+` to add a custom model, use `-` to hide a model, use `name:displayName` to customize model name, separated by comma. ## Requirements diff --git a/README_CN.md b/README_CN.md index 604771c5251..dde8c19b352 100644 --- a/README_CN.md +++ b/README_CN.md @@ -122,9 +122,9 @@ Azure Api 版本,你可以在这里找到:[Azure 文档](https://learn.micro ### `CUSTOM_MODELS` (可选) -> 示例:`+qwen-7b-chat,+glm-6b,-gpt-3.5-turbo` 表示增加 `qwen-7b-chat` 和 `glm-6b` 到模型列表,而从列表中删除 `gpt-3.5-turbo`。 +> 示例:`+qwen-7b-chat,+glm-6b,-gpt-3.5-turbo,gpt-4-1106-preview:gpt-4-turbo` 表示增加 `qwen-7b-chat` 和 `glm-6b` 到模型列表,而从列表中删除 `gpt-3.5-turbo`,并将 `gpt-4-1106-preview` 模型名字展示为 `gpt-4-turbo`。 -用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,用英文逗号隔开。 +用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名:展示名` 来自定义模型的展示名,用英文逗号隔开。 ## 开发 From be6d45e49f1df90daba4625117b95903189891c2 Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Sun, 12 Nov 2023 01:21:39 +0800 Subject: [PATCH 4/4] feat: close #3222 share message list should start from clear context index --- app/components/exporter.tsx | 29 ++++++++++++++----- app/components/message-selector.module.scss | 10 +++++-- app/components/message-selector.tsx | 31 ++++++++++++++++----- app/components/ui-lib.tsx | 4 ++- 4 files changed, 57 insertions(+), 17 deletions(-) diff --git a/app/components/exporter.tsx b/app/components/exporter.tsx index 0a885d87463..435e24953e6 100644 --- a/app/components/exporter.tsx +++ b/app/components/exporter.tsx @@ -27,7 +27,7 @@ import { Avatar } from "./emoji"; import dynamic from "next/dynamic"; import NextImage from "next/image"; -import { toBlob, toJpeg, toPng } from "html-to-image"; +import { toBlob, toPng } from "html-to-image"; import { DEFAULT_MASK_AVATAR } from "../store/mask"; import { api } from "../client/api"; import { prettyObject } from "../utils/format"; @@ -41,7 +41,22 @@ const Markdown = dynamic(async () => (await import("./markdown")).Markdown, { export function ExportMessageModal(props: { onClose: () => void }) { return (
- + + 只有清除上下文之后的消息会被展示 +
+ } + >
@@ -149,7 +164,7 @@ export function MessageExporter() { if (exportConfig.includeContext) { ret.push(...session.mask.context); } - ret.push(...session.messages.filter((m, i) => selection.has(m.id))); + ret.push(...session.messages.filter((m) => selection.has(m.id))); return ret; }, [ exportConfig.includeContext, @@ -437,13 +452,13 @@ export function ImagePreviewer(props: { showToast(Locale.Export.Image.Toast); const dom = previewRef.current; if (!dom) return; - + const isApp = getClientConfig()?.isApp; - + try { const blob = await toPng(dom); if (!blob) return; - + if (isMobile || (isApp && window.__TAURI__)) { if (isApp && window.__TAURI__) { const result = await window.__TAURI__.dialog.save({ @@ -459,7 +474,7 @@ export function ImagePreviewer(props: { }, ], }); - + if (result !== null) { const response = await fetch(blob); const buffer = await response.arrayBuffer(); diff --git a/app/components/message-selector.module.scss b/app/components/message-selector.module.scss index b4ba1a1412a..c8defb6b027 100644 --- a/app/components/message-selector.module.scss +++ b/app/components/message-selector.module.scss @@ -58,8 +58,8 @@ } .body { - flex-grow: 1; - max-width: calc(100% - 40px); + flex: 1; + max-width: calc(100% - 80px); .date { font-size: 12px; @@ -71,6 +71,12 @@ font-size: 12px; } } + + .checkbox { + display: flex; + justify-content: flex-end; + flex: 1; + } } } } diff --git a/app/components/message-selector.tsx b/app/components/message-selector.tsx index cadf52e643e..3d2321d0962 100644 --- a/app/components/message-selector.tsx +++ b/app/components/message-selector.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { ChatMessage, useAppConfig, useChatStore } from "../store"; import { Updater } from "../typing"; import { IconButton } from "./button"; @@ -73,11 +73,23 @@ export function MessageSelector(props: { const chatStore = useChatStore(); const session = chatStore.currentSession(); const isValid = (m: ChatMessage) => m.content && !m.isError && !m.streaming; - const messages = session.messages.filter( - (m, i) => - m.id && // message must have id - isValid(m) && - (i >= session.messages.length - 1 || isValid(session.messages[i + 1])), + const allMessages = useMemo(() => { + let startIndex = Math.max(0, session.clearContextIndex ?? 0); + if (startIndex === session.messages.length - 1) { + startIndex = 0; + } + return session.messages.slice(startIndex); + }, [session.messages, session.clearContextIndex]); + + const messages = useMemo( + () => + allMessages.filter( + (m, i) => + m.id && // message must have id + isValid(m) && + (i >= allMessages.length - 1 || isValid(allMessages[i + 1])), + ), + [allMessages], ); const messageCount = messages.length; const config = useAppConfig(); @@ -176,6 +188,8 @@ export function MessageSelector(props: {
{messages.map((m, i) => { if (!isInSearchResult(m.id!)) return null; + const id = m.id ?? i; + const isSelected = props.selection.has(id); return (
{ props.updateSelection((selection) => { - const id = m.id ?? i; selection.has(id) ? selection.delete(id) : selection.add(id); }); onClickIndex(i); @@ -206,6 +219,10 @@ export function MessageSelector(props: { {m.content}
+ +
+ +
); })} diff --git a/app/components/ui-lib.tsx b/app/components/ui-lib.tsx index 0c927728a4e..f7e326fd318 100644 --- a/app/components/ui-lib.tsx +++ b/app/components/ui-lib.tsx @@ -97,8 +97,9 @@ export function Loading() { interface ModalProps { title: string; children?: any; - actions?: JSX.Element[]; + actions?: React.ReactNode[]; defaultMax?: boolean; + footer?: React.ReactNode; onClose?: () => void; } export function Modal(props: ModalProps) { @@ -147,6 +148,7 @@ export function Modal(props: ModalProps) {
{props.children}
+ {props.footer}
{props.actions?.map((action, i) => (