Merge branch 'ChatGPTNextWeb:main' into main

This commit is contained in:
玖然
2025-03-10 22:40:18 +08:00
committed by GitHub
48 changed files with 2233 additions and 342 deletions

View File

@@ -6,8 +6,21 @@ import EmojiPicker, {
import { ModelType } from "../store";
import BotIcon from "../icons/bot.svg";
import BlackBotIcon from "../icons/black-bot.svg";
import BotIconDefault from "../icons/llm-icons/default.svg";
import BotIconOpenAI from "../icons/llm-icons/openai.svg";
import BotIconGemini from "../icons/llm-icons/gemini.svg";
import BotIconGemma from "../icons/llm-icons/gemma.svg";
import BotIconClaude from "../icons/llm-icons/claude.svg";
import BotIconMeta from "../icons/llm-icons/meta.svg";
import BotIconMistral from "../icons/llm-icons/mistral.svg";
import BotIconDeepseek from "../icons/llm-icons/deepseek.svg";
import BotIconMoonshot from "../icons/llm-icons/moonshot.svg";
import BotIconQwen from "../icons/llm-icons/qwen.svg";
import BotIconWenxin from "../icons/llm-icons/wenxin.svg";
import BotIconGrok from "../icons/llm-icons/grok.svg";
import BotIconHunyuan from "../icons/llm-icons/hunyuan.svg";
import BotIconDoubao from "../icons/llm-icons/doubao.svg";
import BotIconChatglm from "../icons/llm-icons/chatglm.svg";
export function getEmojiUrl(unified: string, style: EmojiStyle) {
// Whoever owns this Content Delivery Network (CDN), I am using your CDN to serve emojis
@@ -33,17 +46,55 @@ export function AvatarPicker(props: {
}
export function Avatar(props: { model?: ModelType; avatar?: string }) {
let LlmIcon = BotIconDefault;
if (props.model) {
const modelName = props.model.toLowerCase();
if (
modelName.startsWith("gpt") ||
modelName.startsWith("chatgpt") ||
modelName.startsWith("dall-e") ||
modelName.startsWith("dalle") ||
modelName.startsWith("o1") ||
modelName.startsWith("o3")
) {
LlmIcon = BotIconOpenAI;
} else if (modelName.startsWith("gemini")) {
LlmIcon = BotIconGemini;
} else if (modelName.startsWith("gemma")) {
LlmIcon = BotIconGemma;
} else if (modelName.startsWith("claude")) {
LlmIcon = BotIconClaude;
} else if (modelName.includes("llama")) {
LlmIcon = BotIconMeta;
} else if (modelName.startsWith("mixtral") || modelName.startsWith("codestral")) {
LlmIcon = BotIconMistral;
} else if (modelName.includes("deepseek")) {
LlmIcon = BotIconDeepseek;
} else if (modelName.startsWith("moonshot")) {
LlmIcon = BotIconMoonshot;
} else if (modelName.startsWith("qwen")) {
LlmIcon = BotIconQwen;
} else if (modelName.startsWith("ernie")) {
LlmIcon = BotIconWenxin;
} else if (modelName.startsWith("grok")) {
LlmIcon = BotIconGrok;
} else if (modelName.startsWith("hunyuan")) {
LlmIcon = BotIconHunyuan;
} else if (modelName.startsWith("doubao") || modelName.startsWith("ep-")) {
LlmIcon = BotIconDoubao;
} else if (
modelName.includes("glm") ||
modelName.startsWith("cogview-") ||
modelName.startsWith("cogvideox-")
) {
LlmIcon = BotIconChatglm;
}
return (
<div className="no-dark">
{props.model?.startsWith("gpt-4") ||
props.model?.startsWith("chatgpt-4o") ||
props.model?.startsWith("o1") ||
props.model?.startsWith("o3") ? (
<BlackBotIcon className="user-avatar" />
) : (
<BotIcon className="user-avatar" />
)}
<LlmIcon className="user-avatar" width={30} height={30} />
</div>
);
}

View File

@@ -23,7 +23,6 @@ import CopyIcon from "../icons/copy.svg";
import LoadingIcon from "../icons/three-dots.svg";
import ChatGptIcon from "../icons/chatgpt.png";
import ShareIcon from "../icons/share.svg";
import BotIcon from "../icons/bot.png";
import DownloadIcon from "../icons/download.svg";
import { useEffect, useMemo, useRef, useState } from "react";
@@ -33,13 +32,13 @@ import dynamic from "next/dynamic";
import NextImage from "next/image";
import { toBlob, toPng } from "html-to-image";
import { DEFAULT_MASK_AVATAR } from "../store/mask";
import { prettyObject } from "../utils/format";
import { EXPORT_MESSAGE_CLASS_NAME } from "../constant";
import { getClientConfig } from "../config/client";
import { type ClientApi, getClientApi } from "../client/api";
import { getMessageTextContent } from "../utils";
import { MaskAvatar } from "./mask";
import clsx from "clsx";
const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
@@ -407,22 +406,6 @@ export function PreviewActions(props: {
);
}
function ExportAvatar(props: { avatar: string }) {
if (props.avatar === DEFAULT_MASK_AVATAR) {
return (
<img
src={BotIcon.src}
width={30}
height={30}
alt="bot"
className="user-avatar"
/>
);
}
return <Avatar avatar={props.avatar} />;
}
export function ImagePreviewer(props: {
messages: ChatMessage[];
topic: string;
@@ -546,9 +529,12 @@ export function ImagePreviewer(props: {
github.com/ChatGPTNextWeb/ChatGPT-Next-Web
</div>
<div className={styles["icons"]}>
<ExportAvatar avatar={config.avatar} />
<MaskAvatar avatar={config.avatar} />
<span className={styles["icon-space"]}>&</span>
<ExportAvatar avatar={mask.avatar} />
<MaskAvatar
avatar={mask.avatar}
model={session.mask.modelConfig.model}
/>
</div>
</div>
<div>
@@ -576,9 +562,14 @@ export function ImagePreviewer(props: {
key={i}
>
<div className={styles["avatar"]}>
<ExportAvatar
avatar={m.role === "user" ? config.avatar : mask.avatar}
/>
{m.role === "user" ? (
<Avatar avatar={config.avatar}></Avatar>
) : (
<MaskAvatar
avatar={session.mask.avatar}
model={m.model || session.mask.modelConfig.model}
/>
)}
</div>
<div className={styles["body"]}>

View File

@@ -74,6 +74,7 @@ import {
SAAS_CHAT_URL,
ChatGLM,
DeepSeek,
SiliconFlow,
} from "../constant";
import { Prompt, SearchService, usePromptStore } from "../store/prompt";
import { ErrorBoundary } from "./error";
@@ -1318,6 +1319,46 @@ export function Settings() {
</ListItem>
</>
);
const siliconflowConfigComponent = accessStore.provider ===
ServiceProvider.SiliconFlow && (
<>
<ListItem
title={Locale.Settings.Access.SiliconFlow.Endpoint.Title}
subTitle={
Locale.Settings.Access.SiliconFlow.Endpoint.SubTitle +
SiliconFlow.ExampleEndpoint
}
>
<input
aria-label={Locale.Settings.Access.SiliconFlow.Endpoint.Title}
type="text"
value={accessStore.siliconflowUrl}
placeholder={SiliconFlow.ExampleEndpoint}
onChange={(e) =>
accessStore.update(
(access) => (access.siliconflowUrl = e.currentTarget.value),
)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.Access.SiliconFlow.ApiKey.Title}
subTitle={Locale.Settings.Access.SiliconFlow.ApiKey.SubTitle}
>
<PasswordInput
aria-label={Locale.Settings.Access.SiliconFlow.ApiKey.Title}
value={accessStore.siliconflowApiKey}
type="text"
placeholder={Locale.Settings.Access.SiliconFlow.ApiKey.Placeholder}
onChange={(e) => {
accessStore.update(
(access) => (access.siliconflowApiKey = e.currentTarget.value),
);
}}
/>
</ListItem>
</>
);
const stabilityConfigComponent = accessStore.provider ===
ServiceProvider.Stability && (
@@ -1780,6 +1821,7 @@ export function Settings() {
{lflytekConfigComponent}
{XAIConfigComponent}
{chatglmConfigComponent}
{siliconflowConfigComponent}
</>
)}
</>

View File

@@ -23,6 +23,7 @@ import React, {
useRef,
} from "react";
import { IconButton } from "./button";
import { Avatar } from "./emoji";
import clsx from "clsx";
export function Popover(props: {
@@ -522,6 +523,7 @@ export function Selector<T>(props: {
key={i}
title={item.title}
subTitle={item.subTitle}
icon={<Avatar model={item.value as string} />}
onClick={(e) => {
if (item.disable) {
e.stopPropagation();