From 4d6b981a54d676a4d70b767418ec842bb4f3114a Mon Sep 17 00:00:00 2001 From: butterfly Date: Tue, 26 Mar 2024 11:43:55 +0800 Subject: [PATCH 001/611] bugfix: Delete the escapeDollarNumber function, which causes errors in rendering a latex string --- app/components/markdown.tsx | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/app/components/markdown.tsx b/app/components/markdown.tsx index 7c70fe1a5ac..c6290d8e015 100644 --- a/app/components/markdown.tsx +++ b/app/components/markdown.tsx @@ -99,23 +99,6 @@ export function PreCode(props: { children: any }) { ); } -function escapeDollarNumber(text: string) { - let escapedText = ""; - - for (let i = 0; i < text.length; i += 1) { - let char = text[i]; - const nextChar = text[i + 1] || " "; - - if (char === "$" && nextChar >= "0" && nextChar <= "9") { - char = "\\$"; - } - - escapedText += char; - } - - return escapedText; -} - function escapeBrackets(text: string) { const pattern = /(```[\s\S]*?```|`.*?`)|\\\[([\s\S]*?[^\\])\\\]|\\\((.*?)\\\)/g; @@ -136,7 +119,7 @@ function escapeBrackets(text: string) { function _MarkDownContent(props: { content: string }) { const escapedContent = useMemo( - () => escapeBrackets(escapeDollarNumber(props.content)), + () => escapeBrackets(props.content), [props.content], ); From f3b972e57381afc74d9417f802f8ae3c64536f68 Mon Sep 17 00:00:00 2001 From: josephrocca <1167575+josephrocca@users.noreply.github.com> Date: Mon, 27 May 2024 10:31:29 +0800 Subject: [PATCH 002/611] Fix web url --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d496d68edfc..e96e2f58875 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ One-Click to get a well-designed cross-platform ChatGPT web UI, with GPT3, GPT4 [网页版](https://app.nextchat.dev/) / [客户端](https://github.com/Yidadaa/ChatGPT-Next-Web/releases) / [反馈](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) -[web-url]: https://chatgpt.nextweb.fun +[web-url]: https://app.nextchat.dev/ [download-url]: https://github.com/Yidadaa/ChatGPT-Next-Web/releases [Web-image]: https://img.shields.io/badge/Web-PWA-orange?logo=microsoftedge [Windows-image]: https://img.shields.io/badge/-Windows-blue?logo=windows From 1d8fd480ca7c75319bf469494d40bb527f2cecf7 Mon Sep 17 00:00:00 2001 From: junxian li-ssslab win10 Date: Fri, 7 Jun 2024 03:28:00 +0800 Subject: [PATCH 003/611] Add new Teracloud domain - Added 'bora.teracloud.jp' to the list of supported domains. --- app/constant.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/constant.ts b/app/constant.ts index 9f1d87161ae..411e481508d 100644 --- a/app/constant.ts +++ b/app/constant.ts @@ -149,7 +149,7 @@ const openaiModels = [ "gpt-4o", "gpt-4o-2024-05-13", "gpt-4-vision-preview", - "gpt-4-turbo-2024-04-09" + "gpt-4-turbo-2024-04-09", ]; const googleModels = [ @@ -207,6 +207,7 @@ export const internalAllowedWebDavEndpoints = [ "https://dav.dropdav.com/", "https://dav.box.com/dav", "https://nanao.teracloud.jp/dav/", + "https://bora.teracloud.jp/dav/", "https://webdav.4shared.com/", "https://dav.idrivesync.com", "https://webdav.yandex.com", From 24bf7950d85c7894a38677bffe5771ae981993b6 Mon Sep 17 00:00:00 2001 From: YeungYeah Date: Wed, 12 Jun 2024 21:59:28 +0800 Subject: [PATCH 004/611] chore: set the google safety setting to lowest --- app/client/platforms/google.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/client/platforms/google.ts b/app/client/platforms/google.ts index a786f5275f4..953aacb065b 100644 --- a/app/client/platforms/google.ts +++ b/app/client/platforms/google.ts @@ -85,19 +85,19 @@ export class GeminiProApi implements LLMApi { safetySettings: [ { category: "HARM_CATEGORY_HARASSMENT", - threshold: "BLOCK_ONLY_HIGH", + threshold: "BLOCK_NONE", }, { category: "HARM_CATEGORY_HATE_SPEECH", - threshold: "BLOCK_ONLY_HIGH", + threshold: "BLOCK_NONE", }, { category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", - threshold: "BLOCK_ONLY_HIGH", + threshold: "BLOCK_NONE", }, { category: "HARM_CATEGORY_DANGEROUS_CONTENT", - threshold: "BLOCK_ONLY_HIGH", + threshold: "BLOCK_NONE", }, ], }; From 163fc9e3a31158b4be8ff25f98a1c905c6eb1afb Mon Sep 17 00:00:00 2001 From: Imamuzzaki Abu Salam Date: Fri, 14 Jun 2024 08:45:06 +0700 Subject: [PATCH 005/611] fix someone forgot to update license year to 2024 --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 542e91f4e77..c979e90c048 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Zhang Yifei +Copyright (c) 2024 Zhang Yifei Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 9b0a7050556bed8d0289aeb9686299c1608b5f3f Mon Sep 17 00:00:00 2001 From: Imamuzzaki Abu Salam Date: Fri, 14 Jun 2024 09:19:38 +0700 Subject: [PATCH 006/611] Update LICENSE --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index c979e90c048..047f9431e7d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Zhang Yifei +Copyright (c) 2023-2024 Zhang Yifei Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 74986803db5241392f4044e9493661113e955ee0 Mon Sep 17 00:00:00 2001 From: YeungYeah Date: Sat, 15 Jun 2024 12:09:58 +0800 Subject: [PATCH 007/611] feat: add google api safety setting --- app/client/platforms/google.ts | 19 +++++++++++-------- app/components/settings.tsx | 30 ++++++++++++++++++++++++++++++ app/constant.ts | 9 +++++++++ app/locales/cn.ts | 4 ++++ app/locales/en.ts | 4 ++++ app/store/access.ts | 2 ++ 6 files changed, 60 insertions(+), 8 deletions(-) diff --git a/app/client/platforms/google.ts b/app/client/platforms/google.ts index 953aacb065b..e62d1078eab 100644 --- a/app/client/platforms/google.ts +++ b/app/client/platforms/google.ts @@ -64,6 +64,9 @@ export class GeminiProApi implements LLMApi { // if (visionModel && messages.length > 1) { // options.onError?.(new Error("Multiturn chat is not enabled for models/gemini-pro-vision")); // } + + const accessStore = useAccessStore.getState(); + const modelConfig = { ...useAppConfig.getState().modelConfig, ...useChatStore.getState().currentSession().mask.modelConfig, @@ -85,25 +88,23 @@ export class GeminiProApi implements LLMApi { safetySettings: [ { category: "HARM_CATEGORY_HARASSMENT", - threshold: "BLOCK_NONE", + threshold: accessStore.googleSafetySettings, }, { category: "HARM_CATEGORY_HATE_SPEECH", - threshold: "BLOCK_NONE", + threshold: accessStore.googleSafetySettings, }, { category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", - threshold: "BLOCK_NONE", + threshold: accessStore.googleSafetySettings, }, { category: "HARM_CATEGORY_DANGEROUS_CONTENT", - threshold: "BLOCK_NONE", + threshold: accessStore.googleSafetySettings, }, ], }; - const accessStore = useAccessStore.getState(); - let baseUrl = ""; if (accessStore.useCustomConfig) { @@ -120,7 +121,9 @@ export class GeminiProApi implements LLMApi { if (!baseUrl) { baseUrl = isApp - ? DEFAULT_API_HOST + "/api/proxy/google/" + Google.ChatPath(modelConfig.model) + ? DEFAULT_API_HOST + + "/api/proxy/google/" + + Google.ChatPath(modelConfig.model) : this.path(Google.ChatPath(modelConfig.model)); } @@ -139,7 +142,7 @@ export class GeminiProApi implements LLMApi { () => controller.abort(), REQUEST_TIMEOUT_MS, ); - + if (shouldStream) { let responseText = ""; let remainText = ""; diff --git a/app/components/settings.tsx b/app/components/settings.tsx index db08b48a9ff..fd28723a534 100644 --- a/app/components/settings.tsx +++ b/app/components/settings.tsx @@ -54,6 +54,7 @@ import { Anthropic, Azure, Google, + GoogleSafetySettingsThreshold, OPENAI_BASE_URL, Path, RELEASE_URL, @@ -1122,6 +1123,35 @@ export function Settings() { } > + + + )} {accessStore.provider === ServiceProvider.Anthropic && ( diff --git a/app/constant.ts b/app/constant.ts index 411e481508d..43660d6da07 100644 --- a/app/constant.ts +++ b/app/constant.ts @@ -72,6 +72,15 @@ export enum ServiceProvider { Anthropic = "Anthropic", } +// Google API safety settings, see https://ai.google.dev/gemini-api/docs/safety-settings +// BLOCK_NONE will not block any content, and BLOCK_ONLY_HIGH will block only high-risk content. +export enum GoogleSafetySettingsThreshold { + BLOCK_NONE = "BLOCK_NONE", + BLOCK_ONLY_HIGH = "BLOCK_ONLY_HIGH", + BLOCK_MEDIUM_AND_ABOVE = "BLOCK_MEDIUM_AND_ABOVE", + BLOCK_LOW_AND_ABOVE = "BLOCK_LOW_AND_ABOVE", +} + export enum ModelProvider { GPT = "GPT", GeminiPro = "GeminiPro", diff --git a/app/locales/cn.ts b/app/locales/cn.ts index 2ff94e32d43..fa75b0e38e1 100644 --- a/app/locales/cn.ts +++ b/app/locales/cn.ts @@ -346,6 +346,10 @@ const cn = { Title: "API 版本(仅适用于 gemini-pro)", SubTitle: "选择一个特定的 API 版本", }, + GoogleSafetySettings: { + Title: "Google 安全过滤级别", + SubTitle: "设置内容过滤级别", + }, }, CustomModel: { Title: "自定义模型名", diff --git a/app/locales/en.ts b/app/locales/en.ts index aa153f52369..e546ce0f822 100644 --- a/app/locales/en.ts +++ b/app/locales/en.ts @@ -354,6 +354,10 @@ const en: LocaleType = { Title: "API Version (specific to gemini-pro)", SubTitle: "Select a specific API version", }, + GoogleSafetySettings: { + Title: "Google Safety Settings", + SubTitle: "Select a safety filtering level", + }, }, }, diff --git a/app/store/access.ts b/app/store/access.ts index 64909609e05..737bcc807bb 100644 --- a/app/store/access.ts +++ b/app/store/access.ts @@ -1,6 +1,7 @@ import { ApiPath, DEFAULT_API_HOST, + GoogleSafetySettingsThreshold, ServiceProvider, StoreKey, } from "../constant"; @@ -36,6 +37,7 @@ const DEFAULT_ACCESS_STATE = { googleUrl: "", googleApiKey: "", googleApiVersion: "v1", + googleSafetySettings: GoogleSafetySettingsThreshold.BLOCK_ONLY_HIGH, // anthropic anthropicApiKey: "", From 6efe4fb73445ea8acd54cbc750b5ef0321d8a50f Mon Sep 17 00:00:00 2001 From: Imamuzzaki Abu Salam Date: Sun, 16 Jun 2024 10:17:58 +0700 Subject: [PATCH 008/611] chore(app/layout.tsx): fix deprecated viewport nextjs 14 --- app/layout.tsx | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/app/layout.tsx b/app/layout.tsx index 5898b21a1fa..637b4556b62 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -3,7 +3,7 @@ import "./styles/globals.scss"; import "./styles/markdown.scss"; import "./styles/highlight.scss"; import { getClientConfig } from "./config/client"; -import { type Metadata } from "next"; +import type { Metadata, Viewport } from "next"; import { SpeedInsights } from "@vercel/speed-insights/next"; import { getServerSideConfig } from "./config/server"; import { GoogleTagManager } from "@next/third-parties/google"; @@ -12,19 +12,20 @@ const serverConfig = getServerSideConfig(); export const metadata: Metadata = { title: "NextChat", description: "Your personal ChatGPT Chat Bot.", - viewport: { - width: "device-width", - initialScale: 1, - maximumScale: 1, + appleWebApp: { + title: "NextChat", + statusBarStyle: "default", }, +}; + +export const viewport: Viewport = { + width: "device-width", + initialScale: 1, + maximumScale: 1, themeColor: [ { media: "(prefers-color-scheme: light)", color: "#fafafa" }, { media: "(prefers-color-scheme: dark)", color: "#151515" }, ], - appleWebApp: { - title: "NextChat", - statusBarStyle: "default", - }, }; export default function RootLayout({ From 4640060891c85b6619cdaf7b7ee4c0cfc4404170 Mon Sep 17 00:00:00 2001 From: hengstchon Date: Fri, 21 Jun 2024 12:05:28 +0200 Subject: [PATCH 009/611] feat: support model: claude-3-5-sonnet-20240620 --- app/constant.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/constant.ts b/app/constant.ts index 411e481508d..1ccb1aeb272 100644 --- a/app/constant.ts +++ b/app/constant.ts @@ -166,6 +166,7 @@ const anthropicModels = [ "claude-3-sonnet-20240229", "claude-3-opus-20240229", "claude-3-haiku-20240307", + "claude-3-5-sonnet-20240620", ]; export const DEFAULT_MODELS = [ From 9fb8fbcc65c29c74473a13715c05725e2b49065d Mon Sep 17 00:00:00 2001 From: Fred Date: Mon, 24 Jun 2024 14:31:50 +0800 Subject: [PATCH 010/611] fix: validate the url to avoid SSRF --- app/api/webdav/[...path]/route.ts | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/app/api/webdav/[...path]/route.ts b/app/api/webdav/[...path]/route.ts index 816c2046b22..01286fc1bf9 100644 --- a/app/api/webdav/[...path]/route.ts +++ b/app/api/webdav/[...path]/route.ts @@ -9,6 +9,14 @@ const mergedAllowedWebDavEndpoints = [ ...config.allowedWebDevEndpoints, ].filter((domain) => Boolean(domain.trim())); +const normalizeUrl = (url: string) => { + try { + return new URL(url); + } catch (err) { + return null; + } +}; + async function handle( req: NextRequest, { params }: { params: { path: string[] } }, @@ -24,9 +32,15 @@ async function handle( // Validate the endpoint to prevent potential SSRF attacks if ( - !mergedAllowedWebDavEndpoints.some( - (allowedEndpoint) => endpoint?.startsWith(allowedEndpoint), - ) + !endpoint || + !mergedAllowedWebDavEndpoints.some((allowedEndpoint) => { + const normalizedAllowedEndpoint = normalizeUrl(allowedEndpoint); + const normalizedEndpoint = normalizeUrl(endpoint as string); + + return normalizedEndpoint && + normalizedEndpoint.hostname === normalizedAllowedEndpoint?.hostname && + normalizedEndpoint.pathname.startsWith(normalizedAllowedEndpoint.pathname); + }) ) { return NextResponse.json( { From b972a0d0817e612fe2a1cba398c338bcec7573e6 Mon Sep 17 00:00:00 2001 From: Fred Date: Mon, 24 Jun 2024 14:45:45 +0800 Subject: [PATCH 011/611] feat: bump version --- src-tauri/tauri.conf.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index ee87d8d1540..6230ba41fac 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -9,7 +9,7 @@ }, "package": { "productName": "NextChat", - "version": "2.12.3" + "version": "2.12.4" }, "tauri": { "allowlist": { @@ -112,4 +112,4 @@ } ] } -} +} \ No newline at end of file From 95e3b156c01e51324c32e7be6d818b5ee3f4025f Mon Sep 17 00:00:00 2001 From: fred-bf <157469842+fred-bf@users.noreply.github.com> Date: Wed, 26 Jun 2024 17:36:14 +0800 Subject: [PATCH 012/611] Update Dockerfile --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 51a810fc5a3..ae9a17cddbd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -43,7 +43,7 @@ COPY --from=builder /app/.next/server ./.next/server EXPOSE 3000 CMD if [ -n "$PROXY_URL" ]; then \ - export HOSTNAME="127.0.0.1"; \ + export HOSTNAME="0.0.0.0"; \ protocol=$(echo $PROXY_URL | cut -d: -f1); \ host=$(echo $PROXY_URL | cut -d/ -f3 | cut -d: -f1); \ port=$(echo $PROXY_URL | cut -d: -f3); \ @@ -58,7 +58,7 @@ CMD if [ -n "$PROXY_URL" ]; then \ echo "[ProxyList]" >> $conf; \ echo "$protocol $host $port" >> $conf; \ cat /etc/proxychains.conf; \ - proxychains -f $conf node server.js --host 0.0.0.0; \ + proxychains -f $conf node server.js; \ else \ node server.js; \ fi From a51fb24f36f24f75d393f4637ea9b60754f7d596 Mon Sep 17 00:00:00 2001 From: licoy Date: Thu, 27 Jun 2024 15:13:45 +0800 Subject: [PATCH 013/611] fix ts error --- app/components/error.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/components/error.tsx b/app/components/error.tsx index 914740f9686..c90997d1166 100644 --- a/app/components/error.tsx +++ b/app/components/error.tsx @@ -1,3 +1,5 @@ +"use client"; + import React from "react"; import { IconButton } from "./button"; import GithubIcon from "../icons/github.svg"; From fa6ebadc7b78cb023dc15705207ce2d180298edf Mon Sep 17 00:00:00 2001 From: licoy Date: Thu, 27 Jun 2024 15:35:16 +0800 Subject: [PATCH 014/611] feat: add plugin entry selection --- app/components/sidebar.tsx | 25 +++++++++++++++++++++---- app/components/ui-lib.module.scss | 4 ++++ app/components/ui-lib.tsx | 18 ++++++++++++------ 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/app/components/sidebar.tsx b/app/components/sidebar.tsx index 69b2e71f871..ea4d70dbb58 100644 --- a/app/components/sidebar.tsx +++ b/app/components/sidebar.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useMemo } from "react"; +import React, { useEffect, useRef, useMemo, useState } from "react"; import styles from "./home.module.scss"; @@ -15,7 +15,7 @@ import DragIcon from "../icons/drag.svg"; import Locale from "../locales"; -import { useAppConfig, useChatStore } from "../store"; +import { ModelType, useAppConfig, useChatStore } from "../store"; import { DEFAULT_SIDEBAR_WIDTH, @@ -29,7 +29,7 @@ import { import { Link, useNavigate } from "react-router-dom"; import { isIOS, useMobileScreen } from "../utils"; import dynamic from "next/dynamic"; -import { showConfirm, showToast } from "./ui-lib"; +import { Selector, showConfirm, showToast } from "./ui-lib"; const ChatList = dynamic(async () => (await import("./chat-list")).ChatList, { loading: () => null, @@ -140,6 +140,7 @@ export function SideBar(props: { className?: string }) { () => isIOS() && isMobileScreen, [isMobileScreen], ); + const [showPluginSelector, setShowPluginSelector] = useState(false); useHotKey(); @@ -183,7 +184,7 @@ export function SideBar(props: { className?: string }) { icon={} text={shouldNarrow ? undefined : Locale.Plugin.Name} className={styles["sidebar-bar-button"]} - onClick={() => showToast(Locale.WIP)} + onClick={() => setShowPluginSelector(true)} shadow /> @@ -245,6 +246,22 @@ export function SideBar(props: { className?: string }) { > + {showPluginSelector && ( + setShowPluginSelector(false)} + onSelection={(s) => { + console.log("go to page: ", s); + }} + /> + )} ); } diff --git a/app/components/ui-lib.module.scss b/app/components/ui-lib.module.scss index 83c02f92a23..ee17746f4ab 100644 --- a/app/components/ui-lib.module.scss +++ b/app/components/ui-lib.module.scss @@ -291,6 +291,10 @@ justify-content: center; z-index: 999; + .selector-item-disabled{ + opacity: 0.6; + } + &-content { .list { max-height: 90vh; diff --git a/app/components/ui-lib.tsx b/app/components/ui-lib.tsx index da700c0fb7c..26e0f4af1d4 100644 --- a/app/components/ui-lib.tsx +++ b/app/components/ui-lib.tsx @@ -13,7 +13,7 @@ import MinIcon from "../icons/min.svg"; import Locale from "../locales"; import { createRoot } from "react-dom/client"; -import React, { HTMLProps, useEffect, useState } from "react"; +import React, { HTMLProps, MouseEvent, useEffect, useState } from "react"; import { IconButton } from "./button"; export function Popover(props: { @@ -47,7 +47,7 @@ export function ListItem(props: { children?: JSX.Element | JSX.Element[]; icon?: JSX.Element; className?: string; - onClick?: () => void; + onClick?: (event: MouseEvent) => void; }) { return (
(props: { title: string; subTitle?: string; value: T; + disable?: boolean; }>; defaultSelectedValue?: T; onSelection?: (selection: T[]) => void; @@ -456,13 +457,18 @@ export function Selector(props: { const selected = props.defaultSelectedValue === item.value; return ( { - props.onSelection?.([item.value]); - props.onClose?.(); + onClick={(event) => { + event.stopPropagation(); + if (!item.disable) { + props.onSelection?.([item.value]); + props.onClose?.(); + } }} > {selected ? ( From d21481173e5c8eeb89024216acb164930ba31175 Mon Sep 17 00:00:00 2001 From: licoy Date: Thu, 27 Jun 2024 16:06:15 +0800 Subject: [PATCH 015/611] feat: add SD page switching --- app/components/home.tsx | 3 +++ app/components/sd-list.module.scss | 0 app/components/sd-list.tsx | 3 +++ app/components/sd.module.scss | 0 app/components/sd.tsx | 3 +++ app/components/sidebar.tsx | 32 +++++++++++++++++++++++++----- app/constant.ts | 3 +++ 7 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 app/components/sd-list.module.scss create mode 100644 app/components/sd-list.tsx create mode 100644 app/components/sd.module.scss create mode 100644 app/components/sd.tsx diff --git a/app/components/home.tsx b/app/components/home.tsx index ffac64fdac0..99755cc2032 100644 --- a/app/components/home.tsx +++ b/app/components/home.tsx @@ -1,5 +1,7 @@ "use client"; +import { Sd } from "@/app/components/sd"; + require("../polyfill"); import { useState, useEffect } from "react"; @@ -159,6 +161,7 @@ function Screen() { } /> } /> } /> + } /> } />
diff --git a/app/components/sd-list.module.scss b/app/components/sd-list.module.scss new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/components/sd-list.tsx b/app/components/sd-list.tsx new file mode 100644 index 00000000000..ba42919a54d --- /dev/null +++ b/app/components/sd-list.tsx @@ -0,0 +1,3 @@ +export function SdList() { + return
sd-list
; +} diff --git a/app/components/sd.module.scss b/app/components/sd.module.scss new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/components/sd.tsx b/app/components/sd.tsx new file mode 100644 index 00000000000..5341e5387c9 --- /dev/null +++ b/app/components/sd.tsx @@ -0,0 +1,3 @@ +export function Sd() { + return
sd
; +} diff --git a/app/components/sidebar.tsx b/app/components/sidebar.tsx index ea4d70dbb58..a06262fefb0 100644 --- a/app/components/sidebar.tsx +++ b/app/components/sidebar.tsx @@ -23,18 +23,24 @@ import { MIN_SIDEBAR_WIDTH, NARROW_SIDEBAR_WIDTH, Path, + PLUGINS, REPO_URL, } from "../constant"; -import { Link, useNavigate } from "react-router-dom"; +import { Link, useLocation, useNavigate } from "react-router-dom"; import { isIOS, useMobileScreen } from "../utils"; import dynamic from "next/dynamic"; import { Selector, showConfirm, showToast } from "./ui-lib"; +import de from "@/app/locales/de"; const ChatList = dynamic(async () => (await import("./chat-list")).ChatList, { loading: () => null, }); +const SdList = dynamic(async () => (await import("./sd-list")).SdList, { + loading: () => null, +}); + function useHotKey() { const chatStore = useChatStore(); @@ -141,9 +147,20 @@ export function SideBar(props: { className?: string }) { [isMobileScreen], ); const [showPluginSelector, setShowPluginSelector] = useState(false); + const location = useLocation(); useHotKey(); + let bodyComponent: React.JSX.Element; + let isChat: boolean; + switch (location.pathname) { + case Path.Sd: + bodyComponent = ; + break; + default: + isChat = true; + bodyComponent = ; + } return (
{ - if (e.target === e.currentTarget) { + if (isChat && e.target === e.currentTarget) { navigate(Path.Home); } }} > - + {bodyComponent}
@@ -254,11 +271,16 @@ export function SideBar(props: { className?: string }) { value: "-", disable: true, }, - { title: "Stable Diffusion", value: "sd" }, + ...PLUGINS.map((item) => { + return { + title: item.name, + value: item.path, + }; + }), ]} onClose={() => setShowPluginSelector(false)} onSelection={(s) => { - console.log("go to page: ", s); + navigate(s[0], { state: { fromHome: true } }); }} /> )} diff --git a/app/constant.ts b/app/constant.ts index 411e481508d..c56f77cb9da 100644 --- a/app/constant.ts +++ b/app/constant.ts @@ -21,6 +21,7 @@ export enum Path { NewChat = "/new-chat", Masks = "/masks", Auth = "/auth", + Sd = "/sd", } export enum ApiPath { @@ -213,3 +214,5 @@ export const internalAllowedWebDavEndpoints = [ "https://webdav.yandex.com", "https://app.koofr.net/dav/Koofr", ]; + +export const PLUGINS = [{ name: "Stable Diffusion", path: Path.Sd }]; From 34034be0e3ccba51214cb7fac7cd7bae2033ff58 Mon Sep 17 00:00:00 2001 From: licoy Date: Thu, 27 Jun 2024 16:13:51 +0800 Subject: [PATCH 016/611] hide new chat button on sd page --- app/components/sidebar.tsx | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/app/components/sidebar.tsx b/app/components/sidebar.tsx index a06262fefb0..91ddd4c2355 100644 --- a/app/components/sidebar.tsx +++ b/app/components/sidebar.tsx @@ -152,7 +152,7 @@ export function SideBar(props: { className?: string }) { useHotKey(); let bodyComponent: React.JSX.Element; - let isChat: boolean; + let isChat: boolean = false; switch (location.pathname) { case Path.Sd: bodyComponent = ; @@ -161,6 +161,7 @@ export function SideBar(props: { className?: string }) { isChat = true; bodyComponent = ; } + // @ts-ignore return (
-
- } - text={shouldNarrow ? undefined : Locale.Home.NewChat} - onClick={() => { - if (config.dontShowMaskSplashScreen) { - chatStore.newSession(); - navigate(Path.Chat); - } else { - navigate(Path.NewChat); - } - }} - shadow - /> -
+ {isChat && ( +
+ } + text={shouldNarrow ? undefined : Locale.Home.NewChat} + onClick={() => { + if (config.dontShowMaskSplashScreen) { + chatStore.newSession(); + navigate(Path.Chat); + } else { + navigate(Path.NewChat); + } + }} + shadow + /> +
+ )}
Date: Mon, 1 Jul 2024 09:41:01 +0000 Subject: [PATCH 017/611] fix: anthropic client using common getHeaders --- app/client/api.ts | 7 +++++-- app/client/platforms/anthropic.ts | 6 ++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/client/api.ts b/app/client/api.ts index 7bee546b4f6..502c7469848 100644 --- a/app/client/api.ts +++ b/app/client/api.ts @@ -162,14 +162,17 @@ export function getHeaders() { const modelConfig = useChatStore.getState().currentSession().mask.modelConfig; const isGoogle = modelConfig.model.startsWith("gemini"); const isAzure = accessStore.provider === ServiceProvider.Azure; - const authHeader = isAzure ? "api-key" : "Authorization"; + const isAnthropic = accessStore.provider === ServiceProvider.Anthropic; + const authHeader = isAzure ? "api-key" : isAnthropic ? 'x-api-key' : "Authorization"; const apiKey = isGoogle ? accessStore.googleApiKey : isAzure ? accessStore.azureApiKey + : isAnthropic + ? accessStore.anthropicApiKey : accessStore.openaiApiKey; const clientConfig = getClientConfig(); - const makeBearer = (s: string) => `${isAzure ? "" : "Bearer "}${s.trim()}`; + const makeBearer = (s: string) => `${isAzure || isAnthropic ? "" : "Bearer "}${s.trim()}`; const validString = (x: string) => x && x.length > 0; // when using google api in app, not set auth header diff --git a/app/client/platforms/anthropic.ts b/app/client/platforms/anthropic.ts index e90c8f057b2..b8eca694633 100644 --- a/app/client/platforms/anthropic.ts +++ b/app/client/platforms/anthropic.ts @@ -1,5 +1,5 @@ import { ACCESS_CODE_PREFIX, Anthropic, ApiPath } from "@/app/constant"; -import { ChatOptions, LLMApi, MultimodalContent } from "../api"; +import { ChatOptions, getHeaders, LLMApi, MultimodalContent, } from "../api"; import { useAccessStore, useAppConfig, useChatStore } from "@/app/store"; import { getClientConfig } from "@/app/config/client"; import { DEFAULT_API_HOST } from "@/app/constant"; @@ -190,9 +190,7 @@ export class ClaudeApi implements LLMApi { body: JSON.stringify(requestBody), signal: controller.signal, headers: { - "Content-Type": "application/json", - Accept: "application/json", - "x-api-key": accessStore.anthropicApiKey, + ...getHeaders(), // get common headers "anthropic-version": accessStore.anthropicApiVersion, Authorization: getAuthKey(accessStore.anthropicApiKey), }, From 37e2517dac850aef0bec0430f02356402b8610d8 Mon Sep 17 00:00:00 2001 From: lloydzhou Date: Mon, 1 Jul 2024 10:24:19 +0000 Subject: [PATCH 018/611] fix: 1. anthropic client using common getHeaders; 2. always using `Authorization` header send access code --- app/client/api.ts | 3 ++- app/client/platforms/anthropic.ts | 27 ++------------------------- 2 files changed, 4 insertions(+), 26 deletions(-) diff --git a/app/client/api.ts b/app/client/api.ts index 502c7469848..edee993424a 100644 --- a/app/client/api.ts +++ b/app/client/api.ts @@ -184,7 +184,8 @@ export function getHeaders() { accessStore.enabledAccessControl() && validString(accessStore.accessCode) ) { - headers[authHeader] = makeBearer( + // access_code must send with header named `Authorization`, will using in auth middleware. + headers['Authorization'] = makeBearer( ACCESS_CODE_PREFIX + accessStore.accessCode, ); } diff --git a/app/client/platforms/anthropic.ts b/app/client/platforms/anthropic.ts index b8eca694633..18c3decac8e 100644 --- a/app/client/platforms/anthropic.ts +++ b/app/client/platforms/anthropic.ts @@ -192,7 +192,8 @@ export class ClaudeApi implements LLMApi { headers: { ...getHeaders(), // get common headers "anthropic-version": accessStore.anthropicApiVersion, - Authorization: getAuthKey(accessStore.anthropicApiKey), + // do not send `anthropicApiKey` in browser!!! + // Authorization: getAuthKey(accessStore.anthropicApiKey), }, }; @@ -387,27 +388,3 @@ function trimEnd(s: string, end = " ") { return s; } - -function bearer(value: string) { - return `Bearer ${value.trim()}`; -} - -function getAuthKey(apiKey = "") { - const accessStore = useAccessStore.getState(); - const isApp = !!getClientConfig()?.isApp; - let authKey = ""; - - if (apiKey) { - // use user's api key first - authKey = bearer(apiKey); - } else if ( - accessStore.enabledAccessControl() && - !isApp && - !!accessStore.accessCode - ) { - // or use access code - authKey = bearer(ACCESS_CODE_PREFIX + accessStore.accessCode); - } - - return authKey; -} From 69974d5651e30c879579e3fe1bd63982548b5dbe Mon Sep 17 00:00:00 2001 From: lloydzhou Date: Mon, 1 Jul 2024 13:24:01 +0000 Subject: [PATCH 019/611] gemini using real sse format response #3677 #3688 --- app/api/google/[...path]/route.ts | 4 +- app/client/platforms/google.ts | 147 +++++++++++++++++------------- 2 files changed, 87 insertions(+), 64 deletions(-) diff --git a/app/api/google/[...path]/route.ts b/app/api/google/[...path]/route.ts index ebd19289129..81e50538a56 100644 --- a/app/api/google/[...path]/route.ts +++ b/app/api/google/[...path]/route.ts @@ -63,7 +63,9 @@ async function handle( ); } - const fetchUrl = `${baseUrl}/${path}?key=${key}`; + const fetchUrl = `${baseUrl}/${path}?key=${key}${ + req?.nextUrl?.searchParams?.get("alt") == "sse" ? "&alt=sse" : "" + }`; const fetchOptions: RequestInit = { headers: { "Content-Type": "application/json", diff --git a/app/client/platforms/google.ts b/app/client/platforms/google.ts index a786f5275f4..7ecba1de404 100644 --- a/app/client/platforms/google.ts +++ b/app/client/platforms/google.ts @@ -3,6 +3,12 @@ import { ChatOptions, getHeaders, LLMApi, LLMModel, LLMUsage } from "../api"; import { useAccessStore, useAppConfig, useChatStore } from "@/app/store"; import { getClientConfig } from "@/app/config/client"; import { DEFAULT_API_HOST } from "@/app/constant"; +import Locale from "../../locales"; +import { + EventStreamContentType, + fetchEventSource, +} from "@fortaine/fetch-event-source"; +import { prettyObject } from "@/app/utils/format"; import { getMessageTextContent, getMessageImages, @@ -20,7 +26,7 @@ export class GeminiProApi implements LLMApi { ); } async chat(options: ChatOptions): Promise { - // const apiClient = this; + const apiClient = this; let multimodal = false; const messages = options.messages.map((v) => { let parts: any[] = [{ text: getMessageTextContent(v) }]; @@ -120,7 +126,9 @@ export class GeminiProApi implements LLMApi { if (!baseUrl) { baseUrl = isApp - ? DEFAULT_API_HOST + "/api/proxy/google/" + Google.ChatPath(modelConfig.model) + ? DEFAULT_API_HOST + + "/api/proxy/google/" + + Google.ChatPath(modelConfig.model) : this.path(Google.ChatPath(modelConfig.model)); } @@ -139,16 +147,15 @@ export class GeminiProApi implements LLMApi { () => controller.abort(), REQUEST_TIMEOUT_MS, ); - + if (shouldStream) { let responseText = ""; let remainText = ""; let finished = false; - let existingTexts: string[] = []; const finish = () => { finished = true; - options.onFinish(existingTexts.join("")); + options.onFinish(responseText + remainText); }; // animate response to make it looks smooth @@ -173,72 +180,86 @@ export class GeminiProApi implements LLMApi { // start animaion animateResponseText(); - fetch( - baseUrl.replace("generateContent", "streamGenerateContent"), - chatPayload, - ) - .then((response) => { - const reader = response?.body?.getReader(); - const decoder = new TextDecoder(); - let partialData = ""; + controller.signal.onabort = finish; + + // https://github.com/google-gemini/cookbook/blob/main/quickstarts/rest/Streaming_REST.ipynb + const chatPath = + baseUrl.replace("generateContent", "streamGenerateContent") + + (baseUrl.indexOf("?") > -1 ? "&alt=sse" : "?alt=sse"); + console.log("chatPath", chatPath); + fetchEventSource(chatPath, { + ...chatPayload, + async onopen(res) { + clearTimeout(requestTimeoutId); + const contentType = res.headers.get("content-type"); + console.log( + "[Gemini] request response content type: ", + contentType, + ); - return reader?.read().then(function processText({ - done, - value, - }): Promise { - if (done) { - if (response.status !== 200) { - try { - let data = JSON.parse(ensureProperEnding(partialData)); - if (data && data[0].error) { - options.onError?.(new Error(data[0].error.message)); - } else { - options.onError?.(new Error("Request failed")); - } - } catch (_) { - options.onError?.(new Error("Request failed")); - } - } + if (contentType?.startsWith("text/plain")) { + responseText = await res.clone().text(); + return finish(); + } + + if ( + !res.ok || + !res.headers + .get("content-type") + ?.startsWith(EventStreamContentType) || + res.status !== 200 + ) { + const responseTexts = [responseText]; + let extraInfo = await res.clone().text(); + try { + const resJson = await res.clone().json(); + extraInfo = prettyObject(resJson); + } catch {} - console.log("Stream complete"); - // options.onFinish(responseText + remainText); - finished = true; - return Promise.resolve(); + if (res.status === 401) { + responseTexts.push(Locale.Error.Unauthorized); } - partialData += decoder.decode(value, { stream: true }); + if (extraInfo) { + responseTexts.push(extraInfo); + } - try { - let data = JSON.parse(ensureProperEnding(partialData)); + responseText = responseTexts.join("\n\n"); - const textArray = data.reduce( - (acc: string[], item: { candidates: any[] }) => { - const texts = item.candidates.map((candidate) => - candidate.content.parts - .map((part: { text: any }) => part.text) - .join(""), - ); - return acc.concat(texts); - }, - [], - ); + return finish(); + } + }, + onmessage(msg) { + if (msg.data === "[DONE]" || finished) { + return finish(); + } + const text = msg.data; + try { + const json = JSON.parse(text); + const delta = apiClient.extractMessage(json); - if (textArray.length > existingTexts.length) { - const deltaArray = textArray.slice(existingTexts.length); - existingTexts = textArray; - remainText += deltaArray.join(""); - } - } catch (error) { - // console.log("[Response Animation] error: ", error,partialData); - // skip error message when parsing json + if (delta) { + remainText += delta; } - return reader.read().then(processText); - }); - }) - .catch((error) => { - console.error("Error:", error); - }); + const blockReason = json?.promptFeedback?.blockReason; + if (blockReason) { + // being blocked + console.log(`[Google] [Safety Ratings] result:`, blockReason); + } + } catch (e) { + console.error("[Request] parse error", text, msg); + } + }, + onclose() { + finish(); + }, + onerror(e) { + options.onError?.(e); + throw e; + }, + openWhenHidden: true, + }); } else { const res = await fetch(baseUrl, chatPayload); clearTimeout(requestTimeoutId); @@ -252,7 +273,7 @@ export class GeminiProApi implements LLMApi { ), ); } - const message = this.extractMessage(resJson); + const message = apiClient.extractMessage(resJson); options.onFinish(message); } } catch (e) { From c4ad66f745a539602910f49156d90be5208986e0 Mon Sep 17 00:00:00 2001 From: lloydzhou Date: Mon, 1 Jul 2024 13:27:06 +0000 Subject: [PATCH 020/611] remove console.log --- app/client/platforms/google.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/app/client/platforms/google.ts b/app/client/platforms/google.ts index 7ecba1de404..4aac1dbff99 100644 --- a/app/client/platforms/google.ts +++ b/app/client/platforms/google.ts @@ -186,7 +186,6 @@ export class GeminiProApi implements LLMApi { const chatPath = baseUrl.replace("generateContent", "streamGenerateContent") + (baseUrl.indexOf("?") > -1 ? "&alt=sse" : "?alt=sse"); - console.log("chatPath", chatPath); fetchEventSource(chatPath, { ...chatPayload, async onopen(res) { From bbbf59c74a2813807a978021a8603ce55f35d04c Mon Sep 17 00:00:00 2001 From: licoy Date: Tue, 2 Jul 2024 10:24:19 +0800 Subject: [PATCH 021/611] Improve the Stability parameter control panel --- app/components/button.tsx | 3 + app/components/sd-list.module.scss | 0 app/components/sd-list.tsx | 3 - app/components/sd-panel.module.scss | 33 +++++ app/components/sd-panel.tsx | 220 ++++++++++++++++++++++++++++ app/components/sidebar.tsx | 4 +- app/components/ui-lib.module.scss | 13 ++ app/components/ui-lib.tsx | 7 +- app/locales/cn.ts | 10 ++ app/locales/en.ts | 11 +- app/styles/globals.scss | 3 +- 11 files changed, 299 insertions(+), 8 deletions(-) delete mode 100644 app/components/sd-list.module.scss delete mode 100644 app/components/sd-list.tsx create mode 100644 app/components/sd-panel.module.scss create mode 100644 app/components/sd-panel.tsx diff --git a/app/components/button.tsx b/app/components/button.tsx index 7a5633924c5..c6039acc292 100644 --- a/app/components/button.tsx +++ b/app/components/button.tsx @@ -1,6 +1,7 @@ import * as React from "react"; import styles from "./button.module.scss"; +import { CSSProperties } from "react"; export type ButtonType = "primary" | "danger" | null; @@ -16,6 +17,7 @@ export function IconButton(props: { disabled?: boolean; tabIndex?: number; autoFocus?: boolean; + style?: CSSProperties; }) { return (