mirror of
https://github.com/Yidadaa/ChatGPT-Next-Web.git
synced 2025-08-31 03:09:04 +08:00
Compare commits
70 Commits
v2.8.6
...
suggestion
Author | SHA1 | Date | |
---|---|---|---|
|
f75b238ebe | ||
|
b55b01cb13 | ||
|
90d8f3117f | ||
|
c7e976c8c5 | ||
|
6014b765f4 | ||
|
ca295588c4 | ||
|
3432d4df29 | ||
|
05fcb96181 | ||
|
6bb27f90a4 | ||
|
c10e8382a9 | ||
|
6653a31eb7 | ||
|
42561d04de | ||
|
9a285ab935 | ||
|
81771568be | ||
|
795b43f174 | ||
|
52203b50eb | ||
|
0373b2c9dd | ||
|
fe2c1c4ec6 | ||
|
596b6542e8 | ||
|
6897bf1254 | ||
|
8a6a13e583 | ||
|
b718285125 | ||
|
e5ab918ef9 | ||
|
6c6a2d08db | ||
|
9e6617e3ca | ||
|
94a50f92e1 | ||
|
5c8be2a8f6 | ||
|
1dcf2d80b4 | ||
|
1197521921 | ||
|
3863cfe786 | ||
|
54bd07702c | ||
|
a75e2b0c0e | ||
|
29fd9b23fe | ||
|
089e3b8946 | ||
|
9c36fcec81 | ||
|
74fa065266 | ||
|
38f2495cf6 | ||
|
4131fccbe0 | ||
|
f2d748cfe4 | ||
|
197ec0c29c | ||
|
0a2af9335c | ||
|
a52fa28ed1 | ||
|
78ed24dbf6 | ||
|
cda074fe24 | ||
|
823032617d | ||
|
5963459499 | ||
|
0bc2c71b0c | ||
|
b4e350e189 | ||
|
941e46490f | ||
|
4df92e903a | ||
|
6ba02d0d50 | ||
|
2dc122831b | ||
|
f3f84e523a | ||
|
0cdee25b5b | ||
|
92b0314c14 | ||
|
ad2bc7da96 | ||
|
d8b606dc83 | ||
|
5ce53dbcf4 | ||
|
c5c1a9ab3c | ||
|
564709aa98 | ||
|
475158a145 | ||
|
829df56733 | ||
|
ee55f8790e | ||
|
6d19fb3909 | ||
|
9057712c8f | ||
|
5d6e7de667 | ||
|
8f6f70879c | ||
|
3120087992 | ||
|
0ec4cc223f | ||
|
60c7be31b6 |
27
.github/workflows/app.yml
vendored
27
.github/workflows/app.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
create-release:
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
release_id: ${{ steps.create-release.outputs.result }}
|
||||
|
||||
@@ -39,9 +39,21 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [macos-latest, ubuntu-20.04, windows-latest]
|
||||
config:
|
||||
- os: ubuntu-latest
|
||||
arch: x86_64
|
||||
rust_target: x86_64-unknown-linux-gnu
|
||||
- os: macos-latest
|
||||
arch: x86_64
|
||||
rust_target: x86_64-apple-darwin
|
||||
- os: macos-latest
|
||||
arch: aarch64
|
||||
rust_target: aarch64-apple-darwin
|
||||
- os: windows-latest
|
||||
arch: x86_64
|
||||
rust_target: x86_64-pc-windows-msvc
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
runs-on: ${{ matrix.config.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: setup node
|
||||
@@ -50,8 +62,13 @@ jobs:
|
||||
node-version: 16
|
||||
- name: install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.config.rust_target }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: ${{ matrix.config.rust_target }}
|
||||
- name: install dependencies (ubuntu only)
|
||||
if: matrix.platform == 'ubuntu-20.04'
|
||||
if: matrix.config.os == 'ubuntu-latest'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
@@ -68,7 +85,7 @@ jobs:
|
||||
publish-release:
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
needs: [create-release, build-tauri]
|
||||
|
||||
steps:
|
||||
|
@@ -84,7 +84,7 @@ One-Click to get well-designed cross-platform ChatGPT web UI.
|
||||
- [x] 预制角色:使用预制角色快速定制新对话 [#993](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/993)
|
||||
- [x] 分享为图片,分享到 ShareGPT 链接 [#1741](https://github.com/Yidadaa/ChatGPT-Next-Web/pull/1741)
|
||||
- [x] 使用 tauri 打包桌面应用
|
||||
- [x] 支持自部署的大语言模型:开箱即用 [RWKV-Runner](https://github.com/josStorer/RWKV-Runner) ,服务端部署 [LocalAI 项目](https://github.com/go-skynet/LocalAI) llama / gpt4all / rwkv / vicuna / koala / gpt4all-j / cerebras / falcon / dolly 等等
|
||||
- [x] 支持自部署的大语言模型:开箱即用 [RWKV-Runner](https://github.com/josStorer/RWKV-Runner) ,服务端部署 [LocalAI 项目](https://github.com/go-skynet/LocalAI) llama / gpt4all / rwkv / vicuna / koala / gpt4all-j / cerebras / falcon / dolly 等等,或者使用 [api-for-open-llm](https://github.com/xusenlinzy/api-for-open-llm)
|
||||
- [ ] 插件机制,支持联网搜索、计算器、调用其他平台 api [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165)
|
||||
|
||||
## 最新动态
|
||||
@@ -263,6 +263,10 @@ bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/s
|
||||
|
||||

|
||||
|
||||
## Translation
|
||||
|
||||
If you want to add a new translation, read this [document](./docs/translation.md).
|
||||
|
||||
## Donation
|
||||
|
||||
[Buy Me a Coffee](https://www.buymeacoffee.com/yidadaa)
|
||||
@@ -294,6 +298,7 @@ bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/s
|
||||
[@Sha1rholder](https://github.com/Sha1rholder)
|
||||
[@AnsonHyq](https://github.com/AnsonHyq)
|
||||
[@synwith](https://github.com/synwith)
|
||||
[@piksonGit](https://github.com/piksonGit)
|
||||
|
||||
### Contributor
|
||||
|
||||
|
@@ -9,7 +9,7 @@ const serverConfig = getServerSideConfig();
|
||||
const DANGER_CONFIG = {
|
||||
needCode: serverConfig.needCode,
|
||||
hideUserApiKey: serverConfig.hideUserApiKey,
|
||||
enableGPT4: serverConfig.enableGPT4,
|
||||
disableGPT4: serverConfig.disableGPT4,
|
||||
hideBalanceQuery: serverConfig.hideBalanceQuery,
|
||||
};
|
||||
|
||||
|
@@ -1,3 +1,5 @@
|
||||
import { type OpenAIListModelResponse } from "@/app/client/platforms/openai";
|
||||
import { getServerSideConfig } from "@/app/config/server";
|
||||
import { OpenaiPath } from "@/app/constant";
|
||||
import { prettyObject } from "@/app/utils/format";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
@@ -6,6 +8,18 @@ import { requestOpenai } from "../../common";
|
||||
|
||||
const ALLOWD_PATH = new Set(Object.values(OpenaiPath));
|
||||
|
||||
function getModels(remoteModelRes: OpenAIListModelResponse) {
|
||||
const config = getServerSideConfig();
|
||||
|
||||
if (config.disableGPT4) {
|
||||
remoteModelRes.data = remoteModelRes.data.filter(
|
||||
(m) => !m.id.startsWith("gpt-4"),
|
||||
);
|
||||
}
|
||||
|
||||
return remoteModelRes;
|
||||
}
|
||||
|
||||
async function handle(
|
||||
req: NextRequest,
|
||||
{ params }: { params: { path: string[] } },
|
||||
@@ -39,7 +53,18 @@ async function handle(
|
||||
}
|
||||
|
||||
try {
|
||||
return await requestOpenai(req);
|
||||
const response = await requestOpenai(req);
|
||||
|
||||
// list models
|
||||
if (subpath === OpenaiPath.ListModelPath && response.status === 200) {
|
||||
const resJson = (await response.json()) as OpenAIListModelResponse;
|
||||
const availableModels = getModels(resJson);
|
||||
return NextResponse.json(availableModels, {
|
||||
status: response.status,
|
||||
});
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (e) {
|
||||
console.error("[OpenAI] ", e);
|
||||
return NextResponse.json(prettyObject(e));
|
||||
|
@@ -38,9 +38,15 @@ export interface LLMUsage {
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface LLMModel {
|
||||
name: string;
|
||||
available: boolean;
|
||||
}
|
||||
|
||||
export abstract class LLMApi {
|
||||
abstract chat(options: ChatOptions): Promise<void>;
|
||||
abstract usage(): Promise<LLMUsage>;
|
||||
abstract models(): Promise<LLMModel[]>;
|
||||
}
|
||||
|
||||
type ProviderName = "openai" | "azure" | "claude" | "palm";
|
||||
|
@@ -5,7 +5,7 @@ import {
|
||||
} from "@/app/constant";
|
||||
import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
|
||||
|
||||
import { ChatOptions, getHeaders, LLMApi, LLMUsage } from "../api";
|
||||
import { ChatOptions, getHeaders, LLMApi, LLMModel, LLMUsage } from "../api";
|
||||
import Locale from "../../locales";
|
||||
import {
|
||||
EventStreamContentType,
|
||||
@@ -13,6 +13,15 @@ import {
|
||||
} from "@fortaine/fetch-event-source";
|
||||
import { prettyObject } from "@/app/utils/format";
|
||||
|
||||
export interface OpenAIListModelResponse {
|
||||
object: string;
|
||||
data: Array<{
|
||||
id: string;
|
||||
object: string;
|
||||
root: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export class ChatGPTApi implements LLMApi {
|
||||
path(path: string): string {
|
||||
let openaiUrl = useAccessStore.getState().openaiUrl;
|
||||
@@ -22,6 +31,9 @@ export class ChatGPTApi implements LLMApi {
|
||||
if (openaiUrl.endsWith("/")) {
|
||||
openaiUrl = openaiUrl.slice(0, openaiUrl.length - 1);
|
||||
}
|
||||
if (!openaiUrl.startsWith("http") && !openaiUrl.startsWith("/api/openai")) {
|
||||
openaiUrl = "https://" + openaiUrl;
|
||||
}
|
||||
return [openaiUrl, path].join("/");
|
||||
}
|
||||
|
||||
@@ -50,6 +62,7 @@ export class ChatGPTApi implements LLMApi {
|
||||
temperature: modelConfig.temperature,
|
||||
presence_penalty: modelConfig.presence_penalty,
|
||||
frequency_penalty: modelConfig.frequency_penalty,
|
||||
top_p: modelConfig.top_p,
|
||||
};
|
||||
|
||||
console.log("[Request] openai payload: ", requestPayload);
|
||||
@@ -231,5 +244,25 @@ export class ChatGPTApi implements LLMApi {
|
||||
total: total.hard_limit_usd,
|
||||
} as LLMUsage;
|
||||
}
|
||||
|
||||
async models(): Promise<LLMModel[]> {
|
||||
const res = await fetch(this.path(OpenaiPath.ListModelPath), {
|
||||
method: "GET",
|
||||
headers: {
|
||||
...getHeaders(),
|
||||
},
|
||||
});
|
||||
|
||||
const resJson = (await res.json()) as OpenAIListModelResponse;
|
||||
const chatModels = resJson.data?.filter((m) => m.id.startsWith("gpt-"));
|
||||
console.log("[Models]", chatModels);
|
||||
|
||||
return (
|
||||
chatModels?.map((m) => ({
|
||||
name: m.id,
|
||||
available: true,
|
||||
})) || []
|
||||
);
|
||||
}
|
||||
}
|
||||
export { OpenaiPath };
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { useEffect } from "react";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import Locale from "./locales";
|
||||
|
||||
@@ -11,21 +12,22 @@ interface Commands {
|
||||
export function useCommand(commands: Commands = {}) {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
if (commands === undefined) return;
|
||||
useEffect(() => {
|
||||
let shouldUpdate = false;
|
||||
searchParams.forEach((param, name) => {
|
||||
const commandName = name as keyof Commands;
|
||||
if (typeof commands[commandName] === "function") {
|
||||
commands[commandName]!(param);
|
||||
searchParams.delete(name);
|
||||
shouldUpdate = true;
|
||||
}
|
||||
});
|
||||
|
||||
let shouldUpdate = false;
|
||||
searchParams.forEach((param, name) => {
|
||||
const commandName = name as keyof Commands;
|
||||
if (typeof commands[commandName] === "function") {
|
||||
commands[commandName]!(param);
|
||||
searchParams.delete(name);
|
||||
shouldUpdate = true;
|
||||
if (shouldUpdate) {
|
||||
setSearchParams(searchParams);
|
||||
}
|
||||
});
|
||||
|
||||
if (shouldUpdate) {
|
||||
setSearchParams(searchParams);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [searchParams, commands]);
|
||||
}
|
||||
|
||||
interface ChatCommands {
|
||||
|
@@ -212,6 +212,7 @@
|
||||
.chat-body {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 20px;
|
||||
padding-bottom: 40px;
|
||||
position: relative;
|
||||
@@ -239,24 +240,39 @@
|
||||
&:last-child {
|
||||
animation: slide-in ease 0.3s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.chat-message-actions {
|
||||
opacity: 1;
|
||||
transform: translateY(0px);
|
||||
max-width: 100%;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.chat-message-action-date {
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chat-message-user {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
|
||||
.chat-message-header {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-message-header {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.chat-message-actions {
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
font-size: 12px;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
transition: all ease 0.3s;
|
||||
transform: scale(0.9) translateY(5px);
|
||||
margin: 0 10px;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
|
||||
.chat-input-actions {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chat-message-container {
|
||||
@@ -269,6 +285,12 @@
|
||||
.chat-message-edit {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.chat-message-actions {
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,7 +299,6 @@
|
||||
}
|
||||
|
||||
.chat-message-avatar {
|
||||
margin-top: 20px;
|
||||
position: relative;
|
||||
|
||||
.chat-message-edit {
|
||||
@@ -317,27 +338,6 @@
|
||||
border: var(--border-in-light);
|
||||
position: relative;
|
||||
transition: all ease 0.3s;
|
||||
|
||||
.chat-message-actions {
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
font-size: 12px;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
transition: all ease 0.3s 0.15s;
|
||||
transform: translateX(-5px) scale(0.9) translateY(30px);
|
||||
opacity: 0;
|
||||
height: 0;
|
||||
max-width: 0;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
|
||||
.chat-input-actions {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chat-message-action-date {
|
||||
@@ -475,3 +475,21 @@
|
||||
bottom: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-suggestions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.chat-suggestion {
|
||||
display: inline;
|
||||
white-space: nowrap;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
background-color: var(--white);
|
||||
color: var(--black);
|
||||
border: var(--border-in-light);
|
||||
padding: 4px 10px;
|
||||
animation: slide-in ease 0.3s;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
@@ -42,12 +42,11 @@ import {
|
||||
Theme,
|
||||
useAppConfig,
|
||||
DEFAULT_TOPIC,
|
||||
ALL_MODELS,
|
||||
ModelType,
|
||||
} from "../store";
|
||||
|
||||
import {
|
||||
copyToClipboard,
|
||||
downloadAs,
|
||||
selectOrCopy,
|
||||
autoGrowTextArea,
|
||||
useMobileScreen,
|
||||
@@ -62,7 +61,14 @@ import Locale from "../locales";
|
||||
import { IconButton } from "./button";
|
||||
import styles from "./chat.module.scss";
|
||||
|
||||
import { ListItem, Modal, showConfirm, showPrompt, showToast } from "./ui-lib";
|
||||
import {
|
||||
ListItem,
|
||||
Modal,
|
||||
Selector,
|
||||
showConfirm,
|
||||
showPrompt,
|
||||
showToast,
|
||||
} from "./ui-lib";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { LAST_INPUT_KEY, Path, REQUEST_TIMEOUT_MS } from "../constant";
|
||||
import { Avatar } from "./emoji";
|
||||
@@ -173,10 +179,29 @@ function PromptToast(props: {
|
||||
function useSubmitHandler() {
|
||||
const config = useAppConfig();
|
||||
const submitKey = config.submitKey;
|
||||
const isComposing = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
const onCompositionStart = () => {
|
||||
isComposing.current = true;
|
||||
};
|
||||
const onCompositionEnd = () => {
|
||||
isComposing.current = false;
|
||||
};
|
||||
|
||||
window.addEventListener("compositionstart", onCompositionStart);
|
||||
window.addEventListener("compositionend", onCompositionEnd);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("compositionstart", onCompositionStart);
|
||||
window.removeEventListener("compositionend", onCompositionEnd);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const shouldSubmit = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (e.key !== "Enter") return false;
|
||||
if (e.key === "Enter" && e.nativeEvent.isComposing) return false;
|
||||
if (e.key === "Enter" && (e.nativeEvent.isComposing || isComposing.current))
|
||||
return false;
|
||||
return (
|
||||
(config.submitKey === SubmitKey.AltEnter && e.altKey) ||
|
||||
(config.submitKey === SubmitKey.CtrlEnter && e.ctrlKey) ||
|
||||
@@ -386,16 +411,11 @@ export function ChatActions(props: {
|
||||
|
||||
// switch model
|
||||
const currentModel = chatStore.currentSession().mask.modelConfig.model;
|
||||
function nextModel() {
|
||||
const models = ALL_MODELS.filter((m) => m.available).map((m) => m.name);
|
||||
const modelIndex = models.indexOf(currentModel);
|
||||
const nextIndex = (modelIndex + 1) % models.length;
|
||||
const nextModel = models[nextIndex];
|
||||
chatStore.updateCurrentSession((session) => {
|
||||
session.mask.modelConfig.model = nextModel;
|
||||
session.mask.syncGlobalConfig = false;
|
||||
});
|
||||
}
|
||||
const models = useMemo(
|
||||
() => config.models.filter((m) => m.available).map((m) => m.name),
|
||||
[config.models],
|
||||
);
|
||||
const [showModelSelector, setShowModelSelector] = useState(false);
|
||||
|
||||
return (
|
||||
<div className={styles["chat-input-actions"]}>
|
||||
@@ -467,10 +487,28 @@ export function ChatActions(props: {
|
||||
/>
|
||||
|
||||
<ChatAction
|
||||
onClick={nextModel}
|
||||
onClick={() => setShowModelSelector(true)}
|
||||
text={currentModel}
|
||||
icon={<RobotIcon />}
|
||||
/>
|
||||
|
||||
{showModelSelector && (
|
||||
<Selector
|
||||
items={models.map((m) => ({
|
||||
title: m,
|
||||
value: m,
|
||||
}))}
|
||||
onClose={() => setShowModelSelector(false)}
|
||||
onSelection={(s) => {
|
||||
if (s.length === 0) return;
|
||||
chatStore.updateCurrentSession((session) => {
|
||||
session.mask.modelConfig.model = s[0] as ModelType;
|
||||
session.mask.syncGlobalConfig = false;
|
||||
});
|
||||
showToast(s[0]);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -568,6 +606,8 @@ export function Chat() {
|
||||
}
|
||||
};
|
||||
|
||||
const [suggestions, setSuggestions] = useState<string[]>([]);
|
||||
|
||||
const doSubmit = (userInput: string) => {
|
||||
if (userInput.trim() === "") return;
|
||||
const matchCommand = chatCommands.match(userInput);
|
||||
@@ -578,7 +618,13 @@ export function Chat() {
|
||||
return;
|
||||
}
|
||||
setIsLoading(true);
|
||||
chatStore.onUserInput(userInput).then(() => setIsLoading(false));
|
||||
setSuggestions([]);
|
||||
chatStore.onUserInput(userInput).then(() => {
|
||||
setIsLoading(false);
|
||||
chatStore
|
||||
.getSuggestions()
|
||||
.then((suggestions) => setSuggestions(suggestions));
|
||||
});
|
||||
localStorage.setItem(LAST_INPUT_KEY, userInput);
|
||||
setUserInput("");
|
||||
setPromptHints([]);
|
||||
@@ -670,49 +716,46 @@ export function Chat() {
|
||||
let lastUserMessageIndex: number | null = null;
|
||||
for (let i = 0; i < session.messages.length; i += 1) {
|
||||
const message = session.messages[i];
|
||||
if (message.id === messageId) {
|
||||
break;
|
||||
}
|
||||
if (message.role === "user") {
|
||||
lastUserMessageIndex = i;
|
||||
}
|
||||
if (message.id === messageId) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return lastUserMessageIndex;
|
||||
};
|
||||
|
||||
const deleteMessage = (userIndex: number) => {
|
||||
chatStore.updateCurrentSession((session) =>
|
||||
session.messages.splice(userIndex, 2),
|
||||
const deleteMessage = (msgId?: number) => {
|
||||
chatStore.updateCurrentSession(
|
||||
(session) =>
|
||||
(session.messages = session.messages.filter((m) => m.id !== msgId)),
|
||||
);
|
||||
};
|
||||
|
||||
const onDelete = (botMessageId: number) => {
|
||||
const userIndex = findLastUserIndex(botMessageId);
|
||||
if (userIndex === null) return;
|
||||
deleteMessage(userIndex);
|
||||
const onDelete = (msgId: number) => {
|
||||
deleteMessage(msgId);
|
||||
};
|
||||
|
||||
const onResend = (botMessageId: number) => {
|
||||
// find last user input message and resend
|
||||
const userIndex = findLastUserIndex(botMessageId);
|
||||
if (userIndex === null) return;
|
||||
const onResend = (message: ChatMessage) => {
|
||||
let content = message.content;
|
||||
|
||||
if (message.role === "assistant" && message.id) {
|
||||
const userIndex = findLastUserIndex(message.id);
|
||||
if (userIndex) {
|
||||
content = session.messages.at(userIndex)?.content ?? content;
|
||||
}
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
const content = session.messages[userIndex].content;
|
||||
deleteMessage(userIndex);
|
||||
chatStore.onUserInput(content).then(() => setIsLoading(false));
|
||||
inputRef.current?.focus();
|
||||
};
|
||||
|
||||
const onPinMessage = (botMessage: ChatMessage) => {
|
||||
if (!botMessage.id) return;
|
||||
const userMessageIndex = findLastUserIndex(botMessage.id);
|
||||
if (userMessageIndex === null) return;
|
||||
|
||||
const userMessage = session.messages[userMessageIndex];
|
||||
const onPinMessage = (message: ChatMessage) => {
|
||||
chatStore.updateCurrentSession((session) =>
|
||||
session.mask.context.push(userMessage, botMessage),
|
||||
session.mask.context.push(message),
|
||||
);
|
||||
|
||||
showToast(Locale.Chat.Actions.PinToastContent, {
|
||||
@@ -885,10 +928,11 @@ export function Chat() {
|
||||
>
|
||||
{messages.map((message, i) => {
|
||||
const isUser = message.role === "user";
|
||||
const isContext = i < context.length;
|
||||
const showActions =
|
||||
!isUser &&
|
||||
i > 0 &&
|
||||
!(message.preview || message.content.length === 0);
|
||||
!(message.preview || message.content.length === 0) &&
|
||||
!isContext;
|
||||
const showTyping = message.preview || message.streaming;
|
||||
|
||||
const shouldShowClearContextDivider = i === clearContextIndex - 1;
|
||||
@@ -902,63 +946,38 @@ export function Chat() {
|
||||
}
|
||||
>
|
||||
<div className={styles["chat-message-container"]}>
|
||||
<div className={styles["chat-message-avatar"]}>
|
||||
<div className={styles["chat-message-edit"]}>
|
||||
<IconButton
|
||||
icon={<EditIcon />}
|
||||
onClick={async () => {
|
||||
const newMessage = await showPrompt(
|
||||
Locale.Chat.Actions.Edit,
|
||||
message.content,
|
||||
);
|
||||
chatStore.updateCurrentSession((session) => {
|
||||
const m = session.messages.find(
|
||||
(m) => m.id === message.id,
|
||||
<div className={styles["chat-message-header"]}>
|
||||
<div className={styles["chat-message-avatar"]}>
|
||||
<div className={styles["chat-message-edit"]}>
|
||||
<IconButton
|
||||
icon={<EditIcon />}
|
||||
onClick={async () => {
|
||||
const newMessage = await showPrompt(
|
||||
Locale.Chat.Actions.Edit,
|
||||
message.content,
|
||||
10,
|
||||
);
|
||||
if (m) {
|
||||
m.content = newMessage;
|
||||
}
|
||||
});
|
||||
}}
|
||||
></IconButton>
|
||||
chatStore.updateCurrentSession((session) => {
|
||||
const m = session.messages.find(
|
||||
(m) => m.id === message.id,
|
||||
);
|
||||
if (m) {
|
||||
m.content = newMessage;
|
||||
}
|
||||
});
|
||||
}}
|
||||
></IconButton>
|
||||
</div>
|
||||
{isUser ? (
|
||||
<Avatar avatar={config.avatar} />
|
||||
) : (
|
||||
<MaskAvatar mask={session.mask} />
|
||||
)}
|
||||
</div>
|
||||
{message.role === "user" ? (
|
||||
<Avatar avatar={config.avatar} />
|
||||
) : (
|
||||
<MaskAvatar mask={session.mask} />
|
||||
)}
|
||||
</div>
|
||||
{showTyping && (
|
||||
<div className={styles["chat-message-status"]}>
|
||||
{Locale.Chat.Typing}
|
||||
</div>
|
||||
)}
|
||||
<div className={styles["chat-message-item"]}>
|
||||
<Markdown
|
||||
content={message.content}
|
||||
loading={
|
||||
(message.preview || message.content.length === 0) &&
|
||||
!isUser
|
||||
}
|
||||
onContextMenu={(e) => onRightClick(e, message)}
|
||||
onDoubleClickCapture={() => {
|
||||
if (!isMobileScreen) return;
|
||||
setUserInput(message.content);
|
||||
}}
|
||||
fontSize={fontSize}
|
||||
parentRef={scrollRef}
|
||||
defaultShow={i >= messages.length - 10}
|
||||
/>
|
||||
|
||||
{showActions && (
|
||||
<div className={styles["chat-message-actions"]}>
|
||||
<div
|
||||
className={styles["chat-input-actions"]}
|
||||
style={{
|
||||
marginTop: 10,
|
||||
marginBottom: 0,
|
||||
}}
|
||||
>
|
||||
<div className={styles["chat-input-actions"]}>
|
||||
{message.streaming ? (
|
||||
<ChatAction
|
||||
text={Locale.Chat.Actions.Stop}
|
||||
@@ -970,7 +989,7 @@ export function Chat() {
|
||||
<ChatAction
|
||||
text={Locale.Chat.Actions.Retry}
|
||||
icon={<ResetIcon />}
|
||||
onClick={() => onResend(message.id ?? i)}
|
||||
onClick={() => onResend(message)}
|
||||
/>
|
||||
|
||||
<ChatAction
|
||||
@@ -995,12 +1014,34 @@ export function Chat() {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{showActions && (
|
||||
<div className={styles["chat-message-action-date"]}>
|
||||
{message.date.toLocaleString()}
|
||||
{showTyping && (
|
||||
<div className={styles["chat-message-status"]}>
|
||||
{Locale.Chat.Typing}
|
||||
</div>
|
||||
)}
|
||||
<div className={styles["chat-message-item"]}>
|
||||
<Markdown
|
||||
content={message.content}
|
||||
loading={
|
||||
(message.preview || message.content.length === 0) &&
|
||||
!isUser
|
||||
}
|
||||
onContextMenu={(e) => onRightClick(e, message)}
|
||||
onDoubleClickCapture={() => {
|
||||
if (!isMobileScreen) return;
|
||||
setUserInput(message.content);
|
||||
}}
|
||||
fontSize={fontSize}
|
||||
parentRef={scrollRef}
|
||||
defaultShow={i >= messages.length - 10}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles["chat-message-action-date"]}>
|
||||
{isContext
|
||||
? Locale.Chat.IsContext
|
||||
: message.date.toLocaleString()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{shouldShowClearContextDivider && <ClearContextDivider />}
|
||||
@@ -1028,6 +1069,25 @@ export function Chat() {
|
||||
onSearch("");
|
||||
}}
|
||||
/>
|
||||
|
||||
{suggestions.length > 0 && (
|
||||
<div className={styles["chat-suggestions"]}>
|
||||
{suggestions.map((s, i) => {
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
className={styles["chat-suggestion"] + " clickable"}
|
||||
onClick={() => {
|
||||
doSubmit(s);
|
||||
}}
|
||||
>
|
||||
{s}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={styles["chat-input-panel-inner"]}>
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
|
@@ -186,7 +186,7 @@
|
||||
box-shadow: var(--card-shadow);
|
||||
border: var(--border-in-light);
|
||||
|
||||
* {
|
||||
*:not(li) {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,16 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import { ChatMessage, useAppConfig, useChatStore } from "../store";
|
||||
import Locale from "../locales";
|
||||
import styles from "./exporter.module.scss";
|
||||
import { List, ListItem, Modal, Select, showToast } from "./ui-lib";
|
||||
import {
|
||||
List,
|
||||
ListItem,
|
||||
Modal,
|
||||
Select,
|
||||
showImageModal,
|
||||
showModal,
|
||||
showToast,
|
||||
} from "./ui-lib";
|
||||
import { IconButton } from "./button";
|
||||
import { copyToClipboard, downloadAs, useMobileScreen } from "../utils";
|
||||
|
||||
@@ -23,6 +32,7 @@ import { DEFAULT_MASK_AVATAR } from "../store/mask";
|
||||
import { api } from "../client/api";
|
||||
import { prettyObject } from "../utils/format";
|
||||
import { EXPORT_MESSAGE_CLASS_NAME } from "../constant";
|
||||
import { getClientConfig } from "../config/client";
|
||||
|
||||
const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
|
||||
loading: () => <LoadingIcon />,
|
||||
@@ -369,6 +379,7 @@ export function ImagePreviewer(props: {
|
||||
const previewRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const copy = () => {
|
||||
showToast(Locale.Export.Image.Toast);
|
||||
const dom = previewRef.current;
|
||||
if (!dom) return;
|
||||
toBlob(dom).then((blob) => {
|
||||
@@ -393,17 +404,15 @@ export function ImagePreviewer(props: {
|
||||
const isMobile = useMobileScreen();
|
||||
|
||||
const download = () => {
|
||||
showToast(Locale.Export.Image.Toast);
|
||||
const dom = previewRef.current;
|
||||
if (!dom) return;
|
||||
toPng(dom)
|
||||
.then((blob) => {
|
||||
if (!blob) return;
|
||||
|
||||
if (isMobile) {
|
||||
const image = new Image();
|
||||
image.src = blob;
|
||||
const win = window.open("");
|
||||
win?.document.write(image.outerHTML);
|
||||
if (isMobile || getClientConfig()?.isApp) {
|
||||
showImageModal(blob);
|
||||
} else {
|
||||
const link = document.createElement("a");
|
||||
link.download = `${props.topic}.png`;
|
||||
|
@@ -27,6 +27,8 @@ import { SideBar } from "./sidebar";
|
||||
import { useAppConfig } from "../store/config";
|
||||
import { AuthPage } from "./auth";
|
||||
import { getClientConfig } from "../config/client";
|
||||
import { api } from "../client/api";
|
||||
import { useChatStore } from "../store";
|
||||
|
||||
export function Loading(props: { noLogo?: boolean }) {
|
||||
return (
|
||||
@@ -113,6 +115,7 @@ function Screen() {
|
||||
const isHome = location.pathname === Path.Home;
|
||||
const isAuth = location.pathname === Path.Auth;
|
||||
const isMobileScreen = useMobileScreen();
|
||||
const chat = useChatStore();
|
||||
|
||||
useEffect(() => {
|
||||
loadAsyncGoogleFont();
|
||||
@@ -152,8 +155,21 @@ function Screen() {
|
||||
);
|
||||
}
|
||||
|
||||
export function useLoadData() {
|
||||
const config = useAppConfig();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const models = await api.llm.models();
|
||||
config.mergeModels(models);
|
||||
})();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
}
|
||||
|
||||
export function Home() {
|
||||
useSwitchTheme();
|
||||
useLoadData();
|
||||
|
||||
useEffect(() => {
|
||||
console.log("[Config] got config from build time", getClientConfig());
|
||||
|
@@ -12,6 +12,7 @@ import mermaid from "mermaid";
|
||||
import LoadingIcon from "../icons/three-dots.svg";
|
||||
import React from "react";
|
||||
import { useDebouncedCallback, useThrottledCallback } from "use-debounce";
|
||||
import { showImageModal } from "./ui-lib";
|
||||
|
||||
export function Mermaid(props: { code: string }) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
@@ -37,11 +38,13 @@ export function Mermaid(props: { code: string }) {
|
||||
if (!svg) return;
|
||||
const text = new XMLSerializer().serializeToString(svg);
|
||||
const blob = new Blob([text], { type: "image/svg+xml" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const win = window.open(url);
|
||||
if (win) {
|
||||
win.onload = () => URL.revokeObjectURL(url);
|
||||
}
|
||||
console.log(blob);
|
||||
// const url = URL.createObjectURL(blob);
|
||||
// const win = window.open(url);
|
||||
// if (win) {
|
||||
// win.onload = () => URL.revokeObjectURL(url);
|
||||
// }
|
||||
showImageModal(URL.createObjectURL(blob));
|
||||
}
|
||||
|
||||
if (hasError) {
|
||||
|
@@ -30,7 +30,7 @@ import { useNavigate } from "react-router-dom";
|
||||
|
||||
import chatStyle from "./chat.module.scss";
|
||||
import { useEffect, useState } from "react";
|
||||
import { downloadAs, readFromFile } from "../utils";
|
||||
import { copyToClipboard, downloadAs, readFromFile } from "../utils";
|
||||
import { Updater } from "../typing";
|
||||
import { ModelConfigList } from "./model-config";
|
||||
import { FileName, Path } from "../constant";
|
||||
@@ -65,6 +65,11 @@ export function MaskConfig(props: {
|
||||
});
|
||||
};
|
||||
|
||||
const copyMaskLink = () => {
|
||||
const maskLink = `${location.protocol}//${location.host}/#${Path.NewChat}?mask=${props.mask.id}`;
|
||||
copyToClipboard(maskLink);
|
||||
};
|
||||
|
||||
const globalConfig = useAppConfig();
|
||||
|
||||
return (
|
||||
@@ -125,6 +130,20 @@ export function MaskConfig(props: {
|
||||
}}
|
||||
></input>
|
||||
</ListItem>
|
||||
|
||||
{!props.shouldSyncFromGlobal ? (
|
||||
<ListItem
|
||||
title={Locale.Mask.Config.Share.Title}
|
||||
subTitle={Locale.Mask.Config.Share.SubTitle}
|
||||
>
|
||||
<IconButton
|
||||
icon={<CopyIcon />}
|
||||
text={Locale.Mask.Config.Share.Action}
|
||||
onClick={copyMaskLink}
|
||||
/>
|
||||
</ListItem>
|
||||
) : null}
|
||||
|
||||
{props.shouldSyncFromGlobal ? (
|
||||
<ListItem
|
||||
title={Locale.Mask.Config.Sync.Title}
|
||||
@@ -134,14 +153,19 @@ export function MaskConfig(props: {
|
||||
type="checkbox"
|
||||
checked={props.mask.syncGlobalConfig}
|
||||
onChange={async (e) => {
|
||||
const checked = e.currentTarget.checked;
|
||||
if (
|
||||
e.currentTarget.checked &&
|
||||
checked &&
|
||||
(await showConfirm(Locale.Mask.Config.Sync.Confirm))
|
||||
) {
|
||||
props.updateMask((mask) => {
|
||||
mask.syncGlobalConfig = e.currentTarget.checked;
|
||||
mask.syncGlobalConfig = checked;
|
||||
mask.modelConfig = { ...globalConfig.modelConfig };
|
||||
});
|
||||
} else if (!checked) {
|
||||
props.updateMask((mask) => {
|
||||
mask.syncGlobalConfig = checked;
|
||||
});
|
||||
}
|
||||
}}
|
||||
></input>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { ALL_MODELS, ModalConfigValidator, ModelConfig } from "../store";
|
||||
import { ModalConfigValidator, ModelConfig, useAppConfig } from "../store";
|
||||
|
||||
import Locale from "../locales";
|
||||
import { InputRange } from "./input-range";
|
||||
@@ -8,6 +8,8 @@ export function ModelConfigList(props: {
|
||||
modelConfig: ModelConfig;
|
||||
updateConfig: (updater: (config: ModelConfig) => void) => void;
|
||||
}) {
|
||||
const config = useAppConfig();
|
||||
|
||||
return (
|
||||
<>
|
||||
<ListItem title={Locale.Settings.Model}>
|
||||
@@ -22,7 +24,7 @@ export function ModelConfigList(props: {
|
||||
);
|
||||
}}
|
||||
>
|
||||
{ALL_MODELS.map((v) => (
|
||||
{config.models.map((v) => (
|
||||
<option value={v.name} key={v.name} disabled={!v.available}>
|
||||
{v.name}
|
||||
</option>
|
||||
@@ -48,6 +50,25 @@ export function ModelConfigList(props: {
|
||||
}}
|
||||
></InputRange>
|
||||
</ListItem>
|
||||
<ListItem
|
||||
title={Locale.Settings.TopP.Title}
|
||||
subTitle={Locale.Settings.TopP.SubTitle}
|
||||
>
|
||||
<InputRange
|
||||
value={(props.modelConfig.top_p ?? 1).toFixed(1)}
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.1"
|
||||
onChange={(e) => {
|
||||
props.updateConfig(
|
||||
(config) =>
|
||||
(config.top_p = ModalConfigValidator.top_p(
|
||||
e.currentTarget.valueAsNumber,
|
||||
)),
|
||||
);
|
||||
}}
|
||||
></InputRange>
|
||||
</ListItem>
|
||||
<ListItem
|
||||
title={Locale.Settings.MaxTokens.Title}
|
||||
subTitle={Locale.Settings.MaxTokens.SubTitle}
|
||||
|
@@ -15,6 +15,7 @@ import { useAppConfig, useChatStore } from "../store";
|
||||
import { MaskAvatar } from "./mask";
|
||||
import { useCommand } from "../command";
|
||||
import { showConfirm } from "./ui-lib";
|
||||
import { BUILTIN_MASK_STORE } from "../masks";
|
||||
|
||||
function getIntersectionArea(aRect: DOMRect, bRect: DOMRect) {
|
||||
const xmin = Math.max(aRect.x, bRect.x);
|
||||
@@ -93,14 +94,17 @@ export function NewChat() {
|
||||
const { state } = useLocation();
|
||||
|
||||
const startChat = (mask?: Mask) => {
|
||||
chatStore.newSession(mask);
|
||||
setTimeout(() => navigate(Path.Chat), 1);
|
||||
setTimeout(() => {
|
||||
chatStore.newSession(mask);
|
||||
navigate(Path.Chat);
|
||||
}, 10);
|
||||
};
|
||||
|
||||
useCommand({
|
||||
mask: (id) => {
|
||||
try {
|
||||
const mask = maskStore.get(parseInt(id));
|
||||
const intId = parseInt(id);
|
||||
const mask = maskStore.get(intId) ?? BUILTIN_MASK_STORE.get(intId);
|
||||
startChat(mask ?? undefined);
|
||||
} catch {
|
||||
console.error("[New Chat] failed to create chat from mask id=", id);
|
||||
|
@@ -340,6 +340,10 @@ export function Settings() {
|
||||
};
|
||||
const [loadingUsage, setLoadingUsage] = useState(false);
|
||||
function checkUsage(force = false) {
|
||||
if (accessStore.hideBalanceQuery) {
|
||||
return;
|
||||
}
|
||||
|
||||
setLoadingUsage(true);
|
||||
updateStore.updateUsage(force).finally(() => {
|
||||
setLoadingUsage(false);
|
||||
@@ -538,10 +542,12 @@ export function Settings() {
|
||||
}
|
||||
></input>
|
||||
</ListItem>
|
||||
</List>
|
||||
|
||||
<List>
|
||||
<ListItem
|
||||
title={Locale.Settings.Mask.Title}
|
||||
subTitle={Locale.Settings.Mask.SubTitle}
|
||||
title={Locale.Settings.Mask.Splash.Title}
|
||||
subTitle={Locale.Settings.Mask.Splash.SubTitle}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -555,6 +561,22 @@ export function Settings() {
|
||||
}
|
||||
></input>
|
||||
</ListItem>
|
||||
|
||||
<ListItem
|
||||
title={Locale.Settings.Mask.Builtin.Title}
|
||||
subTitle={Locale.Settings.Mask.Builtin.SubTitle}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={config.hideBuiltinMasks}
|
||||
onChange={(e) =>
|
||||
updateConfig(
|
||||
(config) =>
|
||||
(config.hideBuiltinMasks = e.currentTarget.checked),
|
||||
)
|
||||
}
|
||||
></input>
|
||||
</ListItem>
|
||||
</List>
|
||||
|
||||
<List>
|
||||
@@ -577,19 +599,34 @@ export function Settings() {
|
||||
)}
|
||||
|
||||
{!accessStore.hideUserApiKey ? (
|
||||
<ListItem
|
||||
title={Locale.Settings.Token.Title}
|
||||
subTitle={Locale.Settings.Token.SubTitle}
|
||||
>
|
||||
<PasswordInput
|
||||
value={accessStore.token}
|
||||
type="text"
|
||||
placeholder={Locale.Settings.Token.Placeholder}
|
||||
onChange={(e) => {
|
||||
accessStore.updateToken(e.currentTarget.value);
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
<>
|
||||
<ListItem
|
||||
title={Locale.Settings.Endpoint.Title}
|
||||
subTitle={Locale.Settings.Endpoint.SubTitle}
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value={accessStore.openaiUrl}
|
||||
placeholder="https://api.openai.com/"
|
||||
onChange={(e) =>
|
||||
accessStore.updateOpenAiUrl(e.currentTarget.value)
|
||||
}
|
||||
></input>
|
||||
</ListItem>
|
||||
<ListItem
|
||||
title={Locale.Settings.Token.Title}
|
||||
subTitle={Locale.Settings.Token.SubTitle}
|
||||
>
|
||||
<PasswordInput
|
||||
value={accessStore.token}
|
||||
type="text"
|
||||
placeholder={Locale.Settings.Token.Placeholder}
|
||||
onChange={(e) => {
|
||||
accessStore.updateToken(e.currentTarget.value);
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{!accessStore.hideBalanceQuery ? (
|
||||
@@ -617,22 +654,6 @@ export function Settings() {
|
||||
)}
|
||||
</ListItem>
|
||||
) : null}
|
||||
|
||||
{!accessStore.hideUserApiKey ? (
|
||||
<ListItem
|
||||
title={Locale.Settings.Endpoint.Title}
|
||||
subTitle={Locale.Settings.Endpoint.SubTitle}
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value={accessStore.openaiUrl}
|
||||
placeholder="https://api.openai.com/"
|
||||
onChange={(e) =>
|
||||
accessStore.updateOpenAiUrl(e.currentTarget.value)
|
||||
}
|
||||
></input>
|
||||
</ListItem>
|
||||
) : null}
|
||||
</List>
|
||||
|
||||
<List>
|
||||
|
@@ -62,6 +62,7 @@
|
||||
box-shadow: var(--card-shadow);
|
||||
margin-bottom: 20px;
|
||||
animation: slide-in ease 0.3s;
|
||||
background: var(--white);
|
||||
}
|
||||
|
||||
.list .list-item:last-child {
|
||||
@@ -72,11 +73,26 @@
|
||||
box-shadow: var(--card-shadow);
|
||||
background-color: var(--white);
|
||||
border-radius: 12px;
|
||||
width: 60vw;
|
||||
width: 80vw;
|
||||
max-width: 900px;
|
||||
min-width: 300px;
|
||||
animation: slide-in ease 0.3s;
|
||||
|
||||
--modal-padding: 20px;
|
||||
|
||||
&-max {
|
||||
width: 95vw;
|
||||
max-width: unset;
|
||||
height: 95vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.modal-content {
|
||||
max-height: unset !important;
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: var(--modal-padding);
|
||||
display: flex;
|
||||
@@ -89,11 +105,19 @@
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.modal-close-btn {
|
||||
cursor: pointer;
|
||||
.modal-header-actions {
|
||||
display: flex;
|
||||
|
||||
&:hover {
|
||||
filter: brightness(1.2);
|
||||
.modal-header-action {
|
||||
cursor: pointer;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -242,9 +266,40 @@
|
||||
resize: none;
|
||||
outline: none;
|
||||
box-sizing: border-box;
|
||||
min-height: 30vh;
|
||||
|
||||
&:focus {
|
||||
border: 1px solid var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
.selector {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 999;
|
||||
|
||||
&-content {
|
||||
.list {
|
||||
overflow: hidden;
|
||||
|
||||
.list-item {
|
||||
cursor: pointer;
|
||||
background-color: var(--white);
|
||||
|
||||
&:hover {
|
||||
filter: brightness(0.95);
|
||||
}
|
||||
|
||||
&:active {
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import styles from "./ui-lib.module.scss";
|
||||
import LoadingIcon from "../icons/three-dots.svg";
|
||||
import CloseIcon from "../icons/close.svg";
|
||||
@@ -6,6 +7,8 @@ import EyeOffIcon from "../icons/eye-off.svg";
|
||||
import DownIcon from "../icons/down.svg";
|
||||
import ConfirmIcon from "../icons/confirm.svg";
|
||||
import CancelIcon from "../icons/cancel.svg";
|
||||
import MaxIcon from "../icons/max.svg";
|
||||
import MinIcon from "../icons/min.svg";
|
||||
|
||||
import Locale from "../locales";
|
||||
|
||||
@@ -44,9 +47,13 @@ export function ListItem(props: {
|
||||
children?: JSX.Element | JSX.Element[];
|
||||
icon?: JSX.Element;
|
||||
className?: string;
|
||||
onClick?: () => void;
|
||||
}) {
|
||||
return (
|
||||
<div className={styles["list-item"] + ` ${props.className || ""}`}>
|
||||
<div
|
||||
className={styles["list-item"] + ` ${props.className || ""}`}
|
||||
onClick={props.onClick}
|
||||
>
|
||||
<div className={styles["list-header"]}>
|
||||
{props.icon && <div className={styles["list-icon"]}>{props.icon}</div>}
|
||||
<div className={styles["list-item-title"]}>
|
||||
@@ -93,6 +100,7 @@ interface ModalProps {
|
||||
title: string;
|
||||
children?: any;
|
||||
actions?: JSX.Element[];
|
||||
defaultMax?: boolean;
|
||||
onClose?: () => void;
|
||||
}
|
||||
export function Modal(props: ModalProps) {
|
||||
@@ -111,13 +119,30 @@ export function Modal(props: ModalProps) {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const [isMax, setMax] = useState(!!props.defaultMax);
|
||||
|
||||
return (
|
||||
<div className={styles["modal-container"]}>
|
||||
<div
|
||||
className={
|
||||
styles["modal-container"] + ` ${isMax && styles["modal-container-max"]}`
|
||||
}
|
||||
>
|
||||
<div className={styles["modal-header"]}>
|
||||
<div className={styles["modal-title"]}>{props.title}</div>
|
||||
|
||||
<div className={styles["modal-close-btn"]} onClick={props.onClose}>
|
||||
<CloseIcon />
|
||||
<div className={styles["modal-header-actions"]}>
|
||||
<div
|
||||
className={styles["modal-header-action"]}
|
||||
onClick={() => setMax(!isMax)}
|
||||
>
|
||||
{isMax ? <MinIcon /> : <MaxIcon />}
|
||||
</div>
|
||||
<div
|
||||
className={styles["modal-header-action"]}
|
||||
onClick={props.onClose}
|
||||
>
|
||||
<CloseIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -321,6 +346,7 @@ export function showConfirm(content: any) {
|
||||
function PromptInput(props: {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
rows?: number;
|
||||
}) {
|
||||
const [input, setInput] = useState(props.value);
|
||||
const onInput = (value: string) => {
|
||||
@@ -334,11 +360,12 @@ function PromptInput(props: {
|
||||
autoFocus
|
||||
value={input}
|
||||
onInput={(e) => onInput(e.currentTarget.value)}
|
||||
rows={props.rows ?? 3}
|
||||
></textarea>
|
||||
);
|
||||
}
|
||||
|
||||
export function showPrompt(content: any, value = "") {
|
||||
export function showPrompt(content: any, value = "", rows = 3) {
|
||||
const div = document.createElement("div");
|
||||
div.className = "modal-mask";
|
||||
document.body.appendChild(div);
|
||||
@@ -386,8 +413,60 @@ export function showPrompt(content: any, value = "") {
|
||||
<PromptInput
|
||||
onChange={(val) => (userInput = val)}
|
||||
value={value}
|
||||
rows={rows}
|
||||
></PromptInput>
|
||||
</Modal>,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function showImageModal(img: string) {
|
||||
showModal({
|
||||
title: Locale.Export.Image.Modal,
|
||||
children: (
|
||||
<div>
|
||||
<img
|
||||
src={img}
|
||||
alt="preview"
|
||||
style={{
|
||||
maxWidth: "100%",
|
||||
}}
|
||||
></img>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
export function Selector<T>(props: {
|
||||
items: Array<{
|
||||
title: string;
|
||||
subTitle?: string;
|
||||
value: T;
|
||||
}>;
|
||||
onSelection?: (selection: T[]) => void;
|
||||
onClose?: () => void;
|
||||
multiple?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className={styles["selector"]} onClick={() => props.onClose?.()}>
|
||||
<div className={styles["selector-content"]}>
|
||||
<List>
|
||||
{props.items.map((item, i) => {
|
||||
return (
|
||||
<ListItem
|
||||
className={styles["selector-item"]}
|
||||
key={i}
|
||||
title={item.title}
|
||||
subTitle={item.subTitle}
|
||||
onClick={() => {
|
||||
props.onSelection?.([item.value]);
|
||||
props.onClose?.();
|
||||
}}
|
||||
></ListItem>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -9,7 +9,7 @@ export const getBuildConfig = () => {
|
||||
|
||||
const buildMode = process.env.BUILD_MODE ?? "standalone";
|
||||
const isApp = !!process.env.BUILD_APP;
|
||||
const version = tauriConfig.package.version;
|
||||
const version = "v" + tauriConfig.package.version;
|
||||
|
||||
const commitInfo = (() => {
|
||||
try {
|
||||
|
@@ -46,7 +46,7 @@ export const getServerSideConfig = () => {
|
||||
proxyUrl: process.env.PROXY_URL,
|
||||
isVercel: !!process.env.VERCEL,
|
||||
hideUserApiKey: !!process.env.HIDE_USER_API_KEY,
|
||||
enableGPT4: !process.env.DISABLE_GPT4,
|
||||
disableGPT4: !!process.env.DISABLE_GPT4,
|
||||
hideBalanceQuery: !!process.env.HIDE_BALANCE_QUERY,
|
||||
};
|
||||
};
|
||||
|
@@ -53,6 +53,7 @@ export const OpenaiPath = {
|
||||
ChatPath: "v1/chat/completions",
|
||||
UsagePath: "dashboard/billing/usage",
|
||||
SubsPath: "dashboard/billing/subscription",
|
||||
ListModelPath: "v1/models",
|
||||
};
|
||||
|
||||
export const DEFAULT_INPUT_TEMPLATE = `{{input}}`; // input / time / model / lang
|
||||
@@ -61,3 +62,70 @@ You are ChatGPT, a large language model trained by OpenAI.
|
||||
Knowledge cutoff: 2021-09
|
||||
Current model: {{model}}
|
||||
Current time: {{time}}`;
|
||||
|
||||
export const DEFAULT_MODELS = [
|
||||
{
|
||||
name: "gpt-4",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
name: "gpt-4-0314",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
name: "gpt-4-0613",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
name: "gpt-4-32k",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
name: "gpt-4-32k-0314",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
name: "gpt-4-32k-0613",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
name: "gpt-3.5-turbo",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
name: "gpt-3.5-turbo-0301",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
name: "gpt-3.5-turbo-0613",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
name: "gpt-3.5-turbo-16k",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
name: "gpt-3.5-turbo-16k-0613",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
name: "qwen-v1", // 通义千问
|
||||
available: false,
|
||||
},
|
||||
{
|
||||
name: "ernie", // 文心一言
|
||||
available: false,
|
||||
},
|
||||
{
|
||||
name: "spark", // 讯飞星火
|
||||
available: false,
|
||||
},
|
||||
{
|
||||
name: "llama", // llama
|
||||
available: false,
|
||||
},
|
||||
{
|
||||
name: "chatglm", // chatglm-6b
|
||||
available: false,
|
||||
},
|
||||
] as const;
|
||||
|
@@ -130,8 +130,10 @@ const ar: PartialLocaleType = {
|
||||
SubTitle: "معاينة Markdown في فقاعة",
|
||||
},
|
||||
Mask: {
|
||||
Title: "شاشة تظهر الأقنعة",
|
||||
SubTitle: "عرض شاشة تظهر الأقنعة قبل بدء الدردشة الجديدة",
|
||||
Splash: {
|
||||
Title: "شاشة تظهر الأقنعة",
|
||||
SubTitle: "عرض شاشة تظهر الأقنعة قبل بدء الدردشة الجديدة",
|
||||
},
|
||||
},
|
||||
Prompt: {
|
||||
Disable: {
|
||||
|
338
app/locales/bn.ts
Normal file
338
app/locales/bn.ts
Normal file
@@ -0,0 +1,338 @@
|
||||
import { SubmitKey } from "../store/config";
|
||||
import { PartialLocaleType } from "./index";
|
||||
|
||||
const bn: PartialLocaleType = {
|
||||
WIP: "শীঘ্রই আসছে...",
|
||||
Error: {
|
||||
Unauthorized:
|
||||
"অননুমোদিত অ্যাক্সেস, অনুগ্রহ করে [অথোরাইজশন](/#/auth) পৃষ্ঠায় অ্যাক্সেস কোড ইনপুট করুন।",
|
||||
},
|
||||
Auth: {
|
||||
Title: "একটি অ্যাক্সেস কোড প্রয়োজন",
|
||||
Tips: "নীচে অ্যাক্সেস কোড ইনপুট করুন",
|
||||
Input: "অ্যাক্সেস কোড",
|
||||
Confirm: "নিশ্চিত করুন",
|
||||
Later: "পরে",
|
||||
},
|
||||
ChatItem: {
|
||||
ChatItemCount: (count: number) => `${count} টি বার্তা`,
|
||||
},
|
||||
Chat: {
|
||||
SubTitle: (count: number) => `${count} টি বার্তা`,
|
||||
Actions: {
|
||||
ChatList: "চ্যাট তালিকায় যান",
|
||||
CompressedHistory: "সংক্ষিপ্ত ইতিহাস মেমোরি প্রম্পট",
|
||||
Export: "সমস্ত বার্তা মার্কডাউন হিসাবে রপ্তানি করুন",
|
||||
Copy: "কপি",
|
||||
Stop: "বন্ধ করুন",
|
||||
Retry: "পুনরায় চেষ্টা করুন",
|
||||
Pin: "পিন করুন",
|
||||
PinToastContent: "পিন করা হয়েছে ২টি বার্তা প্রম্পটে",
|
||||
PinToastAction: "দেখুন",
|
||||
Delete: "মুছে ফেলুন",
|
||||
Edit: "সম্পাদন করুন",
|
||||
},
|
||||
Commands: {
|
||||
new: "নতুন চ্যাট শুরু করুন",
|
||||
newm: "মাস্ক সহ নতুন চ্যাট শুরু করুন",
|
||||
next: "পরবর্তী চ্যাট",
|
||||
prev: "পূর্ববর্তী চ্যাট",
|
||||
clear: "সংশ্লিষ্টতাবদ্ধকরণ পরিষ্কার করুন",
|
||||
del: "চ্যাট মুছুন",
|
||||
},
|
||||
InputActions: {
|
||||
Stop: "বন্ধ করুন",
|
||||
ToBottom: "সর্বশেষতম দিকে",
|
||||
Theme: {
|
||||
auto: "অটো",
|
||||
light: "হালকা থিম",
|
||||
dark: "ডার্ক থিম",
|
||||
},
|
||||
Prompt: "প্রম্পটগুলিতে",
|
||||
Masks: "মাস্কগুলি",
|
||||
Clear: "সংশ্লিষ্টতাবদ্ধকরণ পরিষ্কার করুন",
|
||||
Settings: "সেটিংস",
|
||||
},
|
||||
Rename: "চ্যাট পুনঃনামকরণ করুন",
|
||||
Typing: "টাইপিং...",
|
||||
Input: (submitKey: string) => {
|
||||
var inputHints = `${submitKey} to send`;
|
||||
if (submitKey === String(SubmitKey.Enter)) {
|
||||
inputHints += ", Shift + Enter to wrap";
|
||||
}
|
||||
return inputHints + ", / to search prompts, : to use commands";
|
||||
},
|
||||
Send: "প্রেরণ করুন",
|
||||
Config: {
|
||||
Reset: "ডিফল্টে রিসেট করুন",
|
||||
SaveAs: "মাস্ক হিসাবে সংরক্ষণ করুন",
|
||||
},
|
||||
},
|
||||
Export: {
|
||||
Title: "বার্তা রপ্তানিকরণ",
|
||||
Copy: "সমস্তটি কপি করুন",
|
||||
Download: "ডাউনলোড করুন",
|
||||
MessageFromYou: "আপনার বার্তা",
|
||||
MessageFromChatGPT: "চ্যাটজিপিটির বার্তা",
|
||||
Share: "শেয়ার করুন শেয়ারজিপিটি তে",
|
||||
Format: {
|
||||
Title: "রপ্তানি ফরম্যাট",
|
||||
SubTitle: "মার্কডাউন বা পিএনজি চিত্র",
|
||||
},
|
||||
IncludeContext: {
|
||||
Title: "মাস্ক অন্তর্ভুক্ত করুন",
|
||||
SubTitle: "মাস্কগুলি সংরক্ষণ করবেন না কি",
|
||||
},
|
||||
Steps: {
|
||||
Select: "নির্বাচন করুন",
|
||||
Preview: "প্রিভিউ করুন",
|
||||
},
|
||||
},
|
||||
Select: {
|
||||
Search: "অনুসন্ধান করুন",
|
||||
All: "সমস্তটি নির্বাচন করুন",
|
||||
Latest: "সর্বশেষতমটি নির্বাচন করুন",
|
||||
Clear: "পরিষ্কার করুন",
|
||||
},
|
||||
Memory: {
|
||||
Title: "মেমোরি প্রম্পট",
|
||||
EmptyContent: "এখনও কিছুই নেই।",
|
||||
Send: "মেমোরি প্রেরণ করুন",
|
||||
Copy: "মেমোরি কপি করুন",
|
||||
Reset: "পুনরায় নিশ্চিত করুন",
|
||||
ResetConfirm:
|
||||
"রিসেট করলে বর্তমান চ্যাট ইতিহাস এবং ঐতিহাসিক মেমোরি মুছে যাবে। পুনরায় নির্দিষ্ট করতে চান তা নিশ্চিত করতে চান?",
|
||||
},
|
||||
Home: {
|
||||
NewChat: "নতুন চ্যাট",
|
||||
DeleteChat: "নির্বাচিত সংলাপটি মুছতে নিশ্চিত করুন?",
|
||||
DeleteToast: "চ্যাটটি মুছেছেন",
|
||||
Revert: "পুনরায়",
|
||||
},
|
||||
Settings: {
|
||||
Title: "সেটিংস",
|
||||
SubTitle: "সমস্ত সেটিংস",
|
||||
Danger: {
|
||||
Reset: {
|
||||
Title: "সমস্ত সেটিংস পুনঃনির্দেশ দিন",
|
||||
SubTitle: "সকল সেটিংস ডিফল্টে পুনঃনির্দেশ দিতে",
|
||||
Action: "পুনঃনির্দেশ দিন",
|
||||
Confirm: "সমস্ত সেটিংস ডিফল্টে পুনঃনির্দেশ করতে নিশ্চিত করতে?",
|
||||
},
|
||||
Clear: {
|
||||
Title: "সমস্ত তথ্য মুছুন",
|
||||
SubTitle: "সমস্ত বার্তা এবং সেটিংস মুছুন",
|
||||
Action: "মুছুন",
|
||||
Confirm: "সমস্ত বার্তা এবং সেটিংস মুছে ফেলতে নিশ্চিত করতে?",
|
||||
},
|
||||
},
|
||||
Lang: {
|
||||
Name: "বাংলা", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
|
||||
All: "সমস্ত ভাষা",
|
||||
},
|
||||
Avatar: "অবতার",
|
||||
FontSize: {
|
||||
Title: "ফন্ট সাইজ",
|
||||
SubTitle: "চ্যাট সামগ্রীর ফন্ট সাইজ সংশোধন করুন",
|
||||
},
|
||||
|
||||
InputTemplate: {
|
||||
Title: "ইনপুট টেমপ্লেট",
|
||||
SubTitle: "নতুনতম বার্তা এই টেমপ্লেটে পূরণ হবে",
|
||||
},
|
||||
|
||||
Update: {
|
||||
Version: (x: string) => `Version: ${x}`,
|
||||
IsLatest: "Latest version",
|
||||
CheckUpdate: "Check Update",
|
||||
IsChecking: "Checking update...",
|
||||
FoundUpdate: (x: string) => `Found new version: ${x}`,
|
||||
GoToUpdate: "Update",
|
||||
},
|
||||
SendKey: "প্রেরণ চাবি",
|
||||
Theme: "থিম",
|
||||
TightBorder: "সঙ্গতি সীমা",
|
||||
SendPreviewBubble: {
|
||||
Title: "প্রিভিউ বুলবুল প্রেরণ করুন",
|
||||
SubTitle: "বুলবুলে মার্কডাউন প্রিভিউ করুন",
|
||||
},
|
||||
Mask: {
|
||||
Splash: {
|
||||
Title: "মাস্ক স্প্ল্যাশ স্ক্রিন",
|
||||
SubTitle:
|
||||
"নতুন চ্যাট শুরু করার আগে মাস্ক স্প্ল্যাশ স্ক্রিন প্রদর্শন করুন",
|
||||
},
|
||||
Builtin: {
|
||||
Title: "মূলত মাস্ক গোপন করুন",
|
||||
SubTitle: "মাস্ক তালিকা থেকে মূলত মাস্কগুলি লুকান",
|
||||
},
|
||||
},
|
||||
Prompt: {
|
||||
Disable: {
|
||||
Title: "অটো-সম্পূর্ণতা নিষ্ক্রিয় করুন",
|
||||
SubTitle: "অটো-সম্পূর্ণতা চালু করতে / ইনপুট করুন",
|
||||
},
|
||||
List: "প্রম্পট তালিকা",
|
||||
ListCount: (builtin: number, custom: number) =>
|
||||
`${builtin} built-in, ${custom} user-defined`,
|
||||
Edit: "সম্পাদন করুন",
|
||||
Modal: {
|
||||
Title: "প্রম্পট তালিকা",
|
||||
Add: "একটি যোগ করুন",
|
||||
Search: "সন্ধান প্রম্পট",
|
||||
},
|
||||
EditModal: {
|
||||
Title: "সম্পাদন করুন প্রম্পট",
|
||||
},
|
||||
},
|
||||
HistoryCount: {
|
||||
Title: "সংযুক্ত বার্তা সংখ্যা",
|
||||
SubTitle: "প্রতি অনুরোধে প্রেরণ করা গেলে প্রেরণ করা হবে",
|
||||
},
|
||||
CompressThreshold: {
|
||||
Title: "ইতিহাস সঙ্কুচিত করার সীমা",
|
||||
SubTitle:
|
||||
"নকুল বার্তা দৈর্ঘ্য সীমা অতিক্রান্ত হলে ঐ বার্তাটি সঙ্কুচিত হবে",
|
||||
},
|
||||
Token: {
|
||||
Title: "অ্যাপি কী",
|
||||
SubTitle: "অ্যাক্সেস কোড সীমা উপেক্ষা করতে আপনার কীটি ব্যবহার করুন",
|
||||
Placeholder: "OpenAI API কী",
|
||||
},
|
||||
Usage: {
|
||||
Title: "একাউন্ট ব্যালেন্স",
|
||||
SubTitle(used: any, total: any) {
|
||||
return `এই মাসে ব্যবহৃত $${used}, সাবস্ক্রিপশন $${total}`;
|
||||
},
|
||||
IsChecking: "চেক করা হচ্ছে...",
|
||||
Check: "চেক",
|
||||
NoAccess: "ব্যালেন্স চেক করতে অ্যাপি কী ইনপুট করুন",
|
||||
},
|
||||
AccessCode: {
|
||||
Title: "অ্যাক্সেস কোড",
|
||||
SubTitle: "অ্যাক্সেস নিয়ন্ত্রণ সক্রিয়",
|
||||
Placeholder: "অ্যাক্সেস কোড প্রয়োজন",
|
||||
},
|
||||
Endpoint: {
|
||||
Title: "ইনটারপয়েন্ট",
|
||||
SubTitle: "কাস্টম এন্ডপয়েন্টটি হতে হবে http(s):// দিয়ে শুরু হতে হবে",
|
||||
},
|
||||
Model: "মডেল",
|
||||
Temperature: {
|
||||
Title: "তাপমাত্রা",
|
||||
SubTitle: "আরতি মান বেশি করলে বেশি এলোমেলো আউটপুট হবে",
|
||||
},
|
||||
TopP: {
|
||||
Title: "শীর্ষ পি",
|
||||
SubTitle: "তাপমাত্রা সঙ্গে এই মান পরিবর্তন করবেন না",
|
||||
},
|
||||
MaxTokens: {
|
||||
Title: "সর্বাধিক টোকেন",
|
||||
SubTitle: "ইনপুট টোকেন এবং উৎপাদিত টোকেনের সর্বাধিক দৈর্ঘ্য",
|
||||
},
|
||||
PresencePenalty: {
|
||||
Title: "উপস্থিতির জরিমানা",
|
||||
SubTitle: "আরতি মান বেশি করলে নতুন বিষয়গুলি সম্ভাব্যতা বাড়াতে পারে",
|
||||
},
|
||||
FrequencyPenalty: {
|
||||
Title: "ফ্রিকুয়েন্সি জরিমানা",
|
||||
SubTitle:
|
||||
"আরতি মান বাড়ালে একই লাইন পুনরায় ব্যাবহার করার সম্ভাবনা হ্রাস পায়",
|
||||
},
|
||||
},
|
||||
Store: {
|
||||
DefaultTopic: "নতুন সংলাপ",
|
||||
BotHello: "হ্যালো! আজকে আপনাকে কিভাবে সাহায্য করতে পারি?",
|
||||
Error: "কিছু নিয়ে ভুল হয়েছে, পরে আবার চেষ্টা করুন।",
|
||||
Prompt: {
|
||||
History: (content: string) =>
|
||||
"এটি চ্যাট ইতিহাসের সংক্ষিপ্ত সংকলনের মতো: " + content,
|
||||
Topic:
|
||||
"আমাদের সংলাপটির চার থেকে পাঁচ শব্দের একটি শিরোনাম তৈরি করুন যা আমাদের আলাপের সংক্ষিপ্তসার হিসাবে যোগ হবে না, যেমন অভিবৃত্তি, বিন্যাস, উদ্ধৃতি, পূর্বচালক চিহ্ন, পূর্বরোবক্তির যেকোনো চিহ্ন বা অতিরিক্ত পাঠ। মেয়াদশেষ উদ্ধৃতি চেষ্টা করুন।",
|
||||
Summarize:
|
||||
"২০০ শব্দের লম্বা হয়ে মুহূর্তে আলোচনা সংক্ষেপের রপ্তানি করুন, যেটি ভবিষ্যতের প্রম্পট হিসাবে ব্যবহার করবেন।",
|
||||
},
|
||||
},
|
||||
Copy: {
|
||||
Success: "ক্লিপবোর্ডে কপি করা হয়েছে",
|
||||
Failed: "কপি ব্যর্থ, অনুমতি প্রদান করার জন্য অনুমতি প্রদান করুন",
|
||||
},
|
||||
Context: {
|
||||
Toast: (x: any) => `With ${x} contextual prompts`,
|
||||
Edit: "বর্তমান চ্যাট সেটিংস",
|
||||
Add: "একটি প্রম্পট যোগ করুন",
|
||||
Clear: "সঙ্গতি পরিস্কার করুন",
|
||||
Revert: "পূর্ববর্তী অবস্থানে ফিরে যান",
|
||||
},
|
||||
Plugin: {
|
||||
Name: "প্লাগইন",
|
||||
},
|
||||
Mask: {
|
||||
Name: "মাস্ক",
|
||||
Page: {
|
||||
Title: "প্রম্পট টেমপ্লেট",
|
||||
SubTitle: (count: number) => `${count} টি প্রম্পট টেমপ্লেট`,
|
||||
Search: "টেমপ্লেট অনুসন্ধান করুন",
|
||||
Create: "তৈরি করুন",
|
||||
},
|
||||
Item: {
|
||||
Info: (count: number) => `${count} প্রম্পট`,
|
||||
Chat: "চ্যাট",
|
||||
View: "দেখুন",
|
||||
Edit: "সম্পাদন করুন",
|
||||
Delete: "মুছে ফেলুন",
|
||||
DeleteConfirm: "মুছে ফেলতে নিশ্চিত করুন?",
|
||||
},
|
||||
EditModal: {
|
||||
Title: (readonly: boolean) =>
|
||||
`প্রম্পট টেমপ্লেট সম্পাদন করুন ${readonly ? "(readonly)" : ""}`,
|
||||
Download: "ডাউনলোড করুন",
|
||||
Clone: "ক্লোন করুন",
|
||||
},
|
||||
Config: {
|
||||
Avatar: "বট অবতার",
|
||||
Name: "বটের নাম",
|
||||
Sync: {
|
||||
Title: "গ্লোবাল কনফিগ ব্যবহার করুন",
|
||||
SubTitle: "এই চ্যাটে গ্লোবাল কনফিগ ব্যবহার করুন",
|
||||
Confirm:
|
||||
"গ্লোবাল কনফিগ দ্বারা কাস্টম কনফিগ ওভাররাইড করতে নিশ্চিত করতে?",
|
||||
},
|
||||
HideContext: {
|
||||
Title: "সংশ্লিষ্টতা প্রম্পটগুলি লুকান",
|
||||
SubTitle: "চ্যাটে সংশ্লিষ্টতা প্রম্পটগুলি দেখাবেন না",
|
||||
},
|
||||
Share: {
|
||||
Title: "এই মাস্কটি শেয়ার করুন",
|
||||
SubTitle: "এই মাস্কের একটি লিঙ্ক তৈরি করুন",
|
||||
Action: "লিঙ্ক কপি করুন",
|
||||
},
|
||||
},
|
||||
},
|
||||
NewChat: {
|
||||
Return: "ফিরে যান",
|
||||
Skip: "শুরু করুন",
|
||||
Title: "মাস্ক নির্বাচন করুন",
|
||||
SubTitle: "মাস্কের পিছনে আত্মার সঙ্গে চ্যাট করুন",
|
||||
More: "আরো খুঁজুন",
|
||||
NotShow: "এখনও দেখাবেন না",
|
||||
ConfirmNoShow:
|
||||
"নিষ্ক্রিয় করতে নিশ্চিত করুন? পরে আপনি এটি সেটিংসে সক্ষম করতে পারবেন।",
|
||||
},
|
||||
|
||||
UI: {
|
||||
Confirm: "নিশ্চিত করুন",
|
||||
Cancel: "বাতিল করুন",
|
||||
Close: "বন্ধ করুন",
|
||||
Create: "তৈরি করুন",
|
||||
Edit: "সম্পাদন করুন",
|
||||
},
|
||||
Exporter: {
|
||||
Model: "মডেল",
|
||||
Messages: "বার্তা",
|
||||
Topic: "টপিক",
|
||||
Time: "সময়",
|
||||
},
|
||||
};
|
||||
|
||||
export default bn;
|
@@ -26,7 +26,7 @@ const cn = {
|
||||
Stop: "停止",
|
||||
Retry: "重试",
|
||||
Pin: "固定",
|
||||
PinToastContent: "已将 2 条对话固定至预设提示词",
|
||||
PinToastContent: "已将 1 条对话固定至预设提示词",
|
||||
PinToastAction: "查看",
|
||||
Delete: "删除",
|
||||
Edit: "编辑",
|
||||
@@ -66,6 +66,7 @@ const cn = {
|
||||
Reset: "清除记忆",
|
||||
SaveAs: "存为面具",
|
||||
},
|
||||
IsContext: "预设提示词",
|
||||
},
|
||||
Export: {
|
||||
Title: "分享聊天记录",
|
||||
@@ -86,6 +87,10 @@ const cn = {
|
||||
Select: "选取",
|
||||
Preview: "预览",
|
||||
},
|
||||
Image: {
|
||||
Toast: "正在生成截图",
|
||||
Modal: "长按或右键保存图片",
|
||||
},
|
||||
},
|
||||
Select: {
|
||||
Search: "搜索消息",
|
||||
@@ -156,8 +161,14 @@ const cn = {
|
||||
SubTitle: "在预览气泡中预览 Markdown 内容",
|
||||
},
|
||||
Mask: {
|
||||
Title: "面具启动页",
|
||||
SubTitle: "新建聊天时,展示面具启动页",
|
||||
Splash: {
|
||||
Title: "面具启动页",
|
||||
SubTitle: "新建聊天时,展示面具启动页",
|
||||
},
|
||||
Builtin: {
|
||||
Title: "隐藏内置面具",
|
||||
SubTitle: "在所有面具列表中隐藏内置面具",
|
||||
},
|
||||
},
|
||||
Prompt: {
|
||||
Disable: {
|
||||
@@ -214,6 +225,10 @@ const cn = {
|
||||
Title: "随机性 (temperature)",
|
||||
SubTitle: "值越大,回复越随机",
|
||||
},
|
||||
TopP: {
|
||||
Title: "核采样 (top_p)",
|
||||
SubTitle: "与随机性类似,但不要和随机性一起更改",
|
||||
},
|
||||
MaxTokens: {
|
||||
Title: "单次回复限制 (max_tokens)",
|
||||
SubTitle: "单次交互所用的最大 Token 数",
|
||||
@@ -287,6 +302,11 @@ const cn = {
|
||||
Title: "隐藏预设对话",
|
||||
SubTitle: "隐藏后预设对话不会出现在聊天界面",
|
||||
},
|
||||
Share: {
|
||||
Title: "分享此面具",
|
||||
SubTitle: "生成此面具的直达链接",
|
||||
Action: "复制链接",
|
||||
},
|
||||
},
|
||||
},
|
||||
NewChat: {
|
||||
|
@@ -87,8 +87,10 @@ const cs: PartialLocaleType = {
|
||||
SubTitle: "Zobrazit v náhledu bubliny",
|
||||
},
|
||||
Mask: {
|
||||
Title: "Úvodní obrazovka Masek",
|
||||
SubTitle: "Před zahájením nového chatu zobrazte úvodní obrazovku Masek",
|
||||
Splash: {
|
||||
Title: "Úvodní obrazovka Masek",
|
||||
SubTitle: "Před zahájením nového chatu zobrazte úvodní obrazovku Masek",
|
||||
},
|
||||
},
|
||||
Prompt: {
|
||||
Disable: {
|
||||
|
@@ -87,8 +87,10 @@ const de: PartialLocaleType = {
|
||||
SubTitle: "Preview markdown in bubble",
|
||||
},
|
||||
Mask: {
|
||||
Title: "Mask Splash Screen",
|
||||
SubTitle: "Show a mask splash screen before starting new chat",
|
||||
Splash: {
|
||||
Title: "Mask Splash Screen",
|
||||
SubTitle: "Show a mask splash screen before starting new chat",
|
||||
},
|
||||
},
|
||||
Prompt: {
|
||||
Disable: {
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { SubmitKey } from "../store/config";
|
||||
import { LocaleType } from "./index";
|
||||
|
||||
// if you are adding a new translation, please use PartialLocaleType instead of LocaleType
|
||||
const en: LocaleType = {
|
||||
WIP: "Coming Soon...",
|
||||
Error: {
|
||||
@@ -27,7 +28,7 @@ const en: LocaleType = {
|
||||
Stop: "Stop",
|
||||
Retry: "Retry",
|
||||
Pin: "Pin",
|
||||
PinToastContent: "Pinned 2 messages to contextual prompts",
|
||||
PinToastContent: "Pinned 1 messages to contextual prompts",
|
||||
PinToastAction: "View",
|
||||
Delete: "Delete",
|
||||
Edit: "Edit",
|
||||
@@ -67,6 +68,7 @@ const en: LocaleType = {
|
||||
Reset: "Reset to Default",
|
||||
SaveAs: "Save as Mask",
|
||||
},
|
||||
IsContext: "Contextual Prompt",
|
||||
},
|
||||
Export: {
|
||||
Title: "Export Messages",
|
||||
@@ -87,6 +89,10 @@ const en: LocaleType = {
|
||||
Select: "Select",
|
||||
Preview: "Preview",
|
||||
},
|
||||
Image: {
|
||||
Toast: "Capturing Image...",
|
||||
Modal: "Long press or right click to save image",
|
||||
},
|
||||
},
|
||||
Select: {
|
||||
Search: "Search",
|
||||
@@ -157,8 +163,14 @@ const en: LocaleType = {
|
||||
SubTitle: "Preview markdown in bubble",
|
||||
},
|
||||
Mask: {
|
||||
Title: "Mask Splash Screen",
|
||||
SubTitle: "Show a mask splash screen before starting new chat",
|
||||
Splash: {
|
||||
Title: "Mask Splash Screen",
|
||||
SubTitle: "Show a mask splash screen before starting new chat",
|
||||
},
|
||||
Builtin: {
|
||||
Title: "Hide Builtin Masks",
|
||||
SubTitle: "Hide builtin masks in mask list",
|
||||
},
|
||||
},
|
||||
Prompt: {
|
||||
Disable: {
|
||||
@@ -215,6 +227,10 @@ const en: LocaleType = {
|
||||
Title: "Temperature",
|
||||
SubTitle: "A larger value makes the more random output",
|
||||
},
|
||||
TopP: {
|
||||
Title: "Top P",
|
||||
SubTitle: "Do not alter this value together with temperature",
|
||||
},
|
||||
MaxTokens: {
|
||||
Title: "Max Tokens",
|
||||
SubTitle: "Maximum length of input tokens and generated tokens",
|
||||
@@ -249,7 +265,7 @@ const en: LocaleType = {
|
||||
},
|
||||
Context: {
|
||||
Toast: (x: any) => `With ${x} contextual prompts`,
|
||||
Edit: "Contextual and Memory Prompts",
|
||||
Edit: "Current Chat Settings",
|
||||
Add: "Add a Prompt",
|
||||
Clear: "Context Cleared",
|
||||
Revert: "Revert",
|
||||
@@ -291,6 +307,11 @@ const en: LocaleType = {
|
||||
Title: "Hide Context Prompts",
|
||||
SubTitle: "Do not show in-context prompts in chat",
|
||||
},
|
||||
Share: {
|
||||
Title: "Share This Mask",
|
||||
SubTitle: "Generate a link to this mask",
|
||||
Action: "Copy Link",
|
||||
},
|
||||
},
|
||||
},
|
||||
NewChat: {
|
||||
|
@@ -87,8 +87,10 @@ const es: PartialLocaleType = {
|
||||
SubTitle: "Preview markdown in bubble",
|
||||
},
|
||||
Mask: {
|
||||
Title: "Mask Splash Screen",
|
||||
SubTitle: "Show a mask splash screen before starting new chat",
|
||||
Splash: {
|
||||
Title: "Mask Splash Screen",
|
||||
SubTitle: "Show a mask splash screen before starting new chat",
|
||||
},
|
||||
},
|
||||
Prompt: {
|
||||
Disable: {
|
||||
|
@@ -88,9 +88,11 @@ const fr: PartialLocaleType = {
|
||||
SubTitle: "Aperçu du Markdown dans une bulle",
|
||||
},
|
||||
Mask: {
|
||||
Title: "Écran de masque",
|
||||
SubTitle:
|
||||
"Afficher un écran de masque avant de démarrer une nouvelle discussion",
|
||||
Splash: {
|
||||
Title: "Écran de masque",
|
||||
SubTitle:
|
||||
"Afficher un écran de masque avant de démarrer une nouvelle discussion",
|
||||
},
|
||||
},
|
||||
Prompt: {
|
||||
Disable: {
|
||||
|
@@ -13,6 +13,7 @@ import no from "./no";
|
||||
import cs from "./cs";
|
||||
import ko from "./ko";
|
||||
import ar from "./ar";
|
||||
import bn from "./bn";
|
||||
import { merge } from "../utils/merge";
|
||||
|
||||
import type { LocaleType } from "./cn";
|
||||
@@ -34,6 +35,7 @@ const ALL_LANGS = {
|
||||
cs,
|
||||
no,
|
||||
ar,
|
||||
bn,
|
||||
};
|
||||
|
||||
export type Lang = keyof typeof ALL_LANGS;
|
||||
@@ -56,6 +58,7 @@ export const ALL_LANG_OPTIONS: Record<Lang, string> = {
|
||||
cs: "Čeština",
|
||||
no: "Nynorsk",
|
||||
ar: "العربية",
|
||||
bn: "বাংলা",
|
||||
};
|
||||
|
||||
const LANG_KEY = "lang";
|
||||
|
@@ -87,8 +87,10 @@ const it: PartialLocaleType = {
|
||||
SubTitle: "Preview markdown in bubble",
|
||||
},
|
||||
Mask: {
|
||||
Title: "Mask Splash Screen",
|
||||
SubTitle: "Show a mask splash screen before starting new chat",
|
||||
Splash: {
|
||||
Title: "Mask Splash Screen",
|
||||
SubTitle: "Show a mask splash screen before starting new chat",
|
||||
},
|
||||
},
|
||||
Prompt: {
|
||||
Disable: {
|
||||
|
@@ -61,7 +61,20 @@ const jp: PartialLocaleType = {
|
||||
Settings: {
|
||||
Title: "設定",
|
||||
SubTitle: "設定オプション",
|
||||
|
||||
Danger: {
|
||||
Reset: {
|
||||
Title: "設定をリセット",
|
||||
SubTitle: "すべての設定項目をデフォルトにリセットします",
|
||||
Action: "今すぐリセットする",
|
||||
Confirm: "すべての設定項目をリセットしてもよろしいですか?",
|
||||
},
|
||||
Clear: {
|
||||
Title: "データを消去",
|
||||
SubTitle: "すべてのチャット履歴と設定を消去します",
|
||||
Action: "今すぐ消去する",
|
||||
Confirm: "すべてのチャット履歴と設定を消去しますか?",
|
||||
},
|
||||
},
|
||||
Lang: {
|
||||
Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
|
||||
All: "全ての言語",
|
||||
@@ -71,7 +84,10 @@ const jp: PartialLocaleType = {
|
||||
Title: "フォントサイズ",
|
||||
SubTitle: "チャット内容のフォントサイズ",
|
||||
},
|
||||
|
||||
InputTemplate: {
|
||||
Title: "入力の前処理",
|
||||
SubTitle: "新規入力がこのテンプレートに埋め込まれます",
|
||||
},
|
||||
Update: {
|
||||
Version: (x: string) => `現在のバージョン:${x}`,
|
||||
IsLatest: "最新バージョンです",
|
||||
@@ -88,8 +104,10 @@ const jp: PartialLocaleType = {
|
||||
SubTitle: "プレビューバブルでマークダウンコンテンツをプレビュー",
|
||||
},
|
||||
Mask: {
|
||||
Title: "キャラクターページ",
|
||||
SubTitle: "新規チャット作成時にキャラクターページを表示する",
|
||||
Splash: {
|
||||
Title: "キャラクターページ",
|
||||
SubTitle: "新規チャット作成時にキャラクターページを表示する",
|
||||
},
|
||||
},
|
||||
Prompt: {
|
||||
Disable: {
|
||||
|
@@ -87,8 +87,10 @@ const ko: PartialLocaleType = {
|
||||
SubTitle: "버블에서 마크다운 미리 보기",
|
||||
},
|
||||
Mask: {
|
||||
Title: "마스크 시작 화면",
|
||||
SubTitle: "새로운 채팅 시작 전에 마스크 시작 화면 표시",
|
||||
Splash: {
|
||||
Title: "마스크 시작 화면",
|
||||
SubTitle: "새로운 채팅 시작 전에 마스크 시작 화면 표시",
|
||||
},
|
||||
},
|
||||
Prompt: {
|
||||
Disable: {
|
||||
|
@@ -87,8 +87,10 @@ const ru: PartialLocaleType = {
|
||||
SubTitle: "Предварительный просмотр markdown в пузыре",
|
||||
},
|
||||
Mask: {
|
||||
Title: "Экран заставки маски",
|
||||
SubTitle: "Показывать экран заставки маски перед началом нового чата",
|
||||
Splash: {
|
||||
Title: "Экран заставки маски",
|
||||
SubTitle: "Показывать экран заставки маски перед началом нового чата",
|
||||
},
|
||||
},
|
||||
Prompt: {
|
||||
Disable: {
|
||||
|
@@ -87,8 +87,10 @@ const tr: PartialLocaleType = {
|
||||
SubTitle: "Preview markdown in bubble",
|
||||
},
|
||||
Mask: {
|
||||
Title: "Mask Splash Screen",
|
||||
SubTitle: "Show a mask splash screen before starting new chat",
|
||||
Splash: {
|
||||
Title: "Mask Splash Screen",
|
||||
SubTitle: "Show a mask splash screen before starting new chat",
|
||||
},
|
||||
},
|
||||
Prompt: {
|
||||
Disable: {
|
||||
|
@@ -85,8 +85,10 @@ const tw: PartialLocaleType = {
|
||||
SubTitle: "在预览气泡中预览 Markdown 内容",
|
||||
},
|
||||
Mask: {
|
||||
Title: "面具启动页",
|
||||
SubTitle: "新建聊天时,展示面具启动页",
|
||||
Splash: {
|
||||
Title: "面具启动页",
|
||||
SubTitle: "新建聊天时,展示面具启动页",
|
||||
},
|
||||
},
|
||||
Prompt: {
|
||||
Disable: {
|
||||
|
@@ -87,8 +87,10 @@ const vi: PartialLocaleType = {
|
||||
SubTitle: "Xem trước nội dung markdown bằng bong bóng",
|
||||
},
|
||||
Mask: {
|
||||
Title: "Mask Splash Screen",
|
||||
SubTitle: "Chớp màn hình khi bắt đầu cuộc trò chuyện mới",
|
||||
Splash: {
|
||||
Title: "Mask Splash Screen",
|
||||
SubTitle: "Chớp màn hình khi bắt đầu cuộc trò chuyện mới",
|
||||
},
|
||||
},
|
||||
Prompt: {
|
||||
Disable: {
|
||||
|
@@ -3,7 +3,6 @@ import { persist } from "zustand/middleware";
|
||||
import { DEFAULT_API_HOST, StoreKey } from "../constant";
|
||||
import { getHeaders } from "../client/api";
|
||||
import { BOT_HELLO } from "./chat";
|
||||
import { ALL_MODELS } from "./config";
|
||||
import { getClientConfig } from "../config/client";
|
||||
|
||||
export interface AccessControlStore {
|
||||
@@ -76,14 +75,6 @@ export const useAccessStore = create<AccessControlStore>()(
|
||||
console.log("[Config] got config from server", res);
|
||||
set(() => ({ ...res }));
|
||||
|
||||
if (!res.enableGPT4) {
|
||||
ALL_MODELS.forEach((model) => {
|
||||
if (model.name.startsWith("gpt-4")) {
|
||||
(model as any).available = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if ((res as any).botHello) {
|
||||
BOT_HELLO.content = (res as any).botHello;
|
||||
}
|
||||
|
@@ -103,6 +103,7 @@ interface ChatStore {
|
||||
resetSession: () => void;
|
||||
getMessagesWithMemory: () => ChatMessage[];
|
||||
getMemoryPrompt: () => ChatMessage;
|
||||
getSuggestions: () => Promise<string[]>;
|
||||
|
||||
clearAllData: () => void;
|
||||
}
|
||||
@@ -278,96 +279,99 @@ export const useChatStore = create<ChatStore>()(
|
||||
get().summarizeSession();
|
||||
},
|
||||
|
||||
async onUserInput(content) {
|
||||
const session = get().currentSession();
|
||||
const modelConfig = session.mask.modelConfig;
|
||||
onUserInput(content) {
|
||||
return new Promise((resolve) => {
|
||||
const session = get().currentSession();
|
||||
const modelConfig = session.mask.modelConfig;
|
||||
|
||||
const userContent = fillTemplateWith(content, modelConfig);
|
||||
console.log("[User Input] after template: ", userContent);
|
||||
const userContent = fillTemplateWith(content, modelConfig);
|
||||
console.log("[User Input] after template: ", userContent);
|
||||
|
||||
const userMessage: ChatMessage = createMessage({
|
||||
role: "user",
|
||||
content: userContent,
|
||||
});
|
||||
const userMessage: ChatMessage = createMessage({
|
||||
role: "user",
|
||||
content: userContent,
|
||||
});
|
||||
|
||||
const botMessage: ChatMessage = createMessage({
|
||||
role: "assistant",
|
||||
streaming: true,
|
||||
id: userMessage.id! + 1,
|
||||
model: modelConfig.model,
|
||||
});
|
||||
const botMessage: ChatMessage = createMessage({
|
||||
role: "assistant",
|
||||
streaming: true,
|
||||
id: userMessage.id! + 1,
|
||||
model: modelConfig.model,
|
||||
});
|
||||
|
||||
// get recent messages
|
||||
const recentMessages = get().getMessagesWithMemory();
|
||||
const sendMessages = recentMessages.concat(userMessage);
|
||||
const sessionIndex = get().currentSessionIndex;
|
||||
const messageIndex = get().currentSession().messages.length + 1;
|
||||
// get recent messages
|
||||
const recentMessages = get().getMessagesWithMemory();
|
||||
const sendMessages = recentMessages.concat(userMessage);
|
||||
const sessionIndex = get().currentSessionIndex;
|
||||
const messageIndex = get().currentSession().messages.length + 1;
|
||||
|
||||
// save user's and bot's message
|
||||
get().updateCurrentSession((session) => {
|
||||
const savedUserMessage = {
|
||||
...userMessage,
|
||||
content,
|
||||
};
|
||||
session.messages = session.messages.concat([
|
||||
savedUserMessage,
|
||||
botMessage,
|
||||
]);
|
||||
});
|
||||
// save user's and bot's message
|
||||
get().updateCurrentSession((session) => {
|
||||
const savedUserMessage = {
|
||||
...userMessage,
|
||||
content,
|
||||
};
|
||||
session.messages = session.messages.concat([
|
||||
savedUserMessage,
|
||||
botMessage,
|
||||
]);
|
||||
});
|
||||
|
||||
// make request
|
||||
api.llm.chat({
|
||||
messages: sendMessages,
|
||||
config: { ...modelConfig, stream: true },
|
||||
onUpdate(message) {
|
||||
botMessage.streaming = true;
|
||||
if (message) {
|
||||
botMessage.content = message;
|
||||
}
|
||||
get().updateCurrentSession((session) => {
|
||||
session.messages = session.messages.concat();
|
||||
});
|
||||
},
|
||||
onFinish(message) {
|
||||
botMessage.streaming = false;
|
||||
if (message) {
|
||||
botMessage.content = message;
|
||||
get().onNewMessage(botMessage);
|
||||
}
|
||||
ChatControllerPool.remove(
|
||||
sessionIndex,
|
||||
botMessage.id ?? messageIndex,
|
||||
);
|
||||
},
|
||||
onError(error) {
|
||||
const isAborted = error.message.includes("aborted");
|
||||
botMessage.content =
|
||||
"\n\n" +
|
||||
prettyObject({
|
||||
error: true,
|
||||
message: error.message,
|
||||
// make request
|
||||
api.llm.chat({
|
||||
messages: sendMessages,
|
||||
config: { ...modelConfig, stream: true },
|
||||
onUpdate(message) {
|
||||
botMessage.streaming = true;
|
||||
if (message) {
|
||||
botMessage.content = message;
|
||||
}
|
||||
get().updateCurrentSession((session) => {
|
||||
session.messages = session.messages.concat();
|
||||
});
|
||||
botMessage.streaming = false;
|
||||
userMessage.isError = !isAborted;
|
||||
botMessage.isError = !isAborted;
|
||||
get().updateCurrentSession((session) => {
|
||||
session.messages = session.messages.concat();
|
||||
});
|
||||
ChatControllerPool.remove(
|
||||
sessionIndex,
|
||||
botMessage.id ?? messageIndex,
|
||||
);
|
||||
},
|
||||
onFinish(message) {
|
||||
botMessage.streaming = false;
|
||||
if (message) {
|
||||
botMessage.content = message;
|
||||
get().onNewMessage(botMessage);
|
||||
resolve();
|
||||
}
|
||||
ChatControllerPool.remove(
|
||||
sessionIndex,
|
||||
botMessage.id ?? messageIndex,
|
||||
);
|
||||
},
|
||||
onError(error) {
|
||||
const isAborted = error.message.includes("aborted");
|
||||
botMessage.content =
|
||||
"\n\n" +
|
||||
prettyObject({
|
||||
error: true,
|
||||
message: error.message,
|
||||
});
|
||||
botMessage.streaming = false;
|
||||
userMessage.isError = !isAborted;
|
||||
botMessage.isError = !isAborted;
|
||||
get().updateCurrentSession((session) => {
|
||||
session.messages = session.messages.concat();
|
||||
});
|
||||
ChatControllerPool.remove(
|
||||
sessionIndex,
|
||||
botMessage.id ?? messageIndex,
|
||||
);
|
||||
|
||||
console.error("[Chat] failed ", error);
|
||||
},
|
||||
onController(controller) {
|
||||
// collect controller for stop/retry
|
||||
ChatControllerPool.addController(
|
||||
sessionIndex,
|
||||
botMessage.id ?? messageIndex,
|
||||
controller,
|
||||
);
|
||||
},
|
||||
console.error("[Chat] failed ", error);
|
||||
},
|
||||
onController(controller) {
|
||||
// collect controller for stop/retry
|
||||
ChatControllerPool.addController(
|
||||
sessionIndex,
|
||||
botMessage.id ?? messageIndex,
|
||||
controller,
|
||||
);
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@@ -420,7 +424,7 @@ export const useChatStore = create<ChatStore>()(
|
||||
modelConfig.sendMemory &&
|
||||
session.memoryPrompt &&
|
||||
session.memoryPrompt.length > 0 &&
|
||||
session.lastSummarizeIndex <= clearContextIndex;
|
||||
session.lastSummarizeIndex > clearContextIndex;
|
||||
const longTermMemoryPrompts = shouldSendLongTermMemory
|
||||
? [get().getMemoryPrompt()]
|
||||
: [];
|
||||
@@ -594,6 +598,62 @@ export const useChatStore = create<ChatStore>()(
|
||||
localStorage.clear();
|
||||
location.reload();
|
||||
},
|
||||
|
||||
getSuggestions() {
|
||||
return new Promise((resolve) => {
|
||||
// get last bot messages
|
||||
const messages = get().currentSession().messages;
|
||||
let lastBotMessage: ChatMessage | undefined = undefined;
|
||||
|
||||
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
||||
if (messages[i].role === "assistant") {
|
||||
lastBotMessage = messages[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const botMsg = lastBotMessage?.content;
|
||||
|
||||
if (!lastBotMessage || !botMsg) return resolve([]);
|
||||
|
||||
const prompt = `
|
||||
here is bot's reponse:
|
||||
'''
|
||||
${botMsg}
|
||||
'''
|
||||
|
||||
according to bot's reponse,
|
||||
- according to the bot's message, generate three short user input suggestions
|
||||
- detect the bot's language and response in detected language
|
||||
- no other words, just response in pure json format:
|
||||
{questions: string[]}";
|
||||
`;
|
||||
|
||||
api.llm.chat({
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: prompt,
|
||||
},
|
||||
],
|
||||
config: {
|
||||
model: "gpt-3.5-turbo",
|
||||
},
|
||||
onFinish(msg) {
|
||||
try {
|
||||
const msgJson = JSON.parse(msg) as {
|
||||
questions: string[];
|
||||
};
|
||||
if (Array.isArray(msgJson.questions)) {
|
||||
resolve(msgJson.questions);
|
||||
}
|
||||
} catch {
|
||||
console.error("[Suggestions] failed to parse: ", msg);
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: StoreKey.Chat,
|
||||
|
@@ -1,7 +1,10 @@
|
||||
import { create } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
import { LLMModel } from "../client/api";
|
||||
import { getClientConfig } from "../config/client";
|
||||
import { DEFAULT_INPUT_TEMPLATE, StoreKey } from "../constant";
|
||||
import { DEFAULT_INPUT_TEMPLATE, DEFAULT_MODELS, StoreKey } from "../constant";
|
||||
|
||||
export type ModelType = (typeof DEFAULT_MODELS)[number]["name"];
|
||||
|
||||
export enum SubmitKey {
|
||||
Enter = "Enter",
|
||||
@@ -29,10 +32,14 @@ export const DEFAULT_CONFIG = {
|
||||
disablePromptHint: false,
|
||||
|
||||
dontShowMaskSplashScreen: false, // dont show splash screen when create chat
|
||||
hideBuiltinMasks: false, // dont add builtin masks
|
||||
|
||||
models: DEFAULT_MODELS as any as LLMModel[],
|
||||
|
||||
modelConfig: {
|
||||
model: "gpt-3.5-turbo" as ModelType,
|
||||
temperature: 0.5,
|
||||
top_p: 1,
|
||||
max_tokens: 2000,
|
||||
presence_penalty: 0,
|
||||
frequency_penalty: 0,
|
||||
@@ -48,81 +55,11 @@ export type ChatConfig = typeof DEFAULT_CONFIG;
|
||||
export type ChatConfigStore = ChatConfig & {
|
||||
reset: () => void;
|
||||
update: (updater: (config: ChatConfig) => void) => void;
|
||||
mergeModels: (newModels: LLMModel[]) => void;
|
||||
};
|
||||
|
||||
export type ModelConfig = ChatConfig["modelConfig"];
|
||||
|
||||
const ENABLE_GPT4 = true;
|
||||
|
||||
export const ALL_MODELS = [
|
||||
{
|
||||
name: "gpt-4",
|
||||
available: ENABLE_GPT4,
|
||||
},
|
||||
{
|
||||
name: "gpt-4-0314",
|
||||
available: ENABLE_GPT4,
|
||||
},
|
||||
{
|
||||
name: "gpt-4-0613",
|
||||
available: ENABLE_GPT4,
|
||||
},
|
||||
{
|
||||
name: "gpt-4-32k",
|
||||
available: ENABLE_GPT4,
|
||||
},
|
||||
{
|
||||
name: "gpt-4-32k-0314",
|
||||
available: ENABLE_GPT4,
|
||||
},
|
||||
{
|
||||
name: "gpt-4-32k-0613",
|
||||
available: ENABLE_GPT4,
|
||||
},
|
||||
{
|
||||
name: "gpt-3.5-turbo",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
name: "gpt-3.5-turbo-0301",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
name: "gpt-3.5-turbo-0613",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
name: "gpt-3.5-turbo-16k",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
name: "gpt-3.5-turbo-16k-0613",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
name: "qwen-v1", // 通义千问
|
||||
available: false,
|
||||
},
|
||||
{
|
||||
name: "ernie", // 文心一言
|
||||
available: false,
|
||||
},
|
||||
{
|
||||
name: "spark", // 讯飞星火
|
||||
available: false,
|
||||
},
|
||||
{
|
||||
name: "llama", // llama
|
||||
available: false,
|
||||
},
|
||||
{
|
||||
name: "chatglm", // chatglm-6b
|
||||
available: false,
|
||||
},
|
||||
] as const;
|
||||
|
||||
export type ModelType = (typeof ALL_MODELS)[number]["name"];
|
||||
|
||||
export function limitNumber(
|
||||
x: number,
|
||||
min: number,
|
||||
@@ -137,7 +74,8 @@ export function limitNumber(
|
||||
}
|
||||
|
||||
export function limitModel(name: string) {
|
||||
return ALL_MODELS.some((m) => m.name === name && m.available)
|
||||
const allModels = useAppConfig.getState().models;
|
||||
return allModels.some((m) => m.name === name && m.available)
|
||||
? name
|
||||
: "gpt-3.5-turbo";
|
||||
}
|
||||
@@ -158,6 +96,9 @@ export const ModalConfigValidator = {
|
||||
temperature(x: number) {
|
||||
return limitNumber(x, 0, 1, 1);
|
||||
},
|
||||
top_p(x: number) {
|
||||
return limitNumber(x, 0, 1, 1);
|
||||
},
|
||||
};
|
||||
|
||||
export const useAppConfig = create<ChatConfigStore>()(
|
||||
@@ -174,22 +115,44 @@ export const useAppConfig = create<ChatConfigStore>()(
|
||||
updater(config);
|
||||
set(() => config);
|
||||
},
|
||||
|
||||
mergeModels(newModels) {
|
||||
const oldModels = get().models;
|
||||
const modelMap: Record<string, LLMModel> = {};
|
||||
|
||||
for (const model of oldModels) {
|
||||
model.available = false;
|
||||
modelMap[model.name] = model;
|
||||
}
|
||||
|
||||
for (const model of newModels) {
|
||||
model.available = true;
|
||||
modelMap[model.name] = model;
|
||||
}
|
||||
|
||||
set(() => ({
|
||||
models: Object.values(modelMap),
|
||||
}));
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: StoreKey.Config,
|
||||
version: 3.2,
|
||||
version: 3.4,
|
||||
migrate(persistedState, version) {
|
||||
if (version === 3.2) return persistedState as any;
|
||||
|
||||
const state = persistedState as ChatConfig;
|
||||
state.modelConfig.sendMemory = true;
|
||||
state.modelConfig.historyMessageCount = 4;
|
||||
state.modelConfig.compressMessageLengthThreshold = 1000;
|
||||
state.modelConfig.frequency_penalty = 0;
|
||||
state.modelConfig.template = DEFAULT_INPUT_TEMPLATE;
|
||||
state.dontShowMaskSplashScreen = false;
|
||||
|
||||
return state;
|
||||
if (version < 3.4) {
|
||||
state.modelConfig.sendMemory = true;
|
||||
state.modelConfig.historyMessageCount = 4;
|
||||
state.modelConfig.compressMessageLengthThreshold = 1000;
|
||||
state.modelConfig.frequency_penalty = 0;
|
||||
state.modelConfig.top_p = 1;
|
||||
state.modelConfig.template = DEFAULT_INPUT_TEMPLATE;
|
||||
state.dontShowMaskSplashScreen = false;
|
||||
state.hideBuiltinMasks = false;
|
||||
}
|
||||
|
||||
return state as any;
|
||||
},
|
||||
},
|
||||
),
|
||||
|
@@ -90,6 +90,7 @@ export const useMaskStore = create<MaskStore>()(
|
||||
(a, b) => b.id - a.id,
|
||||
);
|
||||
const config = useAppConfig.getState();
|
||||
if (config.hideBuiltinMasks) return userMasks;
|
||||
const buildinMasks = BUILTIN_MASKS.map(
|
||||
(m) =>
|
||||
({
|
||||
|
@@ -127,7 +127,7 @@ export const usePromptStore = create<PromptStore>()(
|
||||
search(text) {
|
||||
if (text.length === 0) {
|
||||
// return all rompts
|
||||
return SearchService.allPrompts.concat([...get().getUserPrompts()]);
|
||||
return get().getUserPrompts().concat(SearchService.builtinPrompts);
|
||||
}
|
||||
return SearchService.search(text) as Prompt[];
|
||||
},
|
||||
|
@@ -81,8 +81,7 @@ export const useUpdateStore = create<UpdateStore>()(
|
||||
|
||||
set(() => ({ version }));
|
||||
|
||||
const shouldCheck =
|
||||
Date.now() - get().lastUpdate > 24 * 60 * ONE_MINUTE;
|
||||
const shouldCheck = Date.now() - get().lastUpdate > 2 * 60 * ONE_MINUTE;
|
||||
if (!force && !shouldCheck) return;
|
||||
|
||||
set(() => ({
|
||||
|
12
docs/translation.md
Normal file
12
docs/translation.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# How to add a new translation?
|
||||
|
||||
Assume that we are adding a new translation for `new`.
|
||||
|
||||
1. copy `app/locales/en.ts` to `app/locales/new.ts`;
|
||||
2. edit `new.ts`, change `const en: LocaleType = ` to `const new: PartialLocaleType`, and `export default new;`;
|
||||
3. edit `app/locales/index.ts`:
|
||||
4. `import new from './new.ts'`;
|
||||
5. add `new` to `ALL_LANGS`;
|
||||
6. add `new: "new lang"` to `ALL_LANG_OPTIONS`;
|
||||
7. translate the strings in `new.ts`;
|
||||
8. submit a pull request, and the author will merge it.
|
13
package.json
13
package.json
@@ -7,8 +7,8 @@
|
||||
"build": "cross-env BUILD_MODE=standalone next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"export": "cross-env BUILD_MODE=export BUILD_APP=1 yarn build",
|
||||
"export:dev": "cross-env BUILD_MODE=export BUILD_APP=1 yarn dev",
|
||||
"export": "cross-env BUILD_MODE=export BUILD_APP=1 next build",
|
||||
"export:dev": "cross-env BUILD_MODE=export BUILD_APP=1 next dev",
|
||||
"app:dev": "yarn tauri dev",
|
||||
"app:build": "yarn tauri build",
|
||||
"prompts": "node ./scripts/fetch-prompts.mjs",
|
||||
@@ -17,13 +17,14 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortaine/fetch-event-source": "^3.0.6",
|
||||
"@hello-pangea/dnd": "^16.2.0",
|
||||
"@hello-pangea/dnd": "^16.3.0",
|
||||
"@svgr/webpack": "^6.5.1",
|
||||
"@vercel/analytics": "^0.1.11",
|
||||
"emoji-picker-react": "^4.4.7",
|
||||
"fuse.js": "^6.6.2",
|
||||
"html-to-image": "^1.11.11",
|
||||
"mermaid": "^10.2.3",
|
||||
"nanoid": "^4.0.2",
|
||||
"next": "^13.4.6",
|
||||
"node-fetch": "^3.3.1",
|
||||
"react": "^18.2.0",
|
||||
@@ -31,7 +32,7 @@
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-router-dom": "^6.10.0",
|
||||
"rehype-highlight": "^6.0.0",
|
||||
"rehype-katex": "^6.0.2",
|
||||
"rehype-katex": "^6.0.3",
|
||||
"remark-breaks": "^3.0.2",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-math": "^5.1.1",
|
||||
@@ -42,7 +43,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^1.3.1",
|
||||
"@types/node": "^20.3.1",
|
||||
"@types/node": "^20.3.3",
|
||||
"@types/react": "^18.2.12",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@types/react-katex": "^3.0.0",
|
||||
@@ -54,7 +55,7 @@
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"husky": "^8.0.0",
|
||||
"lint-staged": "^13.2.2",
|
||||
"prettier": "^2.8.7",
|
||||
"prettier": "^2.8.8",
|
||||
"typescript": "4.9.5",
|
||||
"webpack": "^5.88.1"
|
||||
},
|
||||
|
BIN
public/macos.png
Normal file
BIN
public/macos.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
Binary file not shown.
@@ -9,7 +9,7 @@
|
||||
},
|
||||
"package": {
|
||||
"productName": "ChatGPT Next Web",
|
||||
"version": "2.8.6"
|
||||
"version": "2.8.8"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
|
142
yarn.lock
142
yarn.lock
@@ -954,10 +954,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
|
||||
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
|
||||
|
||||
"@babel/runtime@^7.12.1", "@babel/runtime@^7.19.4", "@babel/runtime@^7.20.7", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
||||
version "7.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673"
|
||||
integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==
|
||||
"@babel/runtime@^7.12.1", "@babel/runtime@^7.20.7", "@babel/runtime@^7.22.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
||||
version "7.22.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.5.tgz#8564dd588182ce0047d55d7a75e93921107b57ec"
|
||||
integrity sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.11"
|
||||
|
||||
@@ -1037,17 +1037,17 @@
|
||||
resolved "https://registry.npmmirror.com/@fortaine/fetch-event-source/-/fetch-event-source-3.0.6.tgz#b8552a2ca2c5202f5699b93a92be0188d422b06e"
|
||||
integrity sha512-621GAuLMvKtyZQ3IA6nlDWhV1V/7PGOTNIGLUifxt0KzM+dZIweJ6F3XvQF3QnqeNfS1N7WQ0Kil1Di/lhChEw==
|
||||
|
||||
"@hello-pangea/dnd@^16.2.0":
|
||||
version "16.2.0"
|
||||
resolved "https://registry.npmmirror.com/@hello-pangea/dnd/-/dnd-16.2.0.tgz#58cbadeb56f8c7a381da696bb7aa3bfbb87876ec"
|
||||
integrity sha512-inACvMcvvLr34CG0P6+G/3bprVKhwswxjcsFUSJ+fpOGjhvDj9caiA9X3clby0lgJ6/ILIJjyedHZYECB7GAgA==
|
||||
"@hello-pangea/dnd@^16.3.0":
|
||||
version "16.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@hello-pangea/dnd/-/dnd-16.3.0.tgz#3776212f812df4e8e69c42831ec8ab7ff3a087d6"
|
||||
integrity sha512-RYQ/K8shtJoyNPvFWz0gfXIK7HF3P3mL9UZFGMuHB0ljRSXVgMjVFI/FxcZmakMzw6tO7NflWLriwTNBow/4vw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.19.4"
|
||||
"@babel/runtime" "^7.22.5"
|
||||
css-box-model "^1.2.1"
|
||||
memoize-one "^6.0.0"
|
||||
raf-schd "^4.0.3"
|
||||
react-redux "^8.0.4"
|
||||
redux "^4.2.0"
|
||||
react-redux "^8.1.1"
|
||||
redux "^4.2.1"
|
||||
use-memo-one "^1.1.3"
|
||||
|
||||
"@humanwhocodes/config-array@^0.11.8":
|
||||
@@ -1462,6 +1462,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/katex/-/katex-0.11.1.tgz#34de04477dcf79e2ef6c8d23b41a3d81f9ebeaf5"
|
||||
integrity sha512-DUlIj2nk0YnJdlWgsFuVKcX27MLW0KbKmGVoUHmFr+74FYYNUDAaj9ZqTADvsbE8rfxuVmSFc7KczYn5Y09ozg==
|
||||
|
||||
"@types/katex@^0.14.0":
|
||||
version "0.14.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/katex/-/katex-0.14.0.tgz#b84c0afc3218069a5ad64fe2a95321881021b5fe"
|
||||
integrity sha512-+2FW2CcT0K3P+JMR8YG846bmDwplKUTsWgT2ENwdQ1UdVfRk3GQrh6Mi4sTopy30gI8Uau5CEqHTDZ6YvWIUPA==
|
||||
|
||||
"@types/mdast@^3.0.0":
|
||||
version "3.0.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.11.tgz#dc130f7e7d9306124286f6d6cee40cf4d14a3dc0"
|
||||
@@ -1474,15 +1479,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197"
|
||||
integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==
|
||||
|
||||
"@types/node@*":
|
||||
version "20.3.2"
|
||||
resolved "https://registry.npmmirror.com/@types/node/-/node-20.3.2.tgz#fa6a90f2600e052a03c18b8cb3fd83dd4e599898"
|
||||
integrity sha512-vOBLVQeCQfIcF/2Y7eKFTqrMnizK5lRNQ7ykML/5RuwVXVWxYkgwS7xbt4B6fKCUPgbSL5FSsjHQpaGQP/dQmw==
|
||||
|
||||
"@types/node@^20.3.1":
|
||||
version "20.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.1.tgz#e8a83f1aa8b649377bb1fb5d7bac5cb90e784dfe"
|
||||
integrity sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==
|
||||
"@types/node@*", "@types/node@^20.3.3":
|
||||
version "20.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.3.tgz#329842940042d2b280897150e023e604d11657d6"
|
||||
integrity sha512-wheIYdr4NYML61AjC8MKj/2jrR/kDQri/CIpVoZwldwhnIrD/j9jIU5bJ8yBKuB2VhpFV7Ab6G2XkBjv9r9Zzw==
|
||||
|
||||
"@types/parse-json@^4.0.0":
|
||||
version "4.0.0"
|
||||
@@ -2156,7 +2156,7 @@ commander@^2.20.0:
|
||||
resolved "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
|
||||
commander@^8.0.0:
|
||||
commander@^8.0.0, commander@^8.3.0:
|
||||
version "8.3.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
|
||||
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
|
||||
@@ -3453,6 +3453,35 @@ has@^1.0.3:
|
||||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
|
||||
hast-util-from-dom@^4.0.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/hast-util-from-dom/-/hast-util-from-dom-4.2.0.tgz#25836ddecc3cc0849d32749c2a7aec03e94b59a7"
|
||||
integrity sha512-t1RJW/OpJbCAJQeKi3Qrj1cAOLA0+av/iPFori112+0X7R3wng+jxLA+kXec8K4szqPRGI8vPxbbpEYvvpwaeQ==
|
||||
dependencies:
|
||||
hastscript "^7.0.0"
|
||||
web-namespaces "^2.0.0"
|
||||
|
||||
hast-util-from-html-isomorphic@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-1.0.0.tgz#592b2bea880d476665b76ca1cf7d1a94925c80ec"
|
||||
integrity sha512-Yu480AKeOEN/+l5LA674a+7BmIvtDj24GvOt7MtQWuhzUwlaaRWdEPXAh3Qm5vhuthpAipFb2vTetKXWOjmTvw==
|
||||
dependencies:
|
||||
"@types/hast" "^2.0.0"
|
||||
hast-util-from-dom "^4.0.0"
|
||||
hast-util-from-html "^1.0.0"
|
||||
unist-util-remove-position "^4.0.0"
|
||||
|
||||
hast-util-from-html@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/hast-util-from-html/-/hast-util-from-html-1.0.2.tgz#2482fd701b2d8270b912b3909d6fb645d4a346cf"
|
||||
integrity sha512-LhrTA2gfCbLOGJq2u/asp4kwuG0y6NhWTXiPKP+n0qNukKy7hc10whqqCFfyvIA1Q5U5d0sp9HhNim9gglEH4A==
|
||||
dependencies:
|
||||
"@types/hast" "^2.0.0"
|
||||
hast-util-from-parse5 "^7.0.0"
|
||||
parse5 "^7.0.0"
|
||||
vfile "^5.0.0"
|
||||
vfile-message "^3.0.0"
|
||||
|
||||
hast-util-from-parse5@^7.0.0:
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz#aecfef73e3ceafdfa4550716443e4eb7b02e22b0"
|
||||
@@ -3898,12 +3927,12 @@ katex@^0.13.0:
|
||||
dependencies:
|
||||
commander "^8.0.0"
|
||||
|
||||
katex@^0.15.0:
|
||||
version "0.15.6"
|
||||
resolved "https://registry.yarnpkg.com/katex/-/katex-0.15.6.tgz#c4e2f6ced2ac4de1ef6f737fe7c67d3026baa0e5"
|
||||
integrity sha512-UpzJy4yrnqnhXvRPhjEuLA4lcPn6eRngixW7Q3TJErjg3Aw2PuLFBzTkdUb89UtumxjhHTqL3a5GDGETMSwgJA==
|
||||
katex@^0.16.0:
|
||||
version "0.16.8"
|
||||
resolved "https://registry.yarnpkg.com/katex/-/katex-0.16.8.tgz#89b453f40e8557f423f31a1009e9298dd99d5ceb"
|
||||
integrity sha512-ftuDnJbcbOckGY11OO+zg3OofESlbR5DRl2cmN8HeWeeFIV7wTXvAOx8kEjZjobhA+9wh2fbKeO6cdcA9Mnovg==
|
||||
dependencies:
|
||||
commander "^8.0.0"
|
||||
commander "^8.3.0"
|
||||
|
||||
khroma@^2.0.0:
|
||||
version "2.0.0"
|
||||
@@ -4610,6 +4639,11 @@ nanoid@^3.3.4:
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
|
||||
integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
|
||||
|
||||
nanoid@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.npmmirror.com/nanoid/-/nanoid-4.0.2.tgz#140b3c5003959adbebf521c170f282c5e7f9fb9e"
|
||||
integrity sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==
|
||||
|
||||
natural-compare@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||
@@ -4840,10 +4874,12 @@ parse-json@^5.0.0:
|
||||
json-parse-even-better-errors "^2.3.0"
|
||||
lines-and-columns "^1.1.6"
|
||||
|
||||
parse5@^6.0.0:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
|
||||
integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
|
||||
parse5@^7.0.0:
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32"
|
||||
integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==
|
||||
dependencies:
|
||||
entities "^4.4.0"
|
||||
|
||||
path-exists@^4.0.0:
|
||||
version "4.0.0"
|
||||
@@ -4911,10 +4947,10 @@ prettier-linter-helpers@^1.0.0:
|
||||
dependencies:
|
||||
fast-diff "^1.1.2"
|
||||
|
||||
prettier@^2.8.7:
|
||||
version "2.8.7"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.7.tgz#bb79fc8729308549d28fe3a98fce73d2c0656450"
|
||||
integrity sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==
|
||||
prettier@^2.8.8:
|
||||
version "2.8.8"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
|
||||
integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
|
||||
|
||||
prop-types@^15.0.0, prop-types@^15.8.1:
|
||||
version "15.8.1"
|
||||
@@ -4991,10 +5027,10 @@ react-markdown@^8.0.7:
|
||||
unist-util-visit "^4.0.0"
|
||||
vfile "^5.0.0"
|
||||
|
||||
react-redux@^8.0.4:
|
||||
version "8.0.5"
|
||||
resolved "https://registry.npmmirror.com/react-redux/-/react-redux-8.0.5.tgz#e5fb8331993a019b8aaf2e167a93d10af469c7bd"
|
||||
integrity sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==
|
||||
react-redux@^8.1.1:
|
||||
version "8.1.1"
|
||||
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.1.1.tgz#8e740f3fd864a4cd0de5ba9cdc8ad39cc9e7c81a"
|
||||
integrity sha512-5W0QaKtEhj+3bC0Nj0NkqkhIv8gLADH/2kYFMTHxCVqQILiWzLv6MaLuV5wJU3BQEdHKzTfcvPN0WMS6SC1oyA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.1"
|
||||
"@types/hoist-non-react-statics" "^3.3.1"
|
||||
@@ -5032,9 +5068,9 @@ readdirp@~3.6.0:
|
||||
dependencies:
|
||||
picomatch "^2.2.1"
|
||||
|
||||
redux@^4.2.0:
|
||||
redux@^4.2.1:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.npmmirror.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197"
|
||||
resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197"
|
||||
integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.9.2"
|
||||
@@ -5102,30 +5138,18 @@ rehype-highlight@^6.0.0:
|
||||
unified "^10.0.0"
|
||||
unist-util-visit "^4.0.0"
|
||||
|
||||
rehype-katex@^6.0.2:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/rehype-katex/-/rehype-katex-6.0.2.tgz#20197bbc10bdf79f6b999bffa6689d7f17226c35"
|
||||
integrity sha512-C4gDAlS1+l0hJqctyiU64f9CvT00S03qV1T6HiMzbSuLBgWUtcqydWHY9OpKrm0SpkK16FNd62CDKyWLwV2ppg==
|
||||
rehype-katex@^6.0.3:
|
||||
version "6.0.3"
|
||||
resolved "https://registry.yarnpkg.com/rehype-katex/-/rehype-katex-6.0.3.tgz#83e5b929b0967978e9491c02117f55be3594d7e1"
|
||||
integrity sha512-ByZlRwRUcWegNbF70CVRm2h/7xy7jQ3R9LaY4VVSvjnoVWwWVhNL60DiZsBpC5tSzYQOCvDbzncIpIjPZWodZA==
|
||||
dependencies:
|
||||
"@types/hast" "^2.0.0"
|
||||
"@types/katex" "^0.11.0"
|
||||
"@types/katex" "^0.14.0"
|
||||
hast-util-from-html-isomorphic "^1.0.0"
|
||||
hast-util-to-text "^3.1.0"
|
||||
katex "^0.15.0"
|
||||
rehype-parse "^8.0.0"
|
||||
unified "^10.0.0"
|
||||
unist-util-remove-position "^4.0.0"
|
||||
katex "^0.16.0"
|
||||
unist-util-visit "^4.0.0"
|
||||
|
||||
rehype-parse@^8.0.0:
|
||||
version "8.0.4"
|
||||
resolved "https://registry.yarnpkg.com/rehype-parse/-/rehype-parse-8.0.4.tgz#3d17c9ff16ddfef6bbcc8e6a25a99467b482d688"
|
||||
integrity sha512-MJJKONunHjoTh4kc3dsM1v3C9kGrrxvA3U8PxZlP2SjH8RNUSrb+lF7Y0KVaUDnGH2QZ5vAn7ulkiajM9ifuqg==
|
||||
dependencies:
|
||||
"@types/hast" "^2.0.0"
|
||||
hast-util-from-parse5 "^7.0.0"
|
||||
parse5 "^6.0.0"
|
||||
unified "^10.0.0"
|
||||
|
||||
remark-breaks@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/remark-breaks/-/remark-breaks-3.0.2.tgz#f466b9d3474d7323146c0149fc1496dabadd908e"
|
||||
|
Reference in New Issue
Block a user