mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2026-02-19 12:54:27 +08:00
Compare commits
19 Commits
cb8144e168
...
a12d0a6cb4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a12d0a6cb4 | ||
|
|
ea979f5b32 | ||
|
|
511dcdf8b8 | ||
|
|
3575fa6013 | ||
|
|
6c2f0ae57e | ||
|
|
f7cde17919 | ||
|
|
570cbb34b6 | ||
|
|
7aa9ae0a3e | ||
|
|
2d4180f5be | ||
|
|
9f0182b55e | ||
|
|
ad6666eeaf | ||
|
|
a2c4e468a0 | ||
|
|
2167076652 | ||
|
|
e123076250 | ||
|
|
ebcb4db245 | ||
|
|
bee5e15b84 | ||
|
|
0a25a1a8cb | ||
|
|
f3154b20a5 | ||
|
|
b709ee3983 |
@@ -22,7 +22,6 @@ English / [简体中文](./README_CN.md)
|
||||
[![MacOS][MacOS-image]][download-url]
|
||||
[![Linux][Linux-image]][download-url]
|
||||
|
||||
[NextChatAI](https://nextchat.dev/chat?utm_source=readme) / [Web App Demo](https://app.nextchat.dev) / [Desktop App](https://github.com/Yidadaa/ChatGPT-Next-Web/releases)
|
||||
[NextChatAI](https://nextchat.club?utm_source=readme) / [Web App Demo](https://app.nextchat.dev) / [Desktop App](https://github.com/Yidadaa/ChatGPT-Next-Web/releases) / [Discord](https://discord.gg/YCkeafCafC) / [Enterprise Edition](#enterprise-edition) / [Twitter](https://twitter.com/NextChatDev)
|
||||
|
||||
|
||||
|
||||
@@ -42,6 +42,11 @@ export interface MultimodalContent {
|
||||
};
|
||||
}
|
||||
|
||||
export interface MultimodalContentForAlibaba {
|
||||
text?: string;
|
||||
image?: string;
|
||||
}
|
||||
|
||||
export interface RequestMessage {
|
||||
role: MessageRole;
|
||||
content: string | MultimodalContent[];
|
||||
|
||||
@@ -7,7 +7,10 @@ import {
|
||||
ChatMessageTool,
|
||||
usePluginStore,
|
||||
} from "@/app/store";
|
||||
import { streamWithThink } from "@/app/utils/chat";
|
||||
import {
|
||||
preProcessImageContentForAlibabaDashScope,
|
||||
streamWithThink,
|
||||
} from "@/app/utils/chat";
|
||||
import {
|
||||
ChatOptions,
|
||||
getHeaders,
|
||||
@@ -15,12 +18,14 @@ import {
|
||||
LLMModel,
|
||||
SpeechOptions,
|
||||
MultimodalContent,
|
||||
MultimodalContentForAlibaba,
|
||||
} from "../api";
|
||||
import { getClientConfig } from "@/app/config/client";
|
||||
import {
|
||||
getMessageTextContent,
|
||||
getMessageTextContentWithoutThinking,
|
||||
getTimeoutMSByModel,
|
||||
isVisionModel,
|
||||
} from "@/app/utils";
|
||||
import { fetch } from "@/app/utils/stream";
|
||||
|
||||
@@ -89,14 +94,6 @@ export class QwenApi implements LLMApi {
|
||||
}
|
||||
|
||||
async chat(options: ChatOptions) {
|
||||
const messages = options.messages.map((v) => ({
|
||||
role: v.role,
|
||||
content:
|
||||
v.role === "assistant"
|
||||
? getMessageTextContentWithoutThinking(v)
|
||||
: getMessageTextContent(v),
|
||||
}));
|
||||
|
||||
const modelConfig = {
|
||||
...useAppConfig.getState().modelConfig,
|
||||
...useChatStore.getState().currentSession().mask.modelConfig,
|
||||
@@ -105,6 +102,21 @@ export class QwenApi implements LLMApi {
|
||||
},
|
||||
};
|
||||
|
||||
const visionModel = isVisionModel(options.config.model);
|
||||
|
||||
const messages: ChatOptions["messages"] = [];
|
||||
for (const v of options.messages) {
|
||||
const content = (
|
||||
visionModel
|
||||
? await preProcessImageContentForAlibabaDashScope(v.content)
|
||||
: v.role === "assistant"
|
||||
? getMessageTextContentWithoutThinking(v)
|
||||
: getMessageTextContent(v)
|
||||
) as any;
|
||||
|
||||
messages.push({ role: v.role, content });
|
||||
}
|
||||
|
||||
const shouldStream = !!options.config.stream;
|
||||
const requestPayload: RequestPayload = {
|
||||
model: modelConfig.model,
|
||||
@@ -129,7 +141,7 @@ export class QwenApi implements LLMApi {
|
||||
"X-DashScope-SSE": shouldStream ? "enable" : "disable",
|
||||
};
|
||||
|
||||
const chatPath = this.path(Alibaba.ChatPath);
|
||||
const chatPath = this.path(Alibaba.ChatPath(modelConfig.model));
|
||||
const chatPayload = {
|
||||
method: "POST",
|
||||
body: JSON.stringify(requestPayload),
|
||||
@@ -162,7 +174,7 @@ export class QwenApi implements LLMApi {
|
||||
const json = JSON.parse(text);
|
||||
const choices = json.output.choices as Array<{
|
||||
message: {
|
||||
content: string | null;
|
||||
content: string | null | MultimodalContentForAlibaba[];
|
||||
tool_calls: ChatMessageTool[];
|
||||
reasoning_content: string | null;
|
||||
};
|
||||
@@ -212,7 +224,9 @@ export class QwenApi implements LLMApi {
|
||||
} else if (content && content.length > 0) {
|
||||
return {
|
||||
isThinking: false,
|
||||
content: content,
|
||||
content: Array.isArray(content)
|
||||
? content.map((item) => item.text).join(",")
|
||||
: content,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -75,6 +75,25 @@ export class DeepSeekApi implements LLMApi {
|
||||
}
|
||||
}
|
||||
|
||||
// 检测并修复消息顺序,确保除system外的第一个消息是user
|
||||
const filteredMessages: ChatOptions["messages"] = [];
|
||||
let hasFoundFirstUser = false;
|
||||
|
||||
for (const msg of messages) {
|
||||
if (msg.role === "system") {
|
||||
// Keep all system messages
|
||||
filteredMessages.push(msg);
|
||||
} else if (msg.role === "user") {
|
||||
// User message directly added
|
||||
filteredMessages.push(msg);
|
||||
hasFoundFirstUser = true;
|
||||
} else if (hasFoundFirstUser) {
|
||||
// After finding the first user message, all subsequent non-system messages are retained.
|
||||
filteredMessages.push(msg);
|
||||
}
|
||||
// If hasFoundFirstUser is false and it is not a system message, it will be skipped.
|
||||
}
|
||||
|
||||
const modelConfig = {
|
||||
...useAppConfig.getState().modelConfig,
|
||||
...useChatStore.getState().currentSession().mask.modelConfig,
|
||||
@@ -85,7 +104,7 @@ export class DeepSeekApi implements LLMApi {
|
||||
};
|
||||
|
||||
const requestPayload: RequestPayload = {
|
||||
messages,
|
||||
messages: filteredMessages,
|
||||
stream: options.config.stream,
|
||||
model: modelConfig.model,
|
||||
temperature: modelConfig.temperature,
|
||||
|
||||
@@ -21,6 +21,7 @@ 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";
|
||||
import BotIconBedrock from "../icons/llm-icons/bedrock-color.svg";
|
||||
|
||||
export function getEmojiUrl(unified: string, style: EmojiStyle) {
|
||||
// Whoever owns this Content Delivery Network (CDN), I am using your CDN to serve emojis
|
||||
@@ -66,11 +67,14 @@ export function Avatar(props: { model?: ModelType; avatar?: string }) {
|
||||
LlmIcon = BotIconGemma;
|
||||
} else if (modelName.startsWith("claude")) {
|
||||
LlmIcon = BotIconClaude;
|
||||
} else if (modelName.toLowerCase().includes("llama")) {
|
||||
} else if (modelName.includes("llama")) {
|
||||
LlmIcon = BotIconMeta;
|
||||
} else if (modelName.startsWith("mixtral")) {
|
||||
} else if (
|
||||
modelName.startsWith("mixtral") ||
|
||||
modelName.startsWith("codestral")
|
||||
) {
|
||||
LlmIcon = BotIconMistral;
|
||||
} else if (modelName.toLowerCase().includes("deepseek")) {
|
||||
} else if (modelName.includes("deepseek")) {
|
||||
LlmIcon = BotIconDeepseek;
|
||||
} else if (modelName.startsWith("moonshot")) {
|
||||
LlmIcon = BotIconMoonshot;
|
||||
@@ -85,11 +89,13 @@ export function Avatar(props: { model?: ModelType; avatar?: string }) {
|
||||
} else if (modelName.startsWith("doubao") || modelName.startsWith("ep-")) {
|
||||
LlmIcon = BotIconDoubao;
|
||||
} else if (
|
||||
modelName.toLowerCase().includes("glm") ||
|
||||
modelName.includes("glm") ||
|
||||
modelName.startsWith("cogview-") ||
|
||||
modelName.startsWith("cogvideox-")
|
||||
) {
|
||||
LlmIcon = BotIconChatglm;
|
||||
} else if (modelName.includes("nova")) {
|
||||
LlmIcon = BotIconBedrock;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -224,7 +224,12 @@ export const ByteDance = {
|
||||
|
||||
export const Alibaba = {
|
||||
ExampleEndpoint: ALIBABA_BASE_URL,
|
||||
ChatPath: "v1/services/aigc/text-generation/generation",
|
||||
ChatPath: (modelName: string) => {
|
||||
if (modelName.includes("vl") || modelName.includes("omni")) {
|
||||
return "v1/services/aigc/multimodal-generation/generation";
|
||||
}
|
||||
return `v1/services/aigc/text-generation/generation`;
|
||||
},
|
||||
};
|
||||
|
||||
export const Tencent = {
|
||||
@@ -521,6 +526,7 @@ const bedrockModels = [
|
||||
"anthropic.claude-3-sonnet-20240229-v1:0",
|
||||
"anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
"anthropic.claude-3-opus-20240229-v1:0",
|
||||
"us.anthropic.claude-3-7-sonnet-20250219-v1:0",
|
||||
// Meta Llama Models
|
||||
"us.meta.llama3-1-8b-instruct-v1:0",
|
||||
"us.meta.llama3-1-70b-instruct-v1:0",
|
||||
@@ -571,6 +577,8 @@ const anthropicModels = [
|
||||
"claude-3-5-sonnet-20240620",
|
||||
"claude-3-5-sonnet-20241022",
|
||||
"claude-3-5-sonnet-latest",
|
||||
"claude-3-7-sonnet-20250219",
|
||||
"claude-3-7-sonnet-latest",
|
||||
];
|
||||
|
||||
const baiduModels = [
|
||||
@@ -604,6 +612,9 @@ const alibabaModes = [
|
||||
"qwen-max-0403",
|
||||
"qwen-max-0107",
|
||||
"qwen-max-longcontext",
|
||||
"qwen-omni-turbo",
|
||||
"qwen-vl-plus",
|
||||
"qwen-vl-max",
|
||||
];
|
||||
|
||||
const tencentModels = [
|
||||
@@ -834,15 +845,15 @@ export const DEFAULT_MODELS = [
|
||||
},
|
||||
})),
|
||||
...bedrockModels.map((name) => ({
|
||||
name,
|
||||
available: true,
|
||||
sorted: seq++,
|
||||
provider: {
|
||||
name,
|
||||
available: true,
|
||||
sorted: seq++,
|
||||
provider: {
|
||||
id: "bedrock",
|
||||
providerName: "Bedrock",
|
||||
providerType: "bedrock",
|
||||
sorted: 15,
|
||||
},
|
||||
},
|
||||
})),
|
||||
] as const;
|
||||
|
||||
|
||||
6
app/icons/llm-icons/bedrock-color.svg
Normal file
6
app/icons/llm-icons/bedrock-color.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="24" height="24" rx="12" fill="#055F4E"/>
|
||||
<g transform="translate(3.500000, 3.500000)" fill="#FFFFFF">
|
||||
<path d="M8.5,14.3722387 L6.37725,15.0801387 L5.54950001,14.5279637 L6.45075,14.2269637 L6.17425,13.3974637 L4.62725,13.9128637 L4.125,13.5786387 L4.125,11.1872287 C4.125,11.0218637 4.03137501,10.8704887 3.8835,10.7961137 L2.375,10.0418637 L2.375,7.95761374 L3.6875,7.30136374 L5,7.95761374 L5,9.4372287 C5,9.6034887 5.09362501,9.7548637 5.2415,9.8292387 L6.9915,10.7042387 L7.3835,9.9211137 L5.875,9.1668637 L5.875,7.95761374 L7.3835,7.20423874 C7.53137501,7.12986374 7.625,6.97848874 7.625,6.81222874 L7.625,5.49972874 L6.75,5.49972874 L6.75,6.54186374 L5.4375,7.19811374 L4.125,6.54186374 L4.125,4.42173874 L5,3.83811374 L5,5.49972874 L5.875,5.49972874 L5.875,3.25536374 L6.37725,2.92023624 L8.5,3.62811374 L8.5,14.3722387 Z M13.3125,13.3747387 C13.5531251,13.3747387 13.75,13.5707387 13.75,13.8122387 C13.75,14.0537387 13.5531251,14.2497387 13.3125,14.2497387 C13.071875,14.2497387 12.875,14.0537387 12.875,13.8122387 C12.875,13.5707387 13.071875,13.3747387 13.3125,13.3747387 L13.3125,13.3747387 Z M12.4375,3.74972874 C12.678125,3.74972874 12.875,3.94572874 12.875,4.18722874 C12.875,4.42873874 12.678125,4.62472874 12.4375,4.62472874 C12.196875,4.62472874 12,4.42873874 12,4.18722874 C12,3.94572874 12.196875,3.74972874 12.4375,3.74972874 L12.4375,3.74972874 Z M14.1875,8.99972874 C14.428125,8.99972874 14.625,9.1957287 14.625,9.4372287 C14.625,9.6787387 14.428125,9.8747287 14.1875,9.8747287 C13.946875,9.8747287 13.75,9.6787387 13.75,9.4372287 C13.75,9.1957287 13.946875,8.99972874 14.1875,8.99972874 L14.1875,8.99972874 Z M12.9555,9.8747287 C13.136625,10.3831137 13.617875,10.7497287 14.1875,10.7497287 C14.911375,10.7497287 15.5,10.1617387 15.5,9.4372287 C15.5,8.71361374 14.911375,8.12472874 14.1875,8.12472874 C13.617875,8.12472874 13.136625,8.49223874 12.9555,8.99972874 L9.375,8.99972874 L9.375,7.24972874 L12.4375,7.24972874 C12.679,7.24972874 12.875,7.05461374 12.875,6.81222874 L12.875,5.41923874 C13.383375,5.23811374 13.75,4.75686374 13.75,4.18722874 C13.75,3.46361374 13.161375,2.87472874 12.4375,2.87472874 C11.713625,2.87472874 11.125,3.46361374 11.125,4.18722874 C11.125,4.75686374 11.491625,5.23811374 12,5.41923874 L12,6.37472874 L9.375,6.37472874 L9.375,3.31222874 C9.375,3.12411374 9.25425,2.95703624 9.07575,2.89748624 L6.45075,2.02248624 C6.322125,1.97451366 6.182125,1.99886621 6.070125,2.07323866 L3.445125,3.82323874 C3.3235,3.90461374 3.25,4.04111374 3.25,4.18722874 L3.25,6.54186374 L1.7415,7.29611374 C1.593625,7.37048874 1.5,7.52186374 1.5,7.68722874 L1.5,10.3122287 C1.5,10.4784887 1.593625,10.6298637 1.7415,10.7042387 L3.25,11.4584887 L3.25,13.8122387 C3.25,13.9583387 3.3235,14.0957387 3.445125,14.1762387 L6.070125,15.9262387 C6.142751,15.9752387 6.22675,15.9997387 6.3125,15.9997387 C6.359375,15.9997387 6.40525,15.9927387 6.45075,15.9769637 L9.07575,15.1019637 C9.25425,15.0433387 9.375,14.8762387 9.375,14.6872387 L9.375,12.4997387 L11.381375,12.4997387 L12.127751,13.2469637 L12.139125,13.2356387 C12.053375,13.4106387 12,13.6048387 12,13.8122387 C12,14.5358387 12.588625,15.1247387 13.3125,15.1247387 C14.036375,15.1247387 14.625,14.5358387 14.625,13.8122387 C14.625,13.0886387 14.036375,12.4997387 13.3125,12.4997387 C13.104251,12.4997387 12.91,12.5531387 12.735875,12.6397387 L12.747251,12.6283387 L11.872251,11.7533387 C11.79,11.6711137 11.679125,11.6247287 11.5625,11.6247287 L9.375,11.6247287 L9.375,9.8747287 L12.9555,9.8747287 Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
@@ -3,7 +3,7 @@ import {
|
||||
UPLOAD_URL,
|
||||
REQUEST_TIMEOUT_MS,
|
||||
} from "@/app/constant";
|
||||
import { RequestMessage } from "@/app/client/api";
|
||||
import { MultimodalContent, RequestMessage } from "@/app/client/api";
|
||||
import Locale from "@/app/locales";
|
||||
import {
|
||||
EventStreamContentType,
|
||||
@@ -70,8 +70,9 @@ export function compressImage(file: Blob, maxSize: number): Promise<string> {
|
||||
});
|
||||
}
|
||||
|
||||
export async function preProcessImageContent(
|
||||
export async function preProcessImageContentBase(
|
||||
content: RequestMessage["content"],
|
||||
transformImageUrl: (url: string) => Promise<{ [key: string]: any }>,
|
||||
) {
|
||||
if (typeof content === "string") {
|
||||
return content;
|
||||
@@ -81,7 +82,7 @@ export async function preProcessImageContent(
|
||||
if (part?.type == "image_url" && part?.image_url?.url) {
|
||||
try {
|
||||
const url = await cacheImageToBase64Image(part?.image_url?.url);
|
||||
result.push({ type: part.type, image_url: { url } });
|
||||
result.push(await transformImageUrl(url));
|
||||
} catch (error) {
|
||||
console.error("Error processing image URL:", error);
|
||||
}
|
||||
@@ -92,6 +93,23 @@ export async function preProcessImageContent(
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function preProcessImageContent(
|
||||
content: RequestMessage["content"],
|
||||
) {
|
||||
return preProcessImageContentBase(content, async (url) => ({
|
||||
type: "image_url",
|
||||
image_url: { url },
|
||||
})) as Promise<MultimodalContent[] | string>;
|
||||
}
|
||||
|
||||
export async function preProcessImageContentForAlibabaDashScope(
|
||||
content: RequestMessage["content"],
|
||||
) {
|
||||
return preProcessImageContentBase(content, async (url) => ({
|
||||
image: url,
|
||||
}));
|
||||
}
|
||||
|
||||
const imageCaches: Record<string, string> = {};
|
||||
export function cacheImageToBase64Image(imageUrl: string) {
|
||||
if (imageUrl.includes(CACHE_URL_PREFIX)) {
|
||||
|
||||
Reference in New Issue
Block a user