Merge remote-tracking branch 'connectai/main' into feature/H0llyW00dzZ-updater
This commit is contained in:
commit
2419083adf
|
@ -3,9 +3,7 @@ name: VercelPreviewDeployment
|
||||||
on:
|
on:
|
||||||
pull_request_target:
|
pull_request_target:
|
||||||
types:
|
types:
|
||||||
- opened
|
- review_requested
|
||||||
- synchronize
|
|
||||||
- reopened
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
VERCEL_TEAM: ${{ secrets.VERCEL_TEAM }}
|
VERCEL_TEAM: ${{ secrets.VERCEL_TEAM }}
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
name: Run Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
tags:
|
||||||
|
- "!*"
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
cache: "yarn"
|
||||||
|
|
||||||
|
- name: Cache node_modules
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: node_modules
|
||||||
|
key: ${{ runner.os }}-node_modules-${{ hashFiles('**/yarn.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-node_modules-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: yarn install
|
||||||
|
|
||||||
|
- name: Run Jest tests
|
||||||
|
run: yarn test:ci
|
|
@ -352,7 +352,7 @@ export class ChatGPTApi implements LLMApi {
|
||||||
// make a fetch request
|
// make a fetch request
|
||||||
const requestTimeoutId = setTimeout(
|
const requestTimeoutId = setTimeout(
|
||||||
() => controller.abort(),
|
() => controller.abort(),
|
||||||
isDalle3 || isO1 ? REQUEST_TIMEOUT_MS * 2 : REQUEST_TIMEOUT_MS, // dalle3 using b64_json is slow.
|
isDalle3 || isO1 ? REQUEST_TIMEOUT_MS * 4 : REQUEST_TIMEOUT_MS, // dalle3 using b64_json is slow.
|
||||||
);
|
);
|
||||||
|
|
||||||
const res = await fetch(chatPath, chatPayload);
|
const res = await fetch(chatPath, chatPayload);
|
||||||
|
|
|
@ -11,6 +11,7 @@ import Logo from "../icons/logo.svg";
|
||||||
import { useMobileScreen } from "@/app/utils";
|
import { useMobileScreen } from "@/app/utils";
|
||||||
import BotIcon from "../icons/bot.svg";
|
import BotIcon from "../icons/bot.svg";
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
|
import { PasswordInput } from "./ui-lib";
|
||||||
import LeftIcon from "@/app/icons/left.svg";
|
import LeftIcon from "@/app/icons/left.svg";
|
||||||
import { safeLocalStorage } from "@/app/utils";
|
import { safeLocalStorage } from "@/app/utils";
|
||||||
import {
|
import {
|
||||||
|
@ -60,36 +61,43 @@ export function AuthPage() {
|
||||||
<div className={styles["auth-title"]}>{Locale.Auth.Title}</div>
|
<div className={styles["auth-title"]}>{Locale.Auth.Title}</div>
|
||||||
<div className={styles["auth-tips"]}>{Locale.Auth.Tips}</div>
|
<div className={styles["auth-tips"]}>{Locale.Auth.Tips}</div>
|
||||||
|
|
||||||
<input
|
<PasswordInput
|
||||||
className={styles["auth-input"]}
|
style={{ marginTop: "3vh", marginBottom: "3vh" }}
|
||||||
type="password"
|
aria={Locale.Settings.ShowPassword}
|
||||||
placeholder={Locale.Auth.Input}
|
aria-label={Locale.Auth.Input}
|
||||||
value={accessStore.accessCode}
|
value={accessStore.accessCode}
|
||||||
|
type="text"
|
||||||
|
placeholder={Locale.Auth.Input}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
accessStore.update(
|
accessStore.update(
|
||||||
(access) => (access.accessCode = e.currentTarget.value),
|
(access) => (access.accessCode = e.currentTarget.value),
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!accessStore.hideUserApiKey ? (
|
{!accessStore.hideUserApiKey ? (
|
||||||
<>
|
<>
|
||||||
<div className={styles["auth-tips"]}>{Locale.Auth.SubTips}</div>
|
<div className={styles["auth-tips"]}>{Locale.Auth.SubTips}</div>
|
||||||
<input
|
<PasswordInput
|
||||||
className={styles["auth-input"]}
|
style={{ marginTop: "3vh", marginBottom: "3vh" }}
|
||||||
type="password"
|
aria={Locale.Settings.ShowPassword}
|
||||||
placeholder={Locale.Settings.Access.OpenAI.ApiKey.Placeholder}
|
aria-label={Locale.Settings.Access.OpenAI.ApiKey.Placeholder}
|
||||||
value={accessStore.openaiApiKey}
|
value={accessStore.openaiApiKey}
|
||||||
|
type="text"
|
||||||
|
placeholder={Locale.Settings.Access.OpenAI.ApiKey.Placeholder}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
accessStore.update(
|
accessStore.update(
|
||||||
(access) => (access.openaiApiKey = e.currentTarget.value),
|
(access) => (access.openaiApiKey = e.currentTarget.value),
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<input
|
<PasswordInput
|
||||||
className={styles["auth-input-second"]}
|
style={{ marginTop: "3vh", marginBottom: "3vh" }}
|
||||||
type="password"
|
aria={Locale.Settings.ShowPassword}
|
||||||
placeholder={Locale.Settings.Access.Google.ApiKey.Placeholder}
|
aria-label={Locale.Settings.Access.Google.ApiKey.Placeholder}
|
||||||
value={accessStore.googleApiKey}
|
value={accessStore.googleApiKey}
|
||||||
|
type="text"
|
||||||
|
placeholder={Locale.Settings.Access.Google.ApiKey.Placeholder}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
accessStore.update(
|
accessStore.update(
|
||||||
(access) => (access.googleApiKey = e.currentTarget.value),
|
(access) => (access.googleApiKey = e.currentTarget.value),
|
||||||
|
|
|
@ -115,11 +115,14 @@ import { getClientConfig } from "../config/client";
|
||||||
import { useAllModels } from "../utils/hooks";
|
import { useAllModels } from "../utils/hooks";
|
||||||
import { MultimodalContent } from "../client/api";
|
import { MultimodalContent } from "../client/api";
|
||||||
|
|
||||||
const localStorage = safeLocalStorage();
|
|
||||||
import { ClientApi } from "../client/api";
|
import { ClientApi } from "../client/api";
|
||||||
import { createTTSPlayer } from "../utils/audio";
|
import { createTTSPlayer } from "../utils/audio";
|
||||||
import { MsEdgeTTS, OUTPUT_FORMAT } from "../utils/ms_edge_tts";
|
import { MsEdgeTTS, OUTPUT_FORMAT } from "../utils/ms_edge_tts";
|
||||||
|
|
||||||
|
import { isEmpty } from "lodash-es";
|
||||||
|
|
||||||
|
const localStorage = safeLocalStorage();
|
||||||
|
|
||||||
const ttsPlayer = createTTSPlayer();
|
const ttsPlayer = createTTSPlayer();
|
||||||
|
|
||||||
const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
|
const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
|
||||||
|
@ -1015,7 +1018,7 @@ function _Chat() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const doSubmit = (userInput: string) => {
|
const doSubmit = (userInput: string) => {
|
||||||
if (userInput.trim() === "") return;
|
if (userInput.trim() === "" && isEmpty(attachImages)) return;
|
||||||
const matchCommand = chatCommands.match(userInput);
|
const matchCommand = chatCommands.match(userInput);
|
||||||
if (matchCommand.matched) {
|
if (matchCommand.matched) {
|
||||||
setUserInput("");
|
setUserInput("");
|
||||||
|
|
|
@ -140,6 +140,9 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
&-narrow {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-logo {
|
.sidebar-logo {
|
||||||
|
|
|
@ -169,6 +169,12 @@ export function PreCode(props: { children: any }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function CustomCode(props: { children: any; className?: string }) {
|
function CustomCode(props: { children: any; className?: string }) {
|
||||||
|
const chatStore = useChatStore();
|
||||||
|
const session = chatStore.currentSession();
|
||||||
|
const config = useAppConfig();
|
||||||
|
const enableCodeFold =
|
||||||
|
session.mask?.enableCodeFold !== false && config.enableCodeFold;
|
||||||
|
|
||||||
const ref = useRef<HTMLPreElement>(null);
|
const ref = useRef<HTMLPreElement>(null);
|
||||||
const [collapsed, setCollapsed] = useState(true);
|
const [collapsed, setCollapsed] = useState(true);
|
||||||
const [showToggle, setShowToggle] = useState(false);
|
const [showToggle, setShowToggle] = useState(false);
|
||||||
|
@ -184,25 +190,30 @@ function CustomCode(props: { children: any; className?: string }) {
|
||||||
const toggleCollapsed = () => {
|
const toggleCollapsed = () => {
|
||||||
setCollapsed((collapsed) => !collapsed);
|
setCollapsed((collapsed) => !collapsed);
|
||||||
};
|
};
|
||||||
|
const renderShowMoreButton = () => {
|
||||||
|
if (showToggle && enableCodeFold && collapsed) {
|
||||||
|
return (
|
||||||
|
<div className={`show-hide-button ${collapsed ? "collapsed" : "expanded"}`}>
|
||||||
|
<button onClick={toggleCollapsed}>{Locale.NewChat.More}</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<code
|
<code
|
||||||
className={props?.className}
|
className={props?.className}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
style={{
|
style={{
|
||||||
maxHeight: collapsed ? "400px" : "none",
|
maxHeight: enableCodeFold && collapsed ? "400px" : "none",
|
||||||
overflowY: "hidden",
|
overflowY: "hidden",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
</code>
|
</code>
|
||||||
{showToggle && collapsed && (
|
|
||||||
<div
|
{renderShowMoreButton()}
|
||||||
className={`show-hide-button ${collapsed ? "collapsed" : "expanded"}`}
|
|
||||||
>
|
|
||||||
<button onClick={toggleCollapsed}>{Locale.NewChat.More}</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -183,6 +183,23 @@ export function MaskConfig(props: {
|
||||||
></input>
|
></input>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
)}
|
)}
|
||||||
|
{globalConfig.enableCodeFold && (
|
||||||
|
<ListItem
|
||||||
|
title={Locale.Mask.Config.CodeFold.Title}
|
||||||
|
subTitle={Locale.Mask.Config.CodeFold.SubTitle}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-label={Locale.Mask.Config.CodeFold.Title}
|
||||||
|
type="checkbox"
|
||||||
|
checked={props.mask.enableCodeFold !== false}
|
||||||
|
onChange={(e) => {
|
||||||
|
props.updateMask((mask) => {
|
||||||
|
mask.enableCodeFold = e.currentTarget.checked;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
></input>
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
|
|
||||||
{!props.shouldSyncFromGlobal ? (
|
{!props.shouldSyncFromGlobal ? (
|
||||||
<ListItem
|
<ListItem
|
||||||
|
|
|
@ -1517,6 +1517,22 @@ export function Settings() {
|
||||||
}
|
}
|
||||||
></input>
|
></input>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
<ListItem
|
||||||
|
title={Locale.Mask.Config.CodeFold.Title}
|
||||||
|
subTitle={Locale.Mask.Config.CodeFold.SubTitle}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-label={Locale.Mask.Config.CodeFold.Title}
|
||||||
|
type="checkbox"
|
||||||
|
checked={config.enableCodeFold}
|
||||||
|
data-testid="enable-code-fold-checkbox"
|
||||||
|
onChange={(e) =>
|
||||||
|
updateConfig(
|
||||||
|
(config) => (config.enableCodeFold = e.currentTarget.checked),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
></input>
|
||||||
|
</ListItem>
|
||||||
</List>
|
</List>
|
||||||
|
|
||||||
<SyncItems />
|
<SyncItems />
|
||||||
|
|
|
@ -165,11 +165,17 @@ export function SideBarHeader(props: {
|
||||||
subTitle?: string | React.ReactNode;
|
subTitle?: string | React.ReactNode;
|
||||||
logo?: React.ReactNode;
|
logo?: React.ReactNode;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
|
shouldNarrow?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { title, subTitle, logo, children } = props;
|
const { title, subTitle, logo, children, shouldNarrow } = props;
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div className={styles["sidebar-header"]} data-tauri-drag-region>
|
<div
|
||||||
|
className={`${styles["sidebar-header"]} ${
|
||||||
|
shouldNarrow ? styles["sidebar-header-narrow"] : ""
|
||||||
|
}`}
|
||||||
|
data-tauri-drag-region
|
||||||
|
>
|
||||||
<div className={styles["sidebar-title-container"]}>
|
<div className={styles["sidebar-title-container"]}>
|
||||||
<div className={styles["sidebar-title"]} data-tauri-drag-region>
|
<div className={styles["sidebar-title"]} data-tauri-drag-region>
|
||||||
{title}
|
{title}
|
||||||
|
@ -227,6 +233,7 @@ export function SideBar(props: { className?: string }) {
|
||||||
title="NextChat"
|
title="NextChat"
|
||||||
subTitle="Build your own AI assistant."
|
subTitle="Build your own AI assistant."
|
||||||
logo={<ChatGptIcon />}
|
logo={<ChatGptIcon />}
|
||||||
|
shouldNarrow={shouldNarrow}
|
||||||
>
|
>
|
||||||
<div className={styles["sidebar-header-bar"]}>
|
<div className={styles["sidebar-header-bar"]}>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
|
|
@ -497,8 +497,8 @@ const cn = {
|
||||||
|
|
||||||
Model: "模型 (model)",
|
Model: "模型 (model)",
|
||||||
CompressModel: {
|
CompressModel: {
|
||||||
Title: "压缩模型",
|
Title: "对话摘要模型",
|
||||||
SubTitle: "用于压缩历史记录的模型",
|
SubTitle: "用于压缩历史记录、生成对话标题的模型",
|
||||||
},
|
},
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "随机性 (temperature)",
|
Title: "随机性 (temperature)",
|
||||||
|
@ -667,6 +667,10 @@ const cn = {
|
||||||
Title: "启用Artifacts",
|
Title: "启用Artifacts",
|
||||||
SubTitle: "启用之后可以直接渲染HTML页面",
|
SubTitle: "启用之后可以直接渲染HTML页面",
|
||||||
},
|
},
|
||||||
|
CodeFold: {
|
||||||
|
Title: "启用代码折叠",
|
||||||
|
SubTitle: "启用之后可以自动折叠/展开过长的代码块",
|
||||||
|
},
|
||||||
Share: {
|
Share: {
|
||||||
Title: "分享此面具",
|
Title: "分享此面具",
|
||||||
SubTitle: "生成此面具的直达链接",
|
SubTitle: "生成此面具的直达链接",
|
||||||
|
|
|
@ -502,8 +502,8 @@ const en: LocaleType = {
|
||||||
|
|
||||||
Model: "Model",
|
Model: "Model",
|
||||||
CompressModel: {
|
CompressModel: {
|
||||||
Title: "Compression Model",
|
Title: "Summary Model",
|
||||||
SubTitle: "Model used to compress history",
|
SubTitle: "Model used to compress history and generate title",
|
||||||
},
|
},
|
||||||
Temperature: {
|
Temperature: {
|
||||||
Title: "Temperature",
|
Title: "Temperature",
|
||||||
|
@ -677,6 +677,11 @@ const en: LocaleType = {
|
||||||
Title: "Enable Artifacts",
|
Title: "Enable Artifacts",
|
||||||
SubTitle: "Can render HTML page when enable artifacts.",
|
SubTitle: "Can render HTML page when enable artifacts.",
|
||||||
},
|
},
|
||||||
|
CodeFold: {
|
||||||
|
Title: "Enable CodeFold",
|
||||||
|
SubTitle:
|
||||||
|
"Automatically collapse/expand overly long code blocks when CodeFold is enabled",
|
||||||
|
},
|
||||||
Share: {
|
Share: {
|
||||||
Title: "Share This Mask",
|
Title: "Share This Mask",
|
||||||
SubTitle: "Generate a link to this mask",
|
SubTitle: "Generate a link to this mask",
|
||||||
|
|
|
@ -372,22 +372,16 @@ export const useChatStore = createPersistStore(
|
||||||
|
|
||||||
if (attachImages && attachImages.length > 0) {
|
if (attachImages && attachImages.length > 0) {
|
||||||
mContent = [
|
mContent = [
|
||||||
{
|
...(userContent
|
||||||
type: "text",
|
? [{ type: "text" as const, text: userContent }]
|
||||||
text: userContent,
|
: []),
|
||||||
},
|
...attachImages.map((url) => ({
|
||||||
|
type: "image_url" as const,
|
||||||
|
image_url: { url },
|
||||||
|
})),
|
||||||
];
|
];
|
||||||
mContent = mContent.concat(
|
|
||||||
attachImages.map((url) => {
|
|
||||||
return {
|
|
||||||
type: "image_url",
|
|
||||||
image_url: {
|
|
||||||
url: url,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let userMessage: ChatMessage = createMessage({
|
let userMessage: ChatMessage = createMessage({
|
||||||
role: "user",
|
role: "user",
|
||||||
content: mContent,
|
content: mContent,
|
||||||
|
|
|
@ -52,6 +52,8 @@ export const DEFAULT_CONFIG = {
|
||||||
|
|
||||||
enableArtifacts: true, // show artifacts config
|
enableArtifacts: true, // show artifacts config
|
||||||
|
|
||||||
|
enableCodeFold: true, // code fold config
|
||||||
|
|
||||||
disablePromptHint: false,
|
disablePromptHint: false,
|
||||||
|
|
||||||
dontShowMaskSplashScreen: false, // dont show splash screen when create chat
|
dontShowMaskSplashScreen: false, // dont show splash screen when create chat
|
||||||
|
|
|
@ -19,6 +19,7 @@ export type Mask = {
|
||||||
builtin: boolean;
|
builtin: boolean;
|
||||||
plugin?: string[];
|
plugin?: string[];
|
||||||
enableArtifacts?: boolean;
|
enableArtifacts?: boolean;
|
||||||
|
enableCodeFold?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_MASK_STATE = {
|
export const DEFAULT_MASK_STATE = {
|
||||||
|
|
|
@ -6,13 +6,13 @@
|
||||||
"mask": "npx tsx app/masks/build.ts",
|
"mask": "npx tsx app/masks/build.ts",
|
||||||
"mask:watch": "npx watch \"yarn mask\" app/masks",
|
"mask:watch": "npx watch \"yarn mask\" app/masks",
|
||||||
"dev": "concurrently -r \"yarn run mask:watch\" \"next dev\"",
|
"dev": "concurrently -r \"yarn run mask:watch\" \"next dev\"",
|
||||||
"build": "yarn test:ci && yarn mask && cross-env BUILD_MODE=standalone next build",
|
"build": "yarn mask && cross-env BUILD_MODE=standalone next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"export": "yarn test:ci && yarn mask && cross-env BUILD_MODE=export BUILD_APP=1 next build",
|
"export": "yarn mask && cross-env BUILD_MODE=export BUILD_APP=1 next build",
|
||||||
"export:dev": "concurrently -r \"yarn mask:watch\" \"cross-env BUILD_MODE=export BUILD_APP=1 next dev\"",
|
"export:dev": "concurrently -r \"yarn mask:watch\" \"cross-env BUILD_MODE=export BUILD_APP=1 next dev\"",
|
||||||
"app:dev": "concurrently -r \"yarn mask:watch\" \"yarn tauri dev\"",
|
"app:dev": "concurrently -r \"yarn mask:watch\" \"yarn tauri dev\"",
|
||||||
"app:build": "yarn test:ci && yarn mask && yarn tauri build",
|
"app:build": "yarn mask && yarn tauri build",
|
||||||
"prompts": "node ./scripts/fetch-prompts.mjs",
|
"prompts": "node ./scripts/fetch-prompts.mjs",
|
||||||
"prepare": "husky install",
|
"prepare": "husky install",
|
||||||
"proxy-dev": "sh ./scripts/init-proxy.sh && proxychains -f ./scripts/proxychains.conf yarn dev",
|
"proxy-dev": "sh ./scripts/init-proxy.sh && proxychains -f ./scripts/proxychains.conf yarn dev",
|
||||||
|
|
Loading…
Reference in New Issue