i18n
This commit is contained in:
@@ -5,6 +5,7 @@ import { calculate_token_length, Message } from "./chatgpt";
|
|||||||
import getDefaultParams from "./getDefaultParam";
|
import getDefaultParams from "./getDefaultParam";
|
||||||
import ChatBOX from "./chatbox";
|
import ChatBOX from "./chatbox";
|
||||||
import models from "./models";
|
import models from "./models";
|
||||||
|
import { Tr, langCodeContext, LANG_OPTIONS } from "./translate";
|
||||||
|
|
||||||
import CHATGPT_API_WEB_VERSION from "./CHATGPT_API_WEB_VERSION";
|
import CHATGPT_API_WEB_VERSION from "./CHATGPT_API_WEB_VERSION";
|
||||||
|
|
||||||
@@ -245,7 +246,7 @@ export function App() {
|
|||||||
className="bg-violet-300 p-1 rounded hover:bg-violet-400"
|
className="bg-violet-300 p-1 rounded hover:bg-violet-400"
|
||||||
onClick={handleNewChatStore}
|
onClick={handleNewChatStore}
|
||||||
>
|
>
|
||||||
NEW
|
{Tr("NEW")}
|
||||||
</button>
|
</button>
|
||||||
<ul>
|
<ul>
|
||||||
{allChatStoreIndexes
|
{allChatStoreIndexes
|
||||||
@@ -303,7 +304,7 @@ export function App() {
|
|||||||
setAllChatStoreIndexes([...newAllChatStoreIndexes]);
|
setAllChatStoreIndexes([...newAllChatStoreIndexes]);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
DEL
|
{Tr("DEL")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<ChatBOX
|
<ChatBOX
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Tr, langCodeContext, LANG_OPTIONS } from "./translate";
|
||||||
import structuredClone from "@ungap/structured-clone";
|
import structuredClone from "@ungap/structured-clone";
|
||||||
import { createRef } from "preact";
|
import { createRef } from "preact";
|
||||||
import { StateUpdater, useEffect, useState } from "preact/hooks";
|
import { StateUpdater, useEffect, useState } from "preact/hooks";
|
||||||
@@ -281,7 +282,7 @@ export default function ChatBOX(props: {
|
|||||||
: chatStore.systemMessageContent}
|
: chatStore.systemMessageContent}
|
||||||
</button>{" "}
|
</button>{" "}
|
||||||
<button className="underline">
|
<button className="underline">
|
||||||
{chatStore.streamMode ? "STREAM" : "FETCH"}
|
{chatStore.streamMode ? Tr("STREAM") : Tr("FETCH")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs">
|
<div className="text-xs">
|
||||||
@@ -293,14 +294,14 @@ export default function ChatBOX(props: {
|
|||||||
</span>
|
</span>
|
||||||
</span>{" "}
|
</span>{" "}
|
||||||
<span>
|
<span>
|
||||||
Cut:{" "}
|
{Tr("Cut")}:{" "}
|
||||||
<span className="underline">
|
<span className="underline">
|
||||||
{chatStore.postBeginIndex}/
|
{chatStore.postBeginIndex}/
|
||||||
{chatStore.history.filter(({ hide }) => !hide).length}
|
{chatStore.history.filter(({ hide }) => !hide).length}
|
||||||
</span>{" "}
|
</span>{" "}
|
||||||
</span>{" "}
|
</span>{" "}
|
||||||
<span>
|
<span>
|
||||||
Cost:{" "}
|
{Tr("Cost")}:{" "}
|
||||||
<span className="underline">${chatStore.cost.toFixed(4)}</span>
|
<span className="underline">${chatStore.cost.toFixed(4)}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -308,12 +309,12 @@ export default function ChatBOX(props: {
|
|||||||
<div className="grow overflow-scroll">
|
<div className="grow overflow-scroll">
|
||||||
{!chatStore.apiKey && (
|
{!chatStore.apiKey && (
|
||||||
<p className="opacity-60 p-6 rounded bg-white my-3 text-left dark:text-black">
|
<p className="opacity-60 p-6 rounded bg-white my-3 text-left dark:text-black">
|
||||||
请先在上方设置 (OPENAI) API KEY
|
{Tr("Please click above to set")} (OpenAI) API KEY
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{!chatStore.apiEndpoint && (
|
{!chatStore.apiEndpoint && (
|
||||||
<p className="opacity-60 p-6 rounded bg-white my-3 text-left dark:text-black">
|
<p className="opacity-60 p-6 rounded bg-white my-3 text-left dark:text-black">
|
||||||
请先在上方设置 API Endpoint
|
{Tr("Please click above to set")} API Endpoint
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{templateAPIs.length > 0 &&
|
{templateAPIs.length > 0 &&
|
||||||
@@ -321,7 +322,7 @@ export default function ChatBOX(props: {
|
|||||||
!chatStore.apiEndpoint ||
|
!chatStore.apiEndpoint ||
|
||||||
!chatStore.apiKey) && (
|
!chatStore.apiKey) && (
|
||||||
<p className="break-all opacity-80 p-3 rounded bg-white my-3 text-left dark:text-black">
|
<p className="break-all opacity-80 p-3 rounded bg-white my-3 text-left dark:text-black">
|
||||||
<h2>已保存的 API 模板</h2>
|
<h2>{Tr("Saved API templates")}</h2>
|
||||||
<hr className="my-2" />
|
<hr className="my-2" />
|
||||||
<div className="flex flex-wrap">
|
<div className="flex flex-wrap">
|
||||||
{templateAPIs.map((t, index) => (
|
{templateAPIs.map((t, index) => (
|
||||||
@@ -377,7 +378,7 @@ export default function ChatBOX(props: {
|
|||||||
{templates.length > 0 &&
|
{templates.length > 0 &&
|
||||||
chatStore.history.filter((msg) => !msg.example).length == 0 && (
|
chatStore.history.filter((msg) => !msg.example).length == 0 && (
|
||||||
<p className="break-all opacity-80 p-3 rounded bg-white my-3 text-left dark:text-black">
|
<p className="break-all opacity-80 p-3 rounded bg-white my-3 text-left dark:text-black">
|
||||||
<h2>Templates</h2>
|
<h2>{Tr("Saved prompt templates")}</h2>
|
||||||
<hr className="my-2" />
|
<hr className="my-2" />
|
||||||
<div className="flex flex-wrap">
|
<div className="flex flex-wrap">
|
||||||
{templates.map((t, index) => (
|
{templates.map((t, index) => (
|
||||||
@@ -439,21 +440,15 @@ export default function ChatBOX(props: {
|
|||||||
)}
|
)}
|
||||||
{chatStore.history.length === 0 && (
|
{chatStore.history.length === 0 && (
|
||||||
<p className="break-all opacity-60 p-6 rounded bg-white my-3 text-left dark:text-black">
|
<p className="break-all opacity-60 p-6 rounded bg-white my-3 text-left dark:text-black">
|
||||||
暂无历史对话记录
|
{Tr("No chat history here")}
|
||||||
<br />
|
<br />⚙{Tr("Model")}: {chatStore.model}
|
||||||
⚙Model: {chatStore.model}
|
<br />⬆{Tr("Click above to change the settings of this chat")}
|
||||||
<br />
|
<br />↖{Tr("Click the conor to create a new chat")}
|
||||||
⬆点击上方更改此对话的参数(请勿泄漏)
|
<br />⚠
|
||||||
<br />
|
{Tr(
|
||||||
↖点击左上角 NEW 新建对话
|
"All chat history and settings are stored in the local browser"
|
||||||
<br />
|
)}
|
||||||
请注意,使用 ChatGPT API
|
<br />⚠{Tr("Documents and source code are avaliable here")}:{" "}
|
||||||
的生成文本质量和速度会受到会话上下文的影响,同时历史上下文过长会被裁切。API
|
|
||||||
会根据发送的上下文总量进行计费,因此建议您为不相关的问题或者不需要上文的问题创建新的对话,以避免不必要的计费。
|
|
||||||
<br />
|
|
||||||
⚠所有历史对话与参数储存在浏览器本地
|
|
||||||
<br />
|
|
||||||
⚠详细文档与源代码:{" "}
|
|
||||||
<a
|
<a
|
||||||
className="underline"
|
className="underline"
|
||||||
href="https://github.com/heimoshuiyu/chatgpt-api-web"
|
href="https://github.com/heimoshuiyu/chatgpt-api-web"
|
||||||
@@ -473,7 +468,7 @@ export default function ChatBOX(props: {
|
|||||||
))}
|
))}
|
||||||
{showGenerating && (
|
{showGenerating && (
|
||||||
<p className="p-2 my-2 animate-pulse dark:text-white message-content">
|
<p className="p-2 my-2 animate-pulse dark:text-white message-content">
|
||||||
{generatingMessage || "生成中,最长可能需要一分钟,请保持网络稳定"}
|
{generatingMessage || Tr("Generating...")}
|
||||||
...
|
...
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -495,7 +490,7 @@ export default function ChatBOX(props: {
|
|||||||
await complete();
|
await complete();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Re-Generate
|
{Tr("Re-Generate")}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{chatStore.develop_mode && chatStore.history.length > 0 && (
|
{chatStore.develop_mode && chatStore.history.length > 0 && (
|
||||||
@@ -506,25 +501,29 @@ export default function ChatBOX(props: {
|
|||||||
await complete();
|
await complete();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Completion
|
{Tr("Completion")}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<p className="p-2 my-2 text-center opacity-50 dark:text-white">
|
<p className="p-2 my-2 text-center opacity-50 dark:text-white">
|
||||||
{chatStore.responseModelName && (
|
{chatStore.responseModelName && (
|
||||||
<>Generated by {chatStore.responseModelName}</>
|
<>
|
||||||
|
{Tr("Generated by")} {chatStore.responseModelName}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{chatStore.postBeginIndex !== 0 && (
|
{chatStore.postBeginIndex !== 0 && (
|
||||||
<>
|
<>
|
||||||
<br />
|
<br />
|
||||||
提示:会话过长,已裁切前 {chatStore.postBeginIndex} 条消息
|
{Tr("Info: chat history is too long, forget messages")}:{" "}
|
||||||
|
{chatStore.postBeginIndex}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
{chatStore.chatgpt_api_web_version < "v1.3.0" && (
|
{chatStore.chatgpt_api_web_version < "v1.3.0" && (
|
||||||
<p className="p-2 my-2 text-center dark:text-white">
|
<p className="p-2 my-2 text-center dark:text-white">
|
||||||
<br />
|
<br />
|
||||||
提示:当前会话版本 {chatStore.chatgpt_api_web_version}。
|
{Tr("Warning: current chatStore version")}:{" "}
|
||||||
|
{chatStore.chatgpt_api_web_version} {"< v1.3.0"}
|
||||||
<br />
|
<br />
|
||||||
v1.3.0
|
v1.3.0
|
||||||
引入与旧版不兼容的消息裁切算法。继续使用旧版可能会导致消息裁切过多或过少(表现为失去上下文或输出不完整)。
|
引入与旧版不兼容的消息裁切算法。继续使用旧版可能会导致消息裁切过多或过少(表现为失去上下文或输出不完整)。
|
||||||
@@ -535,8 +534,8 @@ export default function ChatBOX(props: {
|
|||||||
{chatStore.chatgpt_api_web_version < "v1.4.0" && (
|
{chatStore.chatgpt_api_web_version < "v1.4.0" && (
|
||||||
<p className="p-2 my-2 text-center dark:text-white">
|
<p className="p-2 my-2 text-center dark:text-white">
|
||||||
<br />
|
<br />
|
||||||
提示:当前会话版本 {chatStore.chatgpt_api_web_version} {"< v1.4.0"}
|
{Tr("Warning: current chatStore version")}:{" "}
|
||||||
。
|
{chatStore.chatgpt_api_web_version} {"< v1.4.0"}
|
||||||
<br />
|
<br />
|
||||||
v1.4.0 增加了更多参数,继续使用旧版可能因参数确实导致未定义的行为
|
v1.4.0 增加了更多参数,继续使用旧版可能因参数确实导致未定义的行为
|
||||||
<br />
|
<br />
|
||||||
@@ -546,7 +545,9 @@ export default function ChatBOX(props: {
|
|||||||
{chatStore.chatgpt_api_web_version < "v1.6.0" && (
|
{chatStore.chatgpt_api_web_version < "v1.6.0" && (
|
||||||
<p className="p-2 my-2 text-center dark:text-white">
|
<p className="p-2 my-2 text-center dark:text-white">
|
||||||
<br />
|
<br />
|
||||||
提示:当前会话版本 {chatStore.chatgpt_api_web_version} {"< v1.6.0"}
|
提示:当前会话版本 {chatStore.chatgpt_api_web_version}
|
||||||
|
{Tr("Warning: current chatStore version")}:{" "}
|
||||||
|
{chatStore.chatgpt_api_web_version} {"< v1.6.0"}
|
||||||
。
|
。
|
||||||
<br />
|
<br />
|
||||||
v1.6.0 开始保存会话模板时会将 apiKey 和 apiEndpoint
|
v1.6.0 开始保存会话模板时会将 apiKey 和 apiEndpoint
|
||||||
@@ -564,7 +565,7 @@ export default function ChatBOX(props: {
|
|||||||
await complete();
|
await complete();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Retry
|
{Tr("Retry")}
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -594,7 +595,7 @@ export default function ChatBOX(props: {
|
|||||||
send(inputMsg);
|
send(inputMsg);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Send
|
{Tr("Send")}
|
||||||
</button>
|
</button>
|
||||||
{chatStore.whisper_api &&
|
{chatStore.whisper_api &&
|
||||||
(chatStore.whisper_key || chatStore.apiKey) && (
|
(chatStore.whisper_key || chatStore.apiKey) && (
|
||||||
@@ -721,7 +722,7 @@ export default function ChatBOX(props: {
|
|||||||
setChatStore({ ...chatStore });
|
setChatStore({ ...chatStore });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Assistant
|
{Tr("Assistant")}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{chatStore.develop_mode && (
|
{chatStore.develop_mode && (
|
||||||
@@ -741,7 +742,7 @@ export default function ChatBOX(props: {
|
|||||||
setChatStore({ ...chatStore });
|
setChatStore({ ...chatStore });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
User
|
{Tr("User")}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
18
src/main.tsx
18
src/main.tsx
@@ -1,4 +1,16 @@
|
|||||||
import { render } from 'preact'
|
import { render } from "preact";
|
||||||
import { App } from './app'
|
import { App } from "./app";
|
||||||
|
import { useState } from "preact/hooks";
|
||||||
|
import { Tr, langCodeContext, LANG_OPTIONS } from "./translate";
|
||||||
|
|
||||||
render(<App />, document.getElementById('app') as HTMLElement)
|
function Base() {
|
||||||
|
const [langCode, setLangCode] = useState("en-US");
|
||||||
|
return (
|
||||||
|
/* @ts-ignore */
|
||||||
|
<langCodeContext.Provider value={{ langCode, setLangCode }}>
|
||||||
|
<App />
|
||||||
|
</langCodeContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render(<Base />, document.getElementById("app") as HTMLElement);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Tr, langCodeContext, LANG_OPTIONS } from "./translate";
|
||||||
import { useState, useEffect, StateUpdater } from "preact/hooks";
|
import { useState, useEffect, StateUpdater } from "preact/hooks";
|
||||||
import { ChatStore, ChatStoreMessage } from "./app";
|
import { ChatStore, ChatStoreMessage } from "./app";
|
||||||
import { calculate_token_length } from "./chatgpt";
|
import { calculate_token_length } from "./chatgpt";
|
||||||
@@ -54,7 +55,7 @@ function EditMessage(props: EditMessageProps) {
|
|||||||
setShowEdit(false);
|
setShowEdit(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Close
|
{Tr("Close")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -101,7 +102,7 @@ export default function Message(props: Props) {
|
|||||||
"bg-purple-400 p-1 rounded shadow-md absolute z-20 left-1/2 top-3/4 transform -translate-x-1/2 -translate-y-1/2"
|
"bg-purple-400 p-1 rounded shadow-md absolute z-20 left-1/2 top-3/4 transform -translate-x-1/2 -translate-y-1/2"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Message Copied to clipboard!
|
{Tr("Message copied to clipboard!")}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -212,13 +213,13 @@ export default function Message(props: Props) {
|
|||||||
setChatStore({ ...chatStore });
|
setChatStore({ ...chatStore });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<label className="dark:text-white">example</label>
|
<label className="dark:text-white">{Tr("example")}</label>
|
||||||
<input type="checkbox" checked={chat.example} />
|
<input type="checkbox" checked={chat.example} />
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
onClick={(event: any) => setRenderWorkdown(!renderMarkdown)}
|
onClick={(event: any) => setRenderWorkdown(!renderMarkdown)}
|
||||||
>
|
>
|
||||||
<label className="dark:text-white">render</label>
|
<label className="dark:text-white">{Tr("render")}</label>
|
||||||
<input type="checkbox" checked={renderMarkdown} />
|
<input type="checkbox" checked={renderMarkdown} />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
113
src/settings.tsx
113
src/settings.tsx
@@ -1,8 +1,9 @@
|
|||||||
import { createRef } from "preact";
|
import { createRef } from "preact";
|
||||||
import { StateUpdater, useEffect, useState } from "preact/hooks";
|
import { StateUpdater, useContext, useEffect, useState } from "preact/hooks";
|
||||||
import { ChatStore, TemplateAPI, clearTotalCost, getTotalCost } from "./app";
|
import { ChatStore, TemplateAPI, clearTotalCost, getTotalCost } from "./app";
|
||||||
import models from "./models";
|
import models from "./models";
|
||||||
import { TemplateChatStore } from "./chatbox";
|
import { TemplateChatStore } from "./chatbox";
|
||||||
|
import { tr, Tr, langCodeContext, LANG_OPTIONS } from "./translate";
|
||||||
|
|
||||||
const Help = (props: { children: any; help: string }) => {
|
const Help = (props: { children: any; help: string }) => {
|
||||||
return (
|
return (
|
||||||
@@ -95,6 +96,43 @@ const Input = (props: {
|
|||||||
</Help>
|
</Help>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Slicer = (props: {
|
||||||
|
chatStore: ChatStore;
|
||||||
|
setChatStore: (cs: ChatStore) => void;
|
||||||
|
field: "temperature";
|
||||||
|
help: string;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Help help={props.help}>
|
||||||
|
<label className="m-2 p-2">{props.field}</label>
|
||||||
|
<span>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
max="1"
|
||||||
|
step="0.01"
|
||||||
|
value={props.chatStore[props.field]}
|
||||||
|
onChange={(event: any) => {
|
||||||
|
const value = parseFloat(event.target.value);
|
||||||
|
props.chatStore[props.field] = value;
|
||||||
|
props.setChatStore({ ...props.chatStore });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={props.chatStore[props.field]}
|
||||||
|
onChange={(event: any) => {
|
||||||
|
const value = parseFloat(event.target.value);
|
||||||
|
props.chatStore[props.field] = value;
|
||||||
|
props.setChatStore({ ...props.chatStore });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</Help>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const Number = (props: {
|
const Number = (props: {
|
||||||
chatStore: ChatStore;
|
chatStore: ChatStore;
|
||||||
setChatStore: (cs: ChatStore) => void;
|
setChatStore: (cs: ChatStore) => void;
|
||||||
@@ -103,7 +141,6 @@ const Number = (props: {
|
|||||||
| "maxTokens"
|
| "maxTokens"
|
||||||
| "tokenMargin"
|
| "tokenMargin"
|
||||||
| "postBeginIndex"
|
| "postBeginIndex"
|
||||||
| "temperature"
|
|
||||||
| "top_p"
|
| "top_p"
|
||||||
| "presence_penalty"
|
| "presence_penalty"
|
||||||
| "frequency_penalty";
|
| "frequency_penalty";
|
||||||
@@ -179,6 +216,8 @@ export default (props: {
|
|||||||
|
|
||||||
const importFileRef = createRef();
|
const importFileRef = createRef();
|
||||||
const [totalCost, setTotalCost] = useState(getTotalCost());
|
const [totalCost, setTotalCost] = useState(getTotalCost());
|
||||||
|
// @ts-ignore
|
||||||
|
const { langCode, setLangCode } = useContext(langCodeContext);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyPress = (event: any) => {
|
const handleKeyPress = (event: any) => {
|
||||||
@@ -206,26 +245,38 @@ export default (props: {
|
|||||||
}}
|
}}
|
||||||
className="m-2 p-2 bg-white rounded-lg h-fit lg:w-2/3 z-20"
|
className="m-2 p-2 bg-white rounded-lg h-fit lg:w-2/3 z-20"
|
||||||
>
|
>
|
||||||
<h3 className="text-xl text-center">Settings</h3>
|
<h3 className="text-xl text-center">
|
||||||
|
<span>{Tr("Settings")}</span>
|
||||||
|
<select>
|
||||||
|
{Object.keys(LANG_OPTIONS).map((opt) => (
|
||||||
|
<option
|
||||||
|
value={opt}
|
||||||
|
selected={opt === (langCodeContext as any).langCode}
|
||||||
|
onClick={(event: any) => {
|
||||||
|
console.log("set lang code", event.target.value);
|
||||||
|
setLangCode(event.target.value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{LANG_OPTIONS[opt].name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</h3>
|
||||||
<hr />
|
<hr />
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<button
|
<button
|
||||||
className="p-2 m-2 rounded bg-purple-600 text-white"
|
className="p-2 m-2 rounded bg-purple-600 text-white"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigator.clipboard.writeText(link);
|
navigator.clipboard.writeText(link);
|
||||||
alert(`Copied link: ${link}`);
|
alert(tr(`Copied link:`, langCode) + `${link}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Copy Link
|
{Tr("Copy Setting Link")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="p-2 m-2 rounded bg-rose-600 text-white"
|
className="p-2 m-2 rounded bg-rose-600 text-white"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (
|
if (!confirm(tr("Are you sure to clear all history?", langCode)))
|
||||||
!confirm(
|
|
||||||
`Are you sure to clear all ${props.chatStore.history.length} messages?`
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return;
|
return;
|
||||||
props.chatStore.history = props.chatStore.history.filter(
|
props.chatStore.history = props.chatStore.history.filter(
|
||||||
(msg) => msg.example && !msg.hide
|
(msg) => msg.example && !msg.hide
|
||||||
@@ -234,7 +285,7 @@ export default (props: {
|
|||||||
props.setChatStore({ ...props.chatStore });
|
props.setChatStore({ ...props.chatStore });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Clear History
|
{Tr("Clear History")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="p-2 m-2 rounded bg-cyan-600 text-white"
|
className="p-2 m-2 rounded bg-cyan-600 text-white"
|
||||||
@@ -242,11 +293,11 @@ export default (props: {
|
|||||||
props.setShow(false);
|
props.setShow(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Close
|
{Tr("Close")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p className="m-2 p-2">
|
<p className="m-2 p-2">
|
||||||
Total cost in this session ${props.chatStore.cost.toFixed(4)}
|
{Tr("Total cost in this session")} ${props.chatStore.cost.toFixed(4)}
|
||||||
</p>
|
</p>
|
||||||
<hr />
|
<hr />
|
||||||
<div className="box">
|
<div className="box">
|
||||||
@@ -303,7 +354,7 @@ export default (props: {
|
|||||||
readOnly={true}
|
readOnly={true}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
<Number field="temperature" help="温度" readOnly={false} {...props} />
|
<Slicer field="temperature" help="温度" {...props} />
|
||||||
<Number field="top_p" help="top_p" readOnly={false} {...props} />
|
<Number field="top_p" help="top_p" readOnly={false} {...props} />
|
||||||
<Number
|
<Number
|
||||||
field="presence_penalty"
|
field="presence_penalty"
|
||||||
@@ -329,7 +380,7 @@ export default (props: {
|
|||||||
/>
|
/>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<p className="m-2 p-2">
|
<p className="m-2 p-2">
|
||||||
Accumulated cost in all sessions ${totalCost.toFixed(4)}
|
{Tr("Accumulated cost in all sessions")} ${totalCost.toFixed(4)}
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
className="p-2 m-2 rounded bg-emerald-500"
|
className="p-2 m-2 rounded bg-emerald-500"
|
||||||
@@ -338,7 +389,7 @@ export default (props: {
|
|||||||
setTotalCost(getTotalCost());
|
setTotalCost(getTotalCost());
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Reset
|
{Tr("Reset")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p className="flex justify-evenly">
|
<p className="flex justify-evenly">
|
||||||
@@ -361,14 +412,14 @@ export default (props: {
|
|||||||
downloadAnchorNode.remove();
|
downloadAnchorNode.remove();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Export
|
{Tr("Export")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="p-2 m-2 rounded bg-amber-500"
|
className="p-2 m-2 rounded bg-amber-500"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const name = prompt("Give this template a name:");
|
const name = prompt(tr("Give this template a name:", langCode));
|
||||||
if (!name) {
|
if (!name) {
|
||||||
alert("No template name specified");
|
alert(tr("No template name specified", langCode));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const tmp: ChatStore = structuredClone(props.chatStore);
|
const tmp: ChatStore = structuredClone(props.chatStore);
|
||||||
@@ -382,7 +433,7 @@ export default (props: {
|
|||||||
props.setTemplates([...props.templates]);
|
props.setTemplates([...props.templates]);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
As template
|
{Tr("As template")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="p-2 m-2 rounded bg-amber-500"
|
className="p-2 m-2 rounded bg-amber-500"
|
||||||
@@ -401,14 +452,17 @@ export default (props: {
|
|||||||
props.setTemplateAPIs([...props.templateAPIs]);
|
props.setTemplateAPIs([...props.templateAPIs]);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
As API Template
|
{Tr("As API Template")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="p-2 m-2 rounded bg-amber-500"
|
className="p-2 m-2 rounded bg-amber-500"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (
|
if (
|
||||||
!confirm(
|
!confirm(
|
||||||
"This will OVERWRITE the current chat history! Continue?"
|
tr(
|
||||||
|
"This will OVERWRITE the current chat history! Continue?",
|
||||||
|
langCode
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
@@ -426,14 +480,14 @@ export default (props: {
|
|||||||
const file = importFileRef.current.files[0];
|
const file = importFileRef.current.files[0];
|
||||||
console.log("file to import", file);
|
console.log("file to import", file);
|
||||||
if (!file || file.type !== "application/json") {
|
if (!file || file.type !== "application/json") {
|
||||||
alert("Please select a json file");
|
alert(tr("Please select a json file", langCode));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = () => {
|
reader.onload = () => {
|
||||||
console.log("import content", reader.result);
|
console.log("import content", reader.result);
|
||||||
if (!reader) {
|
if (!reader) {
|
||||||
alert("Empty file");
|
alert(tr("Empty file", langCode));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -441,11 +495,16 @@ export default (props: {
|
|||||||
reader.result as string
|
reader.result as string
|
||||||
);
|
);
|
||||||
if (!newChatStore.chatgpt_api_web_version) {
|
if (!newChatStore.chatgpt_api_web_version) {
|
||||||
throw "This is not an exported chatgpt-api-web chatstore file. The key 'chatgpt_api_web_version' is missing!";
|
throw tr(
|
||||||
|
"This is not an exported chatgpt-api-web chatstore file. The key 'chatgpt_api_web_version' is missing!",
|
||||||
|
langCode
|
||||||
|
);
|
||||||
}
|
}
|
||||||
props.setChatStore({ ...newChatStore });
|
props.setChatStore({ ...newChatStore });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert(`Import error on parsing json: ${e}`);
|
alert(
|
||||||
|
tr(`Import error on parsing json:`, langCode) + `${e}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
reader.readAsText(file);
|
reader.readAsText(file);
|
||||||
@@ -453,7 +512,7 @@ export default (props: {
|
|||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
<p className="text-center m-2 p-2">
|
<p className="text-center m-2 p-2">
|
||||||
chatgpt-api-web ChatStore Version{" "}
|
chatgpt-api-web ChatStore {Tr("Version")}{" "}
|
||||||
{props.chatStore.chatgpt_api_web_version}
|
{props.chatStore.chatgpt_api_web_version}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
51
src/translate/index.tsx
Normal file
51
src/translate/index.tsx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { createContext } from "preact";
|
||||||
|
import MAP_zh_CN from "./zh_CN";
|
||||||
|
|
||||||
|
interface LangOption {
|
||||||
|
name: string;
|
||||||
|
langMap: Record<string, string>;
|
||||||
|
matches: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const LANG_OPTIONS: Record<string, LangOption> = {
|
||||||
|
"en-US": {
|
||||||
|
name: "English",
|
||||||
|
langMap: {},
|
||||||
|
matches: ["en-US", "en"],
|
||||||
|
},
|
||||||
|
"zh-CN": {
|
||||||
|
name: "中文(简体)",
|
||||||
|
langMap: MAP_zh_CN,
|
||||||
|
matches: ["zh-CN", "zh"],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const langCodeContext = createContext("en-US");
|
||||||
|
|
||||||
|
function tr(text: string, langCode: "en-US" | "zh-CN") {
|
||||||
|
const option = LANG_OPTIONS[langCode];
|
||||||
|
if (option === undefined) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
const langMap = LANG_OPTIONS[langCode].langMap;
|
||||||
|
|
||||||
|
const translatedText = langMap[text.toLowerCase()];
|
||||||
|
if (translatedText === undefined) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
return translatedText;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Tr(text: string) {
|
||||||
|
return (
|
||||||
|
<langCodeContext.Consumer>
|
||||||
|
{/* @ts-ignore */}
|
||||||
|
{({ langCode }) => {
|
||||||
|
return tr(text, langCode);
|
||||||
|
}}
|
||||||
|
</langCodeContext.Consumer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { tr, Tr, LANG_OPTIONS, langCodeContext };
|
||||||
58
src/translate/zh_CN.ts
Normal file
58
src/translate/zh_CN.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
const LANG_MAP: Record<string, string> = {
|
||||||
|
settings: "设置",
|
||||||
|
model: "模型",
|
||||||
|
"copy setting link": "复制设置链接",
|
||||||
|
"are you sure to clear all history?": "确定要清除所有历史记录吗?",
|
||||||
|
"clear history": "清除历史记录",
|
||||||
|
new: "新",
|
||||||
|
del: "删",
|
||||||
|
cut: "遗忘",
|
||||||
|
"please click above to set": "请点击上方进行设置",
|
||||||
|
cost: "消费",
|
||||||
|
stream: "流式返回",
|
||||||
|
fetch: "一次获取",
|
||||||
|
"saved api templates": "已保存的 API 模板",
|
||||||
|
"saved prompt templates": "已保存的提示模板",
|
||||||
|
"no chat history here": "暂无历史对话记录",
|
||||||
|
"click above to change the settings of this chat":
|
||||||
|
"点击上方更改此对话的参数(请勿泄漏)",
|
||||||
|
"click the NEW to create a new chat": "点击左上角 NEW 新建对话",
|
||||||
|
"all chat history and settings are stored in the local browser":
|
||||||
|
"所有历史对话与参数储存在浏览器本地",
|
||||||
|
"documents and source code are avaliable here":
|
||||||
|
"详细文档与源代码可在此处获取",
|
||||||
|
"generating...": "生成中,请保持网络稳定...",
|
||||||
|
"re-generate": "重新生成",
|
||||||
|
completion: "补全",
|
||||||
|
"generated by": "生成模型: ",
|
||||||
|
"info: chat history is too long, forget messages":
|
||||||
|
"提示:对话历史过长,遗忘消息数量",
|
||||||
|
"warning: current chatstore version": "警告:当前会话版本",
|
||||||
|
retry: "重试",
|
||||||
|
send: "发送",
|
||||||
|
assistant: "AI消息",
|
||||||
|
user: "用户消息",
|
||||||
|
close: "关闭",
|
||||||
|
"message copied to clipboard": "消息已复制到剪贴板",
|
||||||
|
"total cost in this session": "本次会话总消费",
|
||||||
|
"accumulated cost in all sessions": "所有会话总消费",
|
||||||
|
export: "导出",
|
||||||
|
"give this template a name:": "给此模板命名:",
|
||||||
|
"no template name specified": "未指定模板名称",
|
||||||
|
"as template": "保存为会话模板",
|
||||||
|
"as api template": "保存为 API 模板",
|
||||||
|
"this will overwrite the current chat history! continue?":
|
||||||
|
"此操作将覆盖当前会话历史!继续?",
|
||||||
|
"please select a json file": "请选择一个 JSON 文件",
|
||||||
|
"empty file": "警告: 空文件",
|
||||||
|
"this is not an exported chatgpt-api-web chatstore file. the key 'chatgpt_api_web_version' is missing!":
|
||||||
|
"此文件不是 chatgpt-api-web 导出的会话文件,缺少 chatgpt_api_web_version 键值!",
|
||||||
|
"import error on parsing json": "JSON 解析错误",
|
||||||
|
version: "版本",
|
||||||
|
"copied link:": "已复制链接:",
|
||||||
|
reset: "重置",
|
||||||
|
example: "示例",
|
||||||
|
render: "渲染",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LANG_MAP;
|
||||||
Reference in New Issue
Block a user