Merge branch 'main' into add_tip_top

This commit is contained in:
mayfwl 2024-09-25 16:19:55 +08:00 committed by GitHub
commit ea04595c5e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 183 additions and 70 deletions

View File

@ -66,4 +66,4 @@ ANTHROPIC_API_VERSION=
ANTHROPIC_URL=
### (optional)
WHITE_WEBDEV_ENDPOINTS=
WHITE_WEBDAV_ENDPOINTS=

View File

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

View File

@ -202,7 +202,7 @@ ByteDance Api Url.
如果你想禁用从链接解析预制设置,将此环境变量设置为 1 即可。
### `WHITE_WEBDEV_ENDPOINTS` (可选)
### `WHITE_WEBDAV_ENDPOINTS` (可选)
如果你想增加允许访问的webdav服务地址可以使用该选项格式要求
- 每一个地址必须是一个完整的 endpoint

View File

@ -193,7 +193,7 @@ ByteDance API の URL。
リンクからのプリセット設定解析を無効にしたい場合は、この環境変数を 1 に設定します。
### `WHITE_WEBDEV_ENDPOINTS` (オプション)
### `WHITE_WEBDAV_ENDPOINTS` (オプション)
アクセス許可を与える WebDAV サービスのアドレスを追加したい場合、このオプションを使用します。フォーマット要件:
- 各アドレスは完全なエンドポイントでなければなりません。

View File

@ -6,7 +6,7 @@ const config = getServerSideConfig();
const mergedAllowedWebDavEndpoints = [
...internalAllowedWebDavEndpoints,
...config.allowedWebDevEndpoints,
...config.allowedWebDavEndpoints,
].filter((domain) => Boolean(domain.trim()));
const normalizeUrl = (url: string) => {

View File

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

View File

@ -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<HTMLDivElement>(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 ?? "",
);
}
}}
></span>
@ -277,6 +283,20 @@ function _MarkDownContent(props: { content: string }) {
p: (pProps) => <p {...pProps} dir="auto" />,
a: (aProps) => {
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 target = isInternal ? "_self" : aProps.target ?? "_blank";
return <a {...aProps} target={target} />;

View File

@ -166,21 +166,23 @@ export function MaskConfig(props: {
></input>
</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={props.mask.enableArtifacts !== false}
onChange={(e) => {
props.updateMask((mask) => {
mask.enableArtifacts = e.currentTarget.checked;
});
}}
></input>
</ListItem>
{globalConfig.enableArtifacts && (
<ListItem
title={Locale.Mask.Config.Artifacts.Title}
subTitle={Locale.Mask.Config.Artifacts.SubTitle}
>
<input
aria-label={Locale.Mask.Config.Artifacts.Title}
type="checkbox"
checked={props.mask.enableArtifacts !== false}
onChange={(e) => {
props.updateMask((mask) => {
mask.enableArtifacts = e.currentTarget.checked;
});
}}
></input>
</ListItem>
)}
{!props.shouldSyncFromGlobal ? (
<ListItem

View File

@ -10,7 +10,29 @@
max-height: 240px;
overflow-y: auto;
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;
}
}
}

View File

@ -12,7 +12,6 @@ import EditIcon from "../icons/edit.svg";
import AddIcon from "../icons/add.svg";
import CloseIcon from "../icons/close.svg";
import DeleteIcon from "../icons/delete.svg";
import EyeIcon from "../icons/eye.svg";
import ConfirmIcon from "../icons/confirm.svg";
import ReloadIcon from "../icons/reload.svg";
import GithubIcon from "../icons/github.svg";
@ -29,7 +28,6 @@ import {
import Locale from "../locales";
import { useNavigate } from "react-router-dom";
import { useState } from "react";
import { getClientConfig } from "../config/client";
export function PluginPage() {
const navigate = useNavigate();
@ -209,19 +207,11 @@ export function PluginPage() {
</div>
</div>
<div className={styles["mask-actions"]}>
{m.builtin ? (
<IconButton
icon={<EyeIcon />}
text={Locale.Plugin.Item.View}
onClick={() => setEditingPluginId(m.id)}
/>
) : (
<IconButton
icon={<EditIcon />}
text={Locale.Plugin.Item.Edit}
onClick={() => setEditingPluginId(m.id)}
/>
)}
<IconButton
icon={<EditIcon />}
text={Locale.Plugin.Item.Edit}
onClick={() => setEditingPluginId(m.id)}
/>
{!m.builtin && (
<IconButton
icon={<DeleteIcon />}
@ -325,30 +315,13 @@ export function PluginPage() {
></PasswordInput>
</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>
<ListItem title={Locale.Plugin.EditModal.Content}>
<div style={{ display: "flex", justifyContent: "flex-end" }}>
<div className={pluginStyles["plugin-schema"]}>
<input
type="text"
style={{ minWidth: 200, marginRight: 20 }}
style={{ minWidth: 200 }}
onInput={(e) => setLoadUrl(e.currentTarget.value)}
></input>
<IconButton

View File

@ -1492,6 +1492,23 @@ export function Settings() {
}
></input>
</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>
<SyncItems />

View File

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

View File

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

View File

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

View File

@ -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<string, string | undefined> = {
"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<Plugin>) {
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;
});
});
});
});
},
},
);

View File

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

17
public/plugins.json Normal file
View File

@ -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"
}
]

View File

@ -9,7 +9,7 @@
},
"package": {
"productName": "NextChat",
"version": "2.15.2"
"version": "2.15.3"
},
"tauri": {
"allowlist": {