Compare commits

...

19 Commits

Author SHA1 Message Date
glay
a12d0a6cb4 Merge branch 'feature/bedrock' of https://github.com/noteflow-ai/ChatGPT-Next-Web into feature/bedrock
合并到主题分支。
2025-03-01 19:54:55 +08:00
glay
ea979f5b32 修改: app/components/emoji.tsx
修改:     app/constant.ts
2025-03-01 19:47:30 +08:00
glay
511dcdf8b8 feat:add claude3.7 support 2025-03-01 19:46:48 +08:00
glay
3575fa6013 Merge branch 'ChatGPTNextWeb:main' into feature/bedrock 2025-03-01 17:03:06 +08:00
glay
6c2f0ae57e Merge branch 'ChatGPTNextWeb:main' into main 2025-03-01 17:02:46 +08:00
RiverRay
f7cde17919 Merge pull request #6292 from Little-LittleProgrammer/feature/alibaba-omni-support
Some checks failed
Run Tests / test (push) Has been cancelled
feat(alibaba): Added alibaba vision model and omni model support
2025-03-01 10:25:16 +08:00
RiverRay
570cbb34b6 Merge pull request #6310 from agi-dude/patch-1
Remove duplicate links
2025-03-01 10:24:38 +08:00
RiverRay
7aa9ae0a3e Merge pull request #6311 from ChatGPTNextWeb/6305-bugthe-first-message-except-the-system-message-of-deepseek-reasoner-must-be-a-user-message-but-an-assistant-message-detected
Some checks are pending
Run Tests / test (push) Waiting to run
fix: enforce that the first message (excluding system messages) is a …
2025-02-28 19:48:09 +08:00
Kadxy
2d4180f5be fix: update request payload to use filtered messages in Deepseek API 2025-02-28 13:59:30 +08:00
Kadxy
9f0182b55e fix: enforce that the first message (excluding system messages) is a user message in the Deepseek API 2025-02-28 13:54:58 +08:00
Mr. AGI
ad6666eeaf Update README.md 2025-02-28 10:47:52 +05:00
EvanWu
a2c4e468a0 fix(app/utils/chat.ts): fix type error 2025-02-26 19:58:32 +08:00
RiverRay
2167076652 Merge pull request #6293 from hyiip/main
Some checks failed
Run Tests / test (push) Has been cancelled
claude 3.7 support
2025-02-26 18:41:28 +08:00
RiverRay
e123076250 Merge pull request #6295 from rexkyng/patch-1
Fix: Improve Mistral icon detection and remove redundant code.
2025-02-26 18:39:59 +08:00
Rex Ng
ebcb4db245 Fix: Improve Mistral icon detection and remove redundant code.
- Added "codestral" to the list of acceptable names for the Mistral icon, ensuring proper detection.
- Removed duplicate `toLowerCase()` calls.
2025-02-25 14:30:18 +08:00
glay
bee5e15b84 Merge branch 'ChatGPTNextWeb:main' into feature/bedrock 2025-02-25 10:38:58 +08:00
EvanWu
0a25a1a8cb refacto(app/utils/chat.ts)r: optimize function preProcessImageContentBase 2025-02-25 09:22:47 +08:00
hyiip
f3154b20a5 claude 3.7 support 2025-02-25 03:55:24 +08:00
EvanWu
b709ee3983 feat(alibaba): Added alibaba vision model and omni model support 2025-02-24 20:18:07 +08:00
8 changed files with 105 additions and 27 deletions

View File

@@ -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)

View File

@@ -42,6 +42,11 @@ export interface MultimodalContent {
};
}
export interface MultimodalContentForAlibaba {
text?: string;
image?: string;
}
export interface RequestMessage {
role: MessageRole;
content: string | MultimodalContent[];

View File

@@ -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,
};
}

View File

@@ -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,

View File

@@ -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 (

View File

@@ -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;

View 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

View File

@@ -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)) {