diff --git a/.env.template b/.env.template index 25addf2b3..82f44216a 100644 --- a/.env.template +++ b/.env.template @@ -66,4 +66,4 @@ ANTHROPIC_API_VERSION= ANTHROPIC_URL= ### (optional) -WHITE_WEBDEV_ENDPOINTS= \ No newline at end of file +WHITE_WEBDAV_ENDPOINTS= \ No newline at end of file diff --git a/README.md b/README.md index c8b158956..2001d4d88 100644 --- a/README.md +++ b/README.md @@ -340,7 +340,7 @@ For ByteDance: use `modelName@bytedance=deploymentName` to customize model name Change default model -### `WHITE_WEBDEV_ENDPOINTS` (optional) +### `WHITE_WEBDAV_ENDPOINTS` (optional) You can use this option if you want to increase the number of webdav service addresses you are allowed to access, as required by the format: - Each address must be a complete endpoint diff --git a/README_CN.md b/README_CN.md index beed396c5..7831e2ee9 100644 --- a/README_CN.md +++ b/README_CN.md @@ -202,7 +202,7 @@ ByteDance Api Url. 如果你想禁用从链接解析预制设置,将此环境变量设置为 1 即可。 -### `WHITE_WEBDEV_ENDPOINTS` (可选) +### `WHITE_WEBDAV_ENDPOINTS` (可选) 如果你想增加允许访问的webdav服务地址,可以使用该选项,格式要求: - 每一个地址必须是一个完整的 endpoint diff --git a/README_JA.md b/README_JA.md index 6b8caadae..1716089af 100644 --- a/README_JA.md +++ b/README_JA.md @@ -193,7 +193,7 @@ ByteDance API の URL。 リンクからのプリセット設定解析を無効にしたい場合は、この環境変数を 1 に設定します。 -### `WHITE_WEBDEV_ENDPOINTS` (オプション) +### `WHITE_WEBDAV_ENDPOINTS` (オプション) アクセス許可を与える WebDAV サービスのアドレスを追加したい場合、このオプションを使用します。フォーマット要件: - 各アドレスは完全なエンドポイントでなければなりません。 diff --git a/app/api/webdav/[...path]/route.ts b/app/api/webdav/[...path]/route.ts index 9f96cbfcf..bb7743bda 100644 --- a/app/api/webdav/[...path]/route.ts +++ b/app/api/webdav/[...path]/route.ts @@ -6,7 +6,7 @@ const config = getServerSideConfig(); const mergedAllowedWebDavEndpoints = [ ...internalAllowedWebDavEndpoints, - ...config.allowedWebDevEndpoints, + ...config.allowedWebDavEndpoints, ].filter((domain) => Boolean(domain.trim())); const normalizeUrl = (url: string) => { diff --git a/app/client/platforms/openai.ts b/app/client/platforms/openai.ts index d86be718b..0a8d6203a 100644 --- a/app/client/platforms/openai.ts +++ b/app/client/platforms/openai.ts @@ -277,6 +277,7 @@ export class ChatGPTApi implements LLMApi { ); } if (shouldStream) { + let index = -1; const [tools, funcs] = usePluginStore .getState() .getAsTools( @@ -302,10 +303,10 @@ export class ChatGPTApi implements LLMApi { }>; const tool_calls = choices[0]?.delta?.tool_calls; if (tool_calls?.length > 0) { - const index = tool_calls[0]?.index; const id = tool_calls[0]?.id; const args = tool_calls[0]?.function?.arguments; if (id) { + index += 1; runTools.push({ id, type: tool_calls[0]?.type, @@ -327,6 +328,8 @@ export class ChatGPTApi implements LLMApi { toolCallMessage: any, toolCallResult: any[], ) => { + // reset index value + index = -1; // @ts-ignore requestPayload?.messages?.splice( // @ts-ignore diff --git a/app/components/markdown.tsx b/app/components/markdown.tsx index 9e1d17a30..6e340d065 100644 --- a/app/components/markdown.tsx +++ b/app/components/markdown.tsx @@ -21,6 +21,9 @@ import { } from "./artifacts"; import { useChatStore } from "../store"; import { IconButton } from "./button"; + +import { useAppConfig } from "../store/config"; + export function Mermaid(props: { code: string }) { const ref = useRef(null); const [hasError, setHasError] = useState(false); @@ -91,7 +94,9 @@ export function PreCode(props: { children: any }) { } }, 600); - const enableArtifacts = session.mask?.enableArtifacts !== false; + const config = useAppConfig(); + const enableArtifacts = + session.mask?.enableArtifacts !== false && config.enableArtifacts; //Wrap the paragraph for plain-text useEffect(() => { @@ -127,8 +132,9 @@ export function PreCode(props: { children: any }) { className="copy-code-button" onClick={() => { if (ref.current) { - const code = ref.current.innerText; - copyToClipboard(code); + copyToClipboard( + ref.current.querySelector("code")?.innerText ?? "", + ); } }} > @@ -277,6 +283,20 @@ function _MarkDownContent(props: { content: string }) { p: (pProps) =>

, a: (aProps) => { const href = aProps.href || ""; + if (/\.(aac|mp3|opus|wav)$/.test(href)) { + return ( +

+ +
+ ); + } + if (/\.(3gp|3g2|webm|ogv|mpeg|mp4|avi)$/.test(href)) { + return ( + + ); + } const isInternal = /^\/#/i.test(href); const target = isInternal ? "_self" : aProps.target ?? "_blank"; return ; diff --git a/app/components/mask.tsx b/app/components/mask.tsx index e4dd90826..c60e7a528 100644 --- a/app/components/mask.tsx +++ b/app/components/mask.tsx @@ -166,21 +166,23 @@ export function MaskConfig(props: { > - - { - props.updateMask((mask) => { - mask.enableArtifacts = e.currentTarget.checked; - }); - }} - > - + {globalConfig.enableArtifacts && ( + + { + props.updateMask((mask) => { + mask.enableArtifacts = e.currentTarget.checked; + }); + }} + > + + )} {!props.shouldSyncFromGlobal ? (
- {m.builtin ? ( - } - text={Locale.Plugin.Item.View} - onClick={() => setEditingPluginId(m.id)} - /> - ) : ( - } - text={Locale.Plugin.Item.Edit} - onClick={() => setEditingPluginId(m.id)} - /> - )} + } + text={Locale.Plugin.Item.Edit} + onClick={() => setEditingPluginId(m.id)} + /> {!m.builtin && ( } @@ -325,30 +315,13 @@ export function PluginPage() { > )} - {!getClientConfig()?.isApp && ( - - { - pluginStore.updatePlugin(editingPlugin.id, (plugin) => { - plugin.usingProxy = e.currentTarget.checked; - }); - }} - > - - )} -
+
setLoadUrl(e.currentTarget.value)} > + + + + updateConfig( + (config) => + (config.enableArtifacts = e.currentTarget.checked), + ) + } + > + diff --git a/app/config/server.ts b/app/config/server.ts index 676b0174f..6544fe564 100644 --- a/app/config/server.ts +++ b/app/config/server.ts @@ -154,8 +154,8 @@ export const getServerSideConfig = () => { // `[Server Config] using ${randomIndex + 1} of ${apiKeys.length} api key`, // ); - const allowedWebDevEndpoints = ( - process.env.WHITE_WEBDEV_ENDPOINTS ?? "" + const allowedWebDavEndpoints = ( + process.env.WHITE_WEBDAV_ENDPOINTS ?? "" ).split(","); return { @@ -229,6 +229,6 @@ export const getServerSideConfig = () => { disableFastLink: !!process.env.DISABLE_FAST_LINK, customModels, defaultModel, - allowedWebDevEndpoints, + allowedWebDavEndpoints, }; }; diff --git a/app/store/chat.ts b/app/store/chat.ts index 86de99836..968d8cb64 100644 --- a/app/store/chat.ts +++ b/app/store/chat.ts @@ -615,6 +615,7 @@ export const useChatStore = createPersistStore( providerName, }, onFinish(message) { + if (!isValidMessage(message)) return; get().updateCurrentSession( (session) => (session.topic = @@ -690,6 +691,10 @@ export const useChatStore = createPersistStore( }, }); } + + function isValidMessage(message: any): boolean { + return typeof message === "string" && !message.startsWith("```json"); + } }, updateStat(message: ChatMessage) { diff --git a/app/store/config.ts b/app/store/config.ts index 615cc8e82..3dcd4d86b 100644 --- a/app/store/config.ts +++ b/app/store/config.ts @@ -50,6 +50,8 @@ export const DEFAULT_CONFIG = { enableAutoGenerateTitle: true, sidebarWidth: DEFAULT_SIDEBAR_WIDTH, + enableArtifacts: true, // show artifacts config + disablePromptHint: false, dontShowMaskSplashScreen: false, // dont show splash screen when create chat diff --git a/app/store/plugin.ts b/app/store/plugin.ts index 44679cbdc..84ae0816e 100644 --- a/app/store/plugin.ts +++ b/app/store/plugin.ts @@ -2,8 +2,12 @@ import OpenAPIClientAxios from "openapi-client-axios"; import { StoreKey } from "../constant"; import { nanoid } from "nanoid"; import { createPersistStore } from "../utils/store"; +import { getClientConfig } from "../config/client"; import yaml from "js-yaml"; import { adapter } from "../utils"; +import { useAccessStore } from "./access"; + +const isApp = getClientConfig()?.isApp; export type Plugin = { id: string; @@ -16,7 +20,6 @@ export type Plugin = { authLocation?: string; authHeader?: string; authToken?: string; - usingProxy?: boolean; }; export type FunctionToolItem = { @@ -46,18 +49,25 @@ export const FunctionToolService = { plugin?.authType == "basic" ? `Basic ${plugin?.authToken}` : plugin?.authType == "bearer" - ? ` Bearer ${plugin?.authToken}` + ? `Bearer ${plugin?.authToken}` : plugin?.authToken; const authLocation = plugin?.authLocation || "header"; const definition = yaml.load(plugin.content) as any; const serverURL = definition?.servers?.[0]?.url; - const baseURL = !!plugin?.usingProxy ? "/api/proxy" : serverURL; + const baseURL = !isApp ? "/api/proxy" : serverURL; const headers: Record = { - "X-Base-URL": !!plugin?.usingProxy ? serverURL : undefined, + "X-Base-URL": !isApp ? serverURL : undefined, }; if (authLocation == "header") { headers[headerName] = tokenValue; } + // try using openaiApiKey for Dalle3 Plugin. + if (!tokenValue && plugin.id === "dalle3") { + const openaiApiKey = useAccessStore.getState().openaiApiKey; + if (openaiApiKey) { + headers[headerName] = `Bearer ${openaiApiKey}`; + } + } const api = new OpenAPIClientAxios({ definition: yaml.load(plugin.content) as any, axiosConfigDefaults: { @@ -165,7 +175,7 @@ export const usePluginStore = createPersistStore( (set, get) => ({ create(plugin?: Partial) { const plugins = get().plugins; - const id = nanoid(); + const id = plugin?.id || nanoid(); plugins[id] = { ...createEmptyPlugin(), ...plugin, @@ -220,5 +230,42 @@ export const usePluginStore = createPersistStore( { name: StoreKey.Plugin, version: 1, + onRehydrateStorage(state) { + // Skip store rehydration on server side + if (typeof window === "undefined") { + return; + } + + fetch("./plugins.json") + .then((res) => res.json()) + .then((res) => { + Promise.all( + res.map((item: any) => + // skip get schema + state.get(item.id) + ? item + : fetch(item.schema) + .then((res) => res.text()) + .then((content) => ({ + ...item, + content, + })) + .catch((e) => item), + ), + ).then((builtinPlugins: any) => { + builtinPlugins + .filter((item: any) => item?.content) + .forEach((item: any) => { + const plugin = state.create(item); + state.updatePlugin(plugin.id, (plugin) => { + const tool = FunctionToolService.add(plugin, true); + plugin.title = tool.api.definition.info.title; + plugin.version = tool.api.definition.info.version; + plugin.builtin = true; + }); + }); + }); + }); + }, }, ); diff --git a/app/store/prompt.ts b/app/store/prompt.ts index a25cda581..f746edecd 100644 --- a/app/store/prompt.ts +++ b/app/store/prompt.ts @@ -1,7 +1,7 @@ import Fuse from "fuse.js"; -import { getLang } from "../locales"; -import { StoreKey } from "../constant"; import { nanoid } from "nanoid"; +import { StoreKey } from "../constant"; +import { getLang } from "../locales"; import { createPersistStore } from "../utils/store"; export interface Prompt { @@ -147,6 +147,11 @@ export const usePromptStore = createPersistStore( }, onRehydrateStorage(state) { + // Skip store rehydration on server side + if (typeof window === "undefined") { + return; + } + const PROMPT_URL = "./prompts.json"; type PromptList = Array<[string, string]>; diff --git a/public/plugins.json b/public/plugins.json new file mode 100644 index 000000000..c4d7ec46a --- /dev/null +++ b/public/plugins.json @@ -0,0 +1,17 @@ +[ + { + "id": "dalle3", + "name": "Dalle3", + "schema": "https://ghp.ci/https://raw.githubusercontent.com/ChatGPTNextWeb/NextChat-Awesome-Plugins/main/plugins/dalle/openapi.json" + }, + { + "id": "arxivsearch", + "name": "ArxivSearch", + "schema": "https://ghp.ci/https://raw.githubusercontent.com/ChatGPTNextWeb/NextChat-Awesome-Plugins/main/plugins/arxivsearch/openapi.json" + }, + { + "id": "duckduckgolite", + "name": "DuckDuckGoLiteSearch", + "schema": "https://ghp.ci/https://raw.githubusercontent.com/ChatGPTNextWeb/NextChat-Awesome-Plugins/main/plugins/duckduckgolite/openapi.json" + } +] diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 2a19c9332..eb0d411cb 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -9,7 +9,7 @@ }, "package": { "productName": "NextChat", - "version": "2.15.2" + "version": "2.15.3" }, "tauri": { "allowlist": {