mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-05-21 21:20:19 +09:00
feat(model): 增加模型服务商选择功能
- 在模型配置中添加服务商选择选项 - 实现服务商切换时自动选择该服务商下的第一个可用模型 - 优化模型选择界面,仅显示当前选中服务商的模型 - 添加摘要模型服务商选择功能 - 移除授权页面的 Saas 相关功能 - 调整聊天页面的模型选择逻辑
This commit is contained in:
parent
c9ad2dd607
commit
5b8b6de3b5
@ -2,22 +2,16 @@ import styles from "./auth.module.scss";
|
||||
import { IconButton } from "./button";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Path, SAAS_CHAT_URL } from "../constant";
|
||||
import { Path } from "../constant";
|
||||
import { useAccessStore } from "../store";
|
||||
import Locale from "../locales";
|
||||
import Delete from "../icons/close.svg";
|
||||
import Arrow from "../icons/arrow.svg";
|
||||
import Logo from "../icons/logo.svg";
|
||||
import { useMobileScreen } from "@/app/utils";
|
||||
import BotIcon from "../icons/bot.svg";
|
||||
import { getClientConfig } from "../config/client";
|
||||
import { PasswordInput } from "./ui-lib";
|
||||
import LeftIcon from "@/app/icons/left.svg";
|
||||
import { safeLocalStorage } from "@/app/utils";
|
||||
import {
|
||||
trackSettingsPageGuideToCPaymentClick,
|
||||
trackAuthorizationPageButtonToCPaymentClick,
|
||||
} from "../utils/auth-settings-events";
|
||||
|
||||
import clsx from "clsx";
|
||||
|
||||
const storage = safeLocalStorage();
|
||||
@ -25,19 +19,8 @@ const storage = safeLocalStorage();
|
||||
export function AuthPage() {
|
||||
const navigate = useNavigate();
|
||||
const accessStore = useAccessStore();
|
||||
const goHome = () => navigate(Path.Home);
|
||||
const goChat = () => navigate(Path.Chat);
|
||||
const goSaas = () => {
|
||||
trackAuthorizationPageButtonToCPaymentClick();
|
||||
window.location.href = SAAS_CHAT_URL;
|
||||
};
|
||||
|
||||
const resetAccessCode = () => {
|
||||
accessStore.update((access) => {
|
||||
access.openaiApiKey = "";
|
||||
access.accessCode = "";
|
||||
});
|
||||
}; // Reset access code to empty string
|
||||
// Reset access code to empty string
|
||||
|
||||
useEffect(() => {
|
||||
if (getClientConfig()?.isApp) {
|
||||
@ -115,17 +98,10 @@ export function AuthPage() {
|
||||
type="primary"
|
||||
onClick={goChat}
|
||||
/>
|
||||
<IconButton
|
||||
text={Locale.Auth.SaasTips}
|
||||
onClick={() => {
|
||||
goSaas();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TopBanner() {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const [isVisible, setIsVisible] = useState(true);
|
||||
@ -159,31 +135,5 @@ function TopBanner() {
|
||||
if (!isVisible) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className={styles["top-banner"]}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
<div className={clsx(styles["top-banner-inner"], "no-dark")}>
|
||||
<Logo className={styles["top-banner-logo"]}></Logo>
|
||||
<span>
|
||||
{Locale.Auth.TopTips}
|
||||
<a
|
||||
href={SAAS_CHAT_URL}
|
||||
rel="stylesheet"
|
||||
onClick={() => {
|
||||
trackSettingsPageGuideToCPaymentClick();
|
||||
}}
|
||||
>
|
||||
{Locale.Settings.Access.SaasStart.ChatNow}
|
||||
<Arrow style={{ marginLeft: "4px" }} />
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
{(isHovered || isMobile) && (
|
||||
<Delete className={styles["top-banner-close"]} onClick={handleClose} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
@ -67,14 +67,14 @@ import {
|
||||
copyToClipboard,
|
||||
getMessageImages,
|
||||
getMessageTextContent,
|
||||
getModelSizes,
|
||||
isDalle3,
|
||||
isVisionModel,
|
||||
safeLocalStorage,
|
||||
getModelSizes,
|
||||
supportsCustomSize,
|
||||
useMobileScreen,
|
||||
selectOrCopy,
|
||||
showPlugins,
|
||||
supportsCustomSize,
|
||||
useMobileScreen,
|
||||
} from "../utils";
|
||||
|
||||
import { uploadImage as uploadImageRemote } from "@/app/utils/chat";
|
||||
@ -535,11 +535,10 @@ export function ChatActions(props: {
|
||||
const defaultModel = filteredModels.find((m) => m.isDefault);
|
||||
|
||||
if (defaultModel) {
|
||||
const arr = [
|
||||
return [
|
||||
defaultModel,
|
||||
...filteredModels.filter((m) => m !== defaultModel),
|
||||
];
|
||||
return arr;
|
||||
} else {
|
||||
return filteredModels;
|
||||
}
|
||||
@ -553,6 +552,7 @@ export function ChatActions(props: {
|
||||
return model?.displayName ?? "";
|
||||
}, [models, currentModel, currentProviderName]);
|
||||
const [showModelSelector, setShowModelSelector] = useState(false);
|
||||
const [showProviderSelector, setShowProviderSelector] = useState(false);
|
||||
const [showPluginSelector, setShowPluginSelector] = useState(false);
|
||||
const [showUploadImage, setShowUploadImage] = useState(false);
|
||||
|
||||
@ -673,23 +673,55 @@ export function ChatActions(props: {
|
||||
}}
|
||||
/>
|
||||
|
||||
<ChatAction
|
||||
onClick={() => setShowProviderSelector(true)}
|
||||
text={currentProviderName}
|
||||
icon={<BrainIcon />}
|
||||
/>
|
||||
|
||||
<ChatAction
|
||||
onClick={() => setShowModelSelector(true)}
|
||||
text={currentModelName}
|
||||
icon={<RobotIcon />}
|
||||
/>
|
||||
|
||||
{showProviderSelector && (
|
||||
<Selector
|
||||
defaultSelectedValue={currentProviderName}
|
||||
items={Object.entries(ServiceProvider).map(([name, value]) => ({
|
||||
title: name,
|
||||
value: value,
|
||||
}))}
|
||||
onClose={() => setShowProviderSelector(false)}
|
||||
onSelection={(s) => {
|
||||
if (s.length === 0) return;
|
||||
const provider = s[0] as ServiceProvider;
|
||||
chatStore.updateTargetSession(session, (session) => {
|
||||
session.mask.modelConfig.providerName = provider;
|
||||
const filteredModels = models.filter(
|
||||
(m) => m.available && m.provider?.providerName === provider,
|
||||
);
|
||||
if (filteredModels.length > 0) {
|
||||
// 选择新的服务商后,自动选择该服务商下的第一个模型
|
||||
session.mask.modelConfig.model = filteredModels[0]
|
||||
.name as ModelType;
|
||||
session.mask.syncGlobalConfig = false;
|
||||
}
|
||||
});
|
||||
showToast(s[0]);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showModelSelector && (
|
||||
<Selector
|
||||
defaultSelectedValue={`${currentModel}@${currentProviderName}`}
|
||||
items={models.map((m) => ({
|
||||
title: `${m.displayName}${
|
||||
m?.provider?.providerName
|
||||
? " (" + m?.provider?.providerName + ")"
|
||||
: ""
|
||||
}`,
|
||||
value: `${m.name}@${m?.provider?.providerName}`,
|
||||
}))}
|
||||
items={models
|
||||
.filter((m) => m.provider?.providerName === currentProviderName)
|
||||
.map((m) => ({
|
||||
title: m.displayName,
|
||||
value: `${m.name}@${m?.provider?.providerName}`,
|
||||
}))}
|
||||
onClose={() => setShowModelSelector(false)}
|
||||
onSelection={(s) => {
|
||||
if (s.length === 0) return;
|
||||
|
@ -5,7 +5,6 @@ import Locale from "../locales";
|
||||
import { InputRange } from "./input-range";
|
||||
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";
|
||||
|
||||
@ -14,15 +13,43 @@ export function ModelConfigList(props: {
|
||||
updateConfig: (updater: (config: ModelConfig) => void) => void;
|
||||
}) {
|
||||
const allModels = useAllModels();
|
||||
const groupModels = groupBy(
|
||||
allModels.filter((v) => v.available),
|
||||
"provider.providerName",
|
||||
const filteredModels = allModels.filter(
|
||||
(v) =>
|
||||
v.available &&
|
||||
v.provider?.providerName === props.modelConfig.providerName,
|
||||
);
|
||||
const value = `${props.modelConfig.model}@${props.modelConfig?.providerName}`;
|
||||
const compressModelValue = `${props.modelConfig.compressModel}@${props.modelConfig?.compressProviderName}`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ListItem title={Locale.Settings.Access.Provider.Title}>
|
||||
<Select
|
||||
aria-label={Locale.Settings.Access.Provider.Title}
|
||||
value={props.modelConfig.providerName}
|
||||
onChange={(e) => {
|
||||
const provider = e.currentTarget.value as ServiceProvider;
|
||||
props.updateConfig((config) => {
|
||||
config.providerName = provider;
|
||||
const firstModelForProvider = allModels.find(
|
||||
(m) => m.available && m.provider?.providerName === provider,
|
||||
);
|
||||
if (firstModelForProvider) {
|
||||
config.model = ModalConfigValidator.model(
|
||||
firstModelForProvider.name,
|
||||
);
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
{Object.entries(ServiceProvider).map(([k, v]) => (
|
||||
<option value={v} key={k}>
|
||||
{k}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</ListItem>
|
||||
|
||||
<ListItem title={Locale.Settings.Model}>
|
||||
<Select
|
||||
aria-label={Locale.Settings.Model}
|
||||
@ -38,14 +65,10 @@ export function ModelConfigList(props: {
|
||||
});
|
||||
}}
|
||||
>
|
||||
{Object.keys(groupModels).map((providerName, index) => (
|
||||
<optgroup label={providerName} key={index}>
|
||||
{groupModels[providerName].map((v, i) => (
|
||||
<option value={`${v.name}@${v.provider?.providerName}`} key={i}>
|
||||
{v.displayName}
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
{filteredModels.map((v, i) => (
|
||||
<option value={`${v.name}@${v.provider?.providerName}`} key={i}>
|
||||
{v.displayName}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</ListItem>
|
||||
@ -241,6 +264,37 @@ export function ModelConfigList(props: {
|
||||
}
|
||||
></input>
|
||||
</ListItem>
|
||||
<ListItem title={Locale.Settings.CompressProvider.Title}>
|
||||
<Select
|
||||
aria-label={Locale.Settings.CompressProvider.Title}
|
||||
value={
|
||||
props.modelConfig.compressProviderName || ServiceProvider.OpenAI
|
||||
}
|
||||
onChange={(e) => {
|
||||
const provider = e.currentTarget.value as ServiceProvider;
|
||||
props.updateConfig((config) => {
|
||||
config.compressProviderName = provider;
|
||||
// 如果选择了新的提供商,自动选择该提供商的第一个可用模型
|
||||
if (provider) {
|
||||
const firstModelForProvider = allModels.find(
|
||||
(m) => m.available && m.provider?.providerName === provider,
|
||||
);
|
||||
if (firstModelForProvider) {
|
||||
config.compressModel = ModalConfigValidator.model(
|
||||
firstModelForProvider.name,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
{Object.entries(ServiceProvider).map(([k, v]) => (
|
||||
<option value={v} key={k}>
|
||||
{k}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</ListItem>
|
||||
<ListItem
|
||||
title={Locale.Settings.CompressModel.Title}
|
||||
subTitle={Locale.Settings.CompressModel.SubTitle}
|
||||
@ -260,10 +314,19 @@ export function ModelConfigList(props: {
|
||||
}}
|
||||
>
|
||||
{allModels
|
||||
.filter((v) => v.available)
|
||||
.filter(
|
||||
(v) =>
|
||||
v.available &&
|
||||
(!props.modelConfig.compressProviderName ||
|
||||
v.provider?.providerName ===
|
||||
props.modelConfig.compressProviderName),
|
||||
)
|
||||
.map((v, i) => (
|
||||
<option value={`${v.name}@${v.provider?.providerName}`} key={i}>
|
||||
{v.displayName}({v.provider?.providerName})
|
||||
{v.displayName}
|
||||
{!props.modelConfig.compressProviderName
|
||||
? `(${v.provider?.providerName})`
|
||||
: ""}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
|
@ -25,9 +25,6 @@ const cn = {
|
||||
Input: "在此处填写访问码",
|
||||
Confirm: "确认",
|
||||
Later: "稍后再说",
|
||||
// SaasTips: "配置太麻烦,想要立即使用",
|
||||
// TopTips:
|
||||
// "🥳 NextChat AI 首发优惠,立刻解锁 OpenAI o1, GPT-4o, Claude-3.5 等最新大模型",
|
||||
},
|
||||
ChatItem: {
|
||||
ChatItemCount: (count: number) => `${count} 条对话`,
|
||||
@ -540,14 +537,15 @@ const cn = {
|
||||
},
|
||||
},
|
||||
|
||||
// 增加厂商选项
|
||||
ServiceProvider: "服务提供商",
|
||||
|
||||
Model: "模型 (model)",
|
||||
CompressModel: {
|
||||
Title: "对话摘要模型",
|
||||
SubTitle: "用于压缩历史记录、生成对话标题的模型",
|
||||
},
|
||||
CompressProvider: {
|
||||
Title: "摘要模型服务商",
|
||||
SubTitle: "选择生成摘要的模型服务商",
|
||||
},
|
||||
Temperature: {
|
||||
Title: "随机性 (temperature)",
|
||||
SubTitle: "值越大,回复越随机",
|
||||
@ -629,7 +627,7 @@ const cn = {
|
||||
Prompt: {
|
||||
History: (content: string) => "这是历史聊天总结作为前情提要:" + content,
|
||||
Topic:
|
||||
"使用四到五个字直接返回这句话的简要主题,不要解释、不要标点、不要语气词、不要多余文本,不要加粗,如果没有主题,请直接返回“闲聊”",
|
||||
'使用四到五个字直接返回这句话的简要主题,不要解释、不要标点、不要语气词、不要多余文本,不要加粗,如果没有主题,请直接返回"闲聊"',
|
||||
Summarize:
|
||||
"简要总结一下对话内容,用作后续的上下文提示 prompt,控制在 200 字以内",
|
||||
},
|
||||
|
@ -26,9 +26,6 @@ const en: LocaleType = {
|
||||
Input: "access code",
|
||||
Confirm: "Confirm",
|
||||
Later: "Later",
|
||||
SaasTips: "Too Complex, Use Immediately Now",
|
||||
TopTips:
|
||||
"🥳 NextChat AI launch promotion: Instantly unlock the latest models like OpenAI o1, GPT-4o, Claude-3.5!",
|
||||
},
|
||||
ChatItem: {
|
||||
ChatItemCount: (count: number) => `${count} messages`,
|
||||
@ -626,6 +623,10 @@ const en: LocaleType = {
|
||||
SubTitle: "Higher values result in more random responses",
|
||||
},
|
||||
},
|
||||
CompressProvider: {
|
||||
Title: "Summary Model Provider",
|
||||
SubTitle: "Select a provider for the summary model",
|
||||
},
|
||||
},
|
||||
Store: {
|
||||
DefaultTopic: "New Conversation",
|
||||
|
Loading…
Reference in New Issue
Block a user