Merge branch 'ChatGPTNextWeb:main' into main
This commit is contained in:
commit
47fb40d572
|
@ -66,4 +66,4 @@ ANTHROPIC_API_VERSION=
|
||||||
ANTHROPIC_URL=
|
ANTHROPIC_URL=
|
||||||
|
|
||||||
### (optional)
|
### (optional)
|
||||||
WHITE_WEBDEV_ENDPOINTS=
|
WHITE_WEBDAV_ENDPOINTS=
|
|
@ -340,7 +340,7 @@ For ByteDance: use `modelName@bytedance=deploymentName` to customize model name
|
||||||
|
|
||||||
Change default model
|
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:
|
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
|
- Each address must be a complete endpoint
|
||||||
|
|
|
@ -202,7 +202,7 @@ ByteDance Api Url.
|
||||||
|
|
||||||
如果你想禁用从链接解析预制设置,将此环境变量设置为 1 即可。
|
如果你想禁用从链接解析预制设置,将此环境变量设置为 1 即可。
|
||||||
|
|
||||||
### `WHITE_WEBDEV_ENDPOINTS` (可选)
|
### `WHITE_WEBDAV_ENDPOINTS` (可选)
|
||||||
|
|
||||||
如果你想增加允许访问的webdav服务地址,可以使用该选项,格式要求:
|
如果你想增加允许访问的webdav服务地址,可以使用该选项,格式要求:
|
||||||
- 每一个地址必须是一个完整的 endpoint
|
- 每一个地址必须是一个完整的 endpoint
|
||||||
|
|
|
@ -193,7 +193,7 @@ ByteDance API の URL。
|
||||||
|
|
||||||
リンクからのプリセット設定解析を無効にしたい場合は、この環境変数を 1 に設定します。
|
リンクからのプリセット設定解析を無効にしたい場合は、この環境変数を 1 に設定します。
|
||||||
|
|
||||||
### `WHITE_WEBDEV_ENDPOINTS` (オプション)
|
### `WHITE_WEBDAV_ENDPOINTS` (オプション)
|
||||||
|
|
||||||
アクセス許可を与える WebDAV サービスのアドレスを追加したい場合、このオプションを使用します。フォーマット要件:
|
アクセス許可を与える WebDAV サービスのアドレスを追加したい場合、このオプションを使用します。フォーマット要件:
|
||||||
- 各アドレスは完全なエンドポイントでなければなりません。
|
- 各アドレスは完全なエンドポイントでなければなりません。
|
||||||
|
|
|
@ -6,7 +6,7 @@ const config = getServerSideConfig();
|
||||||
|
|
||||||
const mergedAllowedWebDavEndpoints = [
|
const mergedAllowedWebDavEndpoints = [
|
||||||
...internalAllowedWebDavEndpoints,
|
...internalAllowedWebDavEndpoints,
|
||||||
...config.allowedWebDevEndpoints,
|
...config.allowedWebDavEndpoints,
|
||||||
].filter((domain) => Boolean(domain.trim()));
|
].filter((domain) => Boolean(domain.trim()));
|
||||||
|
|
||||||
const normalizeUrl = (url: string) => {
|
const normalizeUrl = (url: string) => {
|
||||||
|
|
|
@ -277,6 +277,7 @@ export class ChatGPTApi implements LLMApi {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (shouldStream) {
|
if (shouldStream) {
|
||||||
|
let index = -1;
|
||||||
const [tools, funcs] = usePluginStore
|
const [tools, funcs] = usePluginStore
|
||||||
.getState()
|
.getState()
|
||||||
.getAsTools(
|
.getAsTools(
|
||||||
|
@ -302,10 +303,10 @@ export class ChatGPTApi implements LLMApi {
|
||||||
}>;
|
}>;
|
||||||
const tool_calls = choices[0]?.delta?.tool_calls;
|
const tool_calls = choices[0]?.delta?.tool_calls;
|
||||||
if (tool_calls?.length > 0) {
|
if (tool_calls?.length > 0) {
|
||||||
const index = tool_calls[0]?.index;
|
|
||||||
const id = tool_calls[0]?.id;
|
const id = tool_calls[0]?.id;
|
||||||
const args = tool_calls[0]?.function?.arguments;
|
const args = tool_calls[0]?.function?.arguments;
|
||||||
if (id) {
|
if (id) {
|
||||||
|
index += 1;
|
||||||
runTools.push({
|
runTools.push({
|
||||||
id,
|
id,
|
||||||
type: tool_calls[0]?.type,
|
type: tool_calls[0]?.type,
|
||||||
|
@ -327,6 +328,8 @@ export class ChatGPTApi implements LLMApi {
|
||||||
toolCallMessage: any,
|
toolCallMessage: any,
|
||||||
toolCallResult: any[],
|
toolCallResult: any[],
|
||||||
) => {
|
) => {
|
||||||
|
// reset index value
|
||||||
|
index = -1;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
requestPayload?.messages?.splice(
|
requestPayload?.messages?.splice(
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
|
@ -21,6 +21,7 @@ import {
|
||||||
} from "./artifacts";
|
} from "./artifacts";
|
||||||
import { useChatStore } from "../store";
|
import { useChatStore } from "../store";
|
||||||
import { IconButton } from "./button";
|
import { IconButton } from "./button";
|
||||||
|
import { useAppConfig } from "../store/config";
|
||||||
|
|
||||||
export function Mermaid(props: { code: string }) {
|
export function Mermaid(props: { code: string }) {
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
@ -92,7 +93,9 @@ export function PreCode(props: { children: any }) {
|
||||||
}
|
}
|
||||||
}, 600);
|
}, 600);
|
||||||
|
|
||||||
const enableArtifacts = session.mask?.enableArtifacts !== false;
|
const config = useAppConfig();
|
||||||
|
const enableArtifacts =
|
||||||
|
session.mask?.enableArtifacts !== false && config.enableArtifacts;
|
||||||
|
|
||||||
//Wrap the paragraph for plain-text
|
//Wrap the paragraph for plain-text
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -128,8 +131,9 @@ export function PreCode(props: { children: any }) {
|
||||||
className="copy-code-button"
|
className="copy-code-button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (ref.current) {
|
if (ref.current) {
|
||||||
const code = ref.current.innerText;
|
copyToClipboard(
|
||||||
copyToClipboard(code);
|
ref.current.querySelector("code")?.innerText ?? "",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
></span>
|
></span>
|
||||||
|
@ -278,6 +282,20 @@ function _MarkDownContent(props: { content: string }) {
|
||||||
p: (pProps) => <p {...pProps} dir="auto" />,
|
p: (pProps) => <p {...pProps} dir="auto" />,
|
||||||
a: (aProps) => {
|
a: (aProps) => {
|
||||||
const href = aProps.href || "";
|
const href = aProps.href || "";
|
||||||
|
if (/\.(aac|mp3|opus|wav)$/.test(href)) {
|
||||||
|
return (
|
||||||
|
<figure>
|
||||||
|
<audio controls src={href}></audio>
|
||||||
|
</figure>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (/\.(3gp|3g2|webm|ogv|mpeg|mp4|avi)$/.test(href)) {
|
||||||
|
return (
|
||||||
|
<video controls width="99.9%">
|
||||||
|
<source src={href} />
|
||||||
|
</video>
|
||||||
|
);
|
||||||
|
}
|
||||||
const isInternal = /^\/#/i.test(href);
|
const isInternal = /^\/#/i.test(href);
|
||||||
const target = isInternal ? "_self" : aProps.target ?? "_blank";
|
const target = isInternal ? "_self" : aProps.target ?? "_blank";
|
||||||
return <a {...aProps} target={target} />;
|
return <a {...aProps} target={target} />;
|
||||||
|
|
|
@ -166,6 +166,7 @@ export function MaskConfig(props: {
|
||||||
></input>
|
></input>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
|
{globalConfig.enableArtifacts && (
|
||||||
<ListItem
|
<ListItem
|
||||||
title={Locale.Mask.Config.Artifacts.Title}
|
title={Locale.Mask.Config.Artifacts.Title}
|
||||||
subTitle={Locale.Mask.Config.Artifacts.SubTitle}
|
subTitle={Locale.Mask.Config.Artifacts.SubTitle}
|
||||||
|
@ -181,6 +182,7 @@ export function MaskConfig(props: {
|
||||||
}}
|
}}
|
||||||
></input>
|
></input>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
)}
|
||||||
|
|
||||||
{!props.shouldSyncFromGlobal ? (
|
{!props.shouldSyncFromGlobal ? (
|
||||||
<ListItem
|
<ListItem
|
||||||
|
|
|
@ -10,7 +10,29 @@
|
||||||
max-height: 240px;
|
max-height: 240px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
min-width: 300px;
|
min-width: 280px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.plugin-schema {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
input {
|
||||||
|
margin-right: 20px;
|
||||||
|
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -12,7 +12,6 @@ import EditIcon from "../icons/edit.svg";
|
||||||
import AddIcon from "../icons/add.svg";
|
import AddIcon from "../icons/add.svg";
|
||||||
import CloseIcon from "../icons/close.svg";
|
import CloseIcon from "../icons/close.svg";
|
||||||
import DeleteIcon from "../icons/delete.svg";
|
import DeleteIcon from "../icons/delete.svg";
|
||||||
import EyeIcon from "../icons/eye.svg";
|
|
||||||
import ConfirmIcon from "../icons/confirm.svg";
|
import ConfirmIcon from "../icons/confirm.svg";
|
||||||
import ReloadIcon from "../icons/reload.svg";
|
import ReloadIcon from "../icons/reload.svg";
|
||||||
import GithubIcon from "../icons/github.svg";
|
import GithubIcon from "../icons/github.svg";
|
||||||
|
@ -29,7 +28,6 @@ import {
|
||||||
import Locale from "../locales";
|
import Locale from "../locales";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { getClientConfig } from "../config/client";
|
|
||||||
|
|
||||||
export function PluginPage() {
|
export function PluginPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
@ -209,19 +207,11 @@ export function PluginPage() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles["mask-actions"]}>
|
<div className={styles["mask-actions"]}>
|
||||||
{m.builtin ? (
|
|
||||||
<IconButton
|
|
||||||
icon={<EyeIcon />}
|
|
||||||
text={Locale.Plugin.Item.View}
|
|
||||||
onClick={() => setEditingPluginId(m.id)}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<EditIcon />}
|
icon={<EditIcon />}
|
||||||
text={Locale.Plugin.Item.Edit}
|
text={Locale.Plugin.Item.Edit}
|
||||||
onClick={() => setEditingPluginId(m.id)}
|
onClick={() => setEditingPluginId(m.id)}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
{!m.builtin && (
|
{!m.builtin && (
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<DeleteIcon />}
|
icon={<DeleteIcon />}
|
||||||
|
@ -325,30 +315,13 @@ export function PluginPage() {
|
||||||
></PasswordInput>
|
></PasswordInput>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
)}
|
)}
|
||||||
{!getClientConfig()?.isApp && (
|
|
||||||
<ListItem
|
|
||||||
title={Locale.Plugin.Auth.Proxy}
|
|
||||||
subTitle={Locale.Plugin.Auth.ProxyDescription}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={editingPlugin?.usingProxy}
|
|
||||||
style={{ minWidth: 16 }}
|
|
||||||
onChange={(e) => {
|
|
||||||
pluginStore.updatePlugin(editingPlugin.id, (plugin) => {
|
|
||||||
plugin.usingProxy = e.currentTarget.checked;
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
></input>
|
|
||||||
</ListItem>
|
|
||||||
)}
|
|
||||||
</List>
|
</List>
|
||||||
<List>
|
<List>
|
||||||
<ListItem title={Locale.Plugin.EditModal.Content}>
|
<ListItem title={Locale.Plugin.EditModal.Content}>
|
||||||
<div style={{ display: "flex", justifyContent: "flex-end" }}>
|
<div className={pluginStyles["plugin-schema"]}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
style={{ minWidth: 200, marginRight: 20 }}
|
style={{ minWidth: 200 }}
|
||||||
onInput={(e) => setLoadUrl(e.currentTarget.value)}
|
onInput={(e) => setLoadUrl(e.currentTarget.value)}
|
||||||
></input>
|
></input>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
|
|
@ -1465,6 +1465,23 @@ export function Settings() {
|
||||||
}
|
}
|
||||||
></input>
|
></input>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
|
<ListItem
|
||||||
|
title={Locale.Mask.Config.Artifacts.Title}
|
||||||
|
subTitle={Locale.Mask.Config.Artifacts.SubTitle}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-label={Locale.Mask.Config.Artifacts.Title}
|
||||||
|
type="checkbox"
|
||||||
|
checked={config.enableArtifacts}
|
||||||
|
onChange={(e) =>
|
||||||
|
updateConfig(
|
||||||
|
(config) =>
|
||||||
|
(config.enableArtifacts = e.currentTarget.checked),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
></input>
|
||||||
|
</ListItem>
|
||||||
</List>
|
</List>
|
||||||
|
|
||||||
<SyncItems />
|
<SyncItems />
|
||||||
|
|
|
@ -154,8 +154,8 @@ export const getServerSideConfig = () => {
|
||||||
// `[Server Config] using ${randomIndex + 1} of ${apiKeys.length} api key`,
|
// `[Server Config] using ${randomIndex + 1} of ${apiKeys.length} api key`,
|
||||||
// );
|
// );
|
||||||
|
|
||||||
const allowedWebDevEndpoints = (
|
const allowedWebDavEndpoints = (
|
||||||
process.env.WHITE_WEBDEV_ENDPOINTS ?? ""
|
process.env.WHITE_WEBDAV_ENDPOINTS ?? ""
|
||||||
).split(",");
|
).split(",");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -229,6 +229,6 @@ export const getServerSideConfig = () => {
|
||||||
disableFastLink: !!process.env.DISABLE_FAST_LINK,
|
disableFastLink: !!process.env.DISABLE_FAST_LINK,
|
||||||
customModels,
|
customModels,
|
||||||
defaultModel,
|
defaultModel,
|
||||||
allowedWebDevEndpoints,
|
allowedWebDavEndpoints,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -615,6 +615,7 @@ export const useChatStore = createPersistStore(
|
||||||
providerName,
|
providerName,
|
||||||
},
|
},
|
||||||
onFinish(message) {
|
onFinish(message) {
|
||||||
|
if (!isValidMessage(message)) return;
|
||||||
get().updateCurrentSession(
|
get().updateCurrentSession(
|
||||||
(session) =>
|
(session) =>
|
||||||
(session.topic =
|
(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) {
|
updateStat(message: ChatMessage) {
|
||||||
|
|
|
@ -50,6 +50,8 @@ export const DEFAULT_CONFIG = {
|
||||||
enableAutoGenerateTitle: true,
|
enableAutoGenerateTitle: true,
|
||||||
sidebarWidth: DEFAULT_SIDEBAR_WIDTH,
|
sidebarWidth: DEFAULT_SIDEBAR_WIDTH,
|
||||||
|
|
||||||
|
enableArtifacts: true, // show artifacts config
|
||||||
|
|
||||||
disablePromptHint: false,
|
disablePromptHint: false,
|
||||||
|
|
||||||
dontShowMaskSplashScreen: false, // dont show splash screen when create chat
|
dontShowMaskSplashScreen: false, // dont show splash screen when create chat
|
||||||
|
|
|
@ -2,8 +2,12 @@ import OpenAPIClientAxios from "openapi-client-axios";
|
||||||
import { StoreKey } from "../constant";
|
import { StoreKey } from "../constant";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { createPersistStore } from "../utils/store";
|
import { createPersistStore } from "../utils/store";
|
||||||
|
import { getClientConfig } from "../config/client";
|
||||||
import yaml from "js-yaml";
|
import yaml from "js-yaml";
|
||||||
import { adapter } from "../utils";
|
import { adapter } from "../utils";
|
||||||
|
import { useAccessStore } from "./access";
|
||||||
|
|
||||||
|
const isApp = getClientConfig()?.isApp;
|
||||||
|
|
||||||
export type Plugin = {
|
export type Plugin = {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -16,7 +20,6 @@ export type Plugin = {
|
||||||
authLocation?: string;
|
authLocation?: string;
|
||||||
authHeader?: string;
|
authHeader?: string;
|
||||||
authToken?: string;
|
authToken?: string;
|
||||||
usingProxy?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FunctionToolItem = {
|
export type FunctionToolItem = {
|
||||||
|
@ -51,13 +54,20 @@ export const FunctionToolService = {
|
||||||
const authLocation = plugin?.authLocation || "header";
|
const authLocation = plugin?.authLocation || "header";
|
||||||
const definition = yaml.load(plugin.content) as any;
|
const definition = yaml.load(plugin.content) as any;
|
||||||
const serverURL = definition?.servers?.[0]?.url;
|
const serverURL = definition?.servers?.[0]?.url;
|
||||||
const baseURL = !!plugin?.usingProxy ? "/api/proxy" : serverURL;
|
const baseURL = !isApp ? "/api/proxy" : serverURL;
|
||||||
const headers: Record<string, string | undefined> = {
|
const headers: Record<string, string | undefined> = {
|
||||||
"X-Base-URL": !!plugin?.usingProxy ? serverURL : undefined,
|
"X-Base-URL": !isApp ? serverURL : undefined,
|
||||||
};
|
};
|
||||||
if (authLocation == "header") {
|
if (authLocation == "header") {
|
||||||
headers[headerName] = tokenValue;
|
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({
|
const api = new OpenAPIClientAxios({
|
||||||
definition: yaml.load(plugin.content) as any,
|
definition: yaml.load(plugin.content) as any,
|
||||||
axiosConfigDefaults: {
|
axiosConfigDefaults: {
|
||||||
|
@ -165,7 +175,7 @@ export const usePluginStore = createPersistStore(
|
||||||
(set, get) => ({
|
(set, get) => ({
|
||||||
create(plugin?: Partial<Plugin>) {
|
create(plugin?: Partial<Plugin>) {
|
||||||
const plugins = get().plugins;
|
const plugins = get().plugins;
|
||||||
const id = nanoid();
|
const id = plugin?.id || nanoid();
|
||||||
plugins[id] = {
|
plugins[id] = {
|
||||||
...createEmptyPlugin(),
|
...createEmptyPlugin(),
|
||||||
...plugin,
|
...plugin,
|
||||||
|
@ -220,5 +230,42 @@ export const usePluginStore = createPersistStore(
|
||||||
{
|
{
|
||||||
name: StoreKey.Plugin,
|
name: StoreKey.Plugin,
|
||||||
version: 1,
|
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;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
Loading…
Reference in New Issue