This commit is contained in:
2023-10-25 11:34:02 +08:00
parent 717c76f4dd
commit 2e8e8e008c
7 changed files with 254 additions and 71 deletions

View File

@@ -5,6 +5,7 @@ import { calculate_token_length, Message } from "./chatgpt";
import getDefaultParams from "./getDefaultParam";
import ChatBOX from "./chatbox";
import models from "./models";
import { Tr, langCodeContext, LANG_OPTIONS } from "./translate";
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"
onClick={handleNewChatStore}
>
NEW
{Tr("NEW")}
</button>
<ul>
{allChatStoreIndexes
@@ -303,7 +304,7 @@ export function App() {
setAllChatStoreIndexes([...newAllChatStoreIndexes]);
}}
>
DEL
{Tr("DEL")}
</button>
</div>
<ChatBOX

View File

@@ -1,3 +1,4 @@
import { Tr, langCodeContext, LANG_OPTIONS } from "./translate";
import structuredClone from "@ungap/structured-clone";
import { createRef } from "preact";
import { StateUpdater, useEffect, useState } from "preact/hooks";
@@ -281,7 +282,7 @@ export default function ChatBOX(props: {
: chatStore.systemMessageContent}
</button>{" "}
<button className="underline">
{chatStore.streamMode ? "STREAM" : "FETCH"}
{chatStore.streamMode ? Tr("STREAM") : Tr("FETCH")}
</button>
</div>
<div className="text-xs">
@@ -293,14 +294,14 @@ export default function ChatBOX(props: {
</span>
</span>{" "}
<span>
Cut:{" "}
{Tr("Cut")}:{" "}
<span className="underline">
{chatStore.postBeginIndex}/
{chatStore.history.filter(({ hide }) => !hide).length}
</span>{" "}
</span>{" "}
<span>
Cost:{" "}
{Tr("Cost")}:{" "}
<span className="underline">${chatStore.cost.toFixed(4)}</span>
</span>
</div>
@@ -308,12 +309,12 @@ export default function ChatBOX(props: {
<div className="grow overflow-scroll">
{!chatStore.apiKey && (
<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>
)}
{!chatStore.apiEndpoint && (
<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>
)}
{templateAPIs.length > 0 &&
@@ -321,7 +322,7 @@ export default function ChatBOX(props: {
!chatStore.apiEndpoint ||
!chatStore.apiKey) && (
<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" />
<div className="flex flex-wrap">
{templateAPIs.map((t, index) => (
@@ -377,7 +378,7 @@ export default function ChatBOX(props: {
{templates.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">
<h2>Templates</h2>
<h2>{Tr("Saved prompt templates")}</h2>
<hr className="my-2" />
<div className="flex flex-wrap">
{templates.map((t, index) => (
@@ -439,21 +440,15 @@ export default function ChatBOX(props: {
)}
{chatStore.history.length === 0 && (
<p className="break-all opacity-60 p-6 rounded bg-white my-3 text-left dark:text-black">
<br />
Model: {chatStore.model}
<br />
<br />
NEW
<br />
使 ChatGPT API
API
<br />
<br />
:{" "}
{Tr("No chat history here")}
<br />{Tr("Model")}: {chatStore.model}
<br />{Tr("Click above to change the settings of this chat")}
<br />{Tr("Click the conor to create a new chat")}
<br />
{Tr(
"All chat history and settings are stored in the local browser"
)}
<br />{Tr("Documents and source code are avaliable here")}:{" "}
<a
className="underline"
href="https://github.com/heimoshuiyu/chatgpt-api-web"
@@ -473,7 +468,7 @@ export default function ChatBOX(props: {
))}
{showGenerating && (
<p className="p-2 my-2 animate-pulse dark:text-white message-content">
{generatingMessage || "生成中,最长可能需要一分钟,请保持网络稳定"}
{generatingMessage || Tr("Generating...")}
...
</p>
)}
@@ -495,7 +490,7 @@ export default function ChatBOX(props: {
await complete();
}}
>
Re-Generate
{Tr("Re-Generate")}
</button>
)}
{chatStore.develop_mode && chatStore.history.length > 0 && (
@@ -506,25 +501,29 @@ export default function ChatBOX(props: {
await complete();
}}
>
Completion
{Tr("Completion")}
</button>
)}
</p>
<p className="p-2 my-2 text-center opacity-50 dark:text-white">
{chatStore.responseModelName && (
<>Generated by {chatStore.responseModelName}</>
<>
{Tr("Generated by")} {chatStore.responseModelName}
</>
)}
{chatStore.postBeginIndex !== 0 && (
<>
<br />
{chatStore.postBeginIndex}
{Tr("Info: chat history is too long, forget messages")}:{" "}
{chatStore.postBeginIndex}
</>
)}
</p>
{chatStore.chatgpt_api_web_version < "v1.3.0" && (
<p className="p-2 my-2 text-center dark:text-white">
<br />
{chatStore.chatgpt_api_web_version}
{Tr("Warning: current chatStore version")}:{" "}
{chatStore.chatgpt_api_web_version} {"< v1.3.0"}
<br />
v1.3.0
使
@@ -535,8 +534,8 @@ export default function ChatBOX(props: {
{chatStore.chatgpt_api_web_version < "v1.4.0" && (
<p className="p-2 my-2 text-center dark:text-white">
<br />
{chatStore.chatgpt_api_web_version} {"< v1.4.0"}
{Tr("Warning: current chatStore version")}:{" "}
{chatStore.chatgpt_api_web_version} {"< v1.4.0"}
<br />
v1.4.0 使
<br />
@@ -546,7 +545,9 @@ export default function ChatBOX(props: {
{chatStore.chatgpt_api_web_version < "v1.6.0" && (
<p className="p-2 my-2 text-center dark:text-white">
<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 />
v1.6.0 apiKey apiEndpoint
@@ -564,7 +565,7 @@ export default function ChatBOX(props: {
await complete();
}}
>
Retry
{Tr("Retry")}
</button>
</p>
)}
@@ -594,7 +595,7 @@ export default function ChatBOX(props: {
send(inputMsg);
}}
>
Send
{Tr("Send")}
</button>
{chatStore.whisper_api &&
(chatStore.whisper_key || chatStore.apiKey) && (
@@ -721,7 +722,7 @@ export default function ChatBOX(props: {
setChatStore({ ...chatStore });
}}
>
Assistant
{Tr("Assistant")}
</button>
)}
{chatStore.develop_mode && (
@@ -741,7 +742,7 @@ export default function ChatBOX(props: {
setChatStore({ ...chatStore });
}}
>
User
{Tr("User")}
</button>
)}
</div>

View File

@@ -1,4 +1,16 @@
import { render } from 'preact'
import { App } from './app'
import { render } from "preact";
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);

View File

@@ -1,3 +1,4 @@
import { Tr, langCodeContext, LANG_OPTIONS } from "./translate";
import { useState, useEffect, StateUpdater } from "preact/hooks";
import { ChatStore, ChatStoreMessage } from "./app";
import { calculate_token_length } from "./chatgpt";
@@ -54,7 +55,7 @@ function EditMessage(props: EditMessageProps) {
setShowEdit(false);
}}
>
Close
{Tr("Close")}
</button>
</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"
}
>
Message Copied to clipboard!
{Tr("Message copied to clipboard!")}
</span>
);
@@ -212,13 +213,13 @@ export default function Message(props: Props) {
setChatStore({ ...chatStore });
}}
>
<label className="dark:text-white">example</label>
<label className="dark:text-white">{Tr("example")}</label>
<input type="checkbox" checked={chat.example} />
</span>
<span
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} />
</span>
</div>

View File

@@ -1,8 +1,9 @@
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 models from "./models";
import { TemplateChatStore } from "./chatbox";
import { tr, Tr, langCodeContext, LANG_OPTIONS } from "./translate";
const Help = (props: { children: any; help: string }) => {
return (
@@ -95,6 +96,43 @@ const Input = (props: {
</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: {
chatStore: ChatStore;
setChatStore: (cs: ChatStore) => void;
@@ -103,7 +141,6 @@ const Number = (props: {
| "maxTokens"
| "tokenMargin"
| "postBeginIndex"
| "temperature"
| "top_p"
| "presence_penalty"
| "frequency_penalty";
@@ -179,6 +216,8 @@ export default (props: {
const importFileRef = createRef();
const [totalCost, setTotalCost] = useState(getTotalCost());
// @ts-ignore
const { langCode, setLangCode } = useContext(langCodeContext);
useEffect(() => {
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"
>
<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 />
<div className="flex justify-between">
<button
className="p-2 m-2 rounded bg-purple-600 text-white"
onClick={() => {
navigator.clipboard.writeText(link);
alert(`Copied link: ${link}`);
alert(tr(`Copied link:`, langCode) + `${link}`);
}}
>
Copy Link
{Tr("Copy Setting Link")}
</button>
<button
className="p-2 m-2 rounded bg-rose-600 text-white"
onClick={() => {
if (
!confirm(
`Are you sure to clear all ${props.chatStore.history.length} messages?`
)
)
if (!confirm(tr("Are you sure to clear all history?", langCode)))
return;
props.chatStore.history = props.chatStore.history.filter(
(msg) => msg.example && !msg.hide
@@ -234,7 +285,7 @@ export default (props: {
props.setChatStore({ ...props.chatStore });
}}
>
Clear History
{Tr("Clear History")}
</button>
<button
className="p-2 m-2 rounded bg-cyan-600 text-white"
@@ -242,11 +293,11 @@ export default (props: {
props.setShow(false);
}}
>
Close
{Tr("Close")}
</button>
</div>
<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>
<hr />
<div className="box">
@@ -303,7 +354,7 @@ export default (props: {
readOnly={true}
{...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="presence_penalty"
@@ -329,7 +380,7 @@ export default (props: {
/>
<div className="flex justify-between">
<p className="m-2 p-2">
Accumulated cost in all sessions ${totalCost.toFixed(4)}
{Tr("Accumulated cost in all sessions")} ${totalCost.toFixed(4)}
</p>
<button
className="p-2 m-2 rounded bg-emerald-500"
@@ -338,7 +389,7 @@ export default (props: {
setTotalCost(getTotalCost());
}}
>
Reset
{Tr("Reset")}
</button>
</div>
<p className="flex justify-evenly">
@@ -361,14 +412,14 @@ export default (props: {
downloadAnchorNode.remove();
}}
>
Export
{Tr("Export")}
</button>
<button
className="p-2 m-2 rounded bg-amber-500"
onClick={() => {
const name = prompt("Give this template a name:");
const name = prompt(tr("Give this template a name:", langCode));
if (!name) {
alert("No template name specified");
alert(tr("No template name specified", langCode));
return;
}
const tmp: ChatStore = structuredClone(props.chatStore);
@@ -382,7 +433,7 @@ export default (props: {
props.setTemplates([...props.templates]);
}}
>
As template
{Tr("As template")}
</button>
<button
className="p-2 m-2 rounded bg-amber-500"
@@ -401,14 +452,17 @@ export default (props: {
props.setTemplateAPIs([...props.templateAPIs]);
}}
>
As API Template
{Tr("As API Template")}
</button>
<button
className="p-2 m-2 rounded bg-amber-500"
onClick={() => {
if (
!confirm(
"This will OVERWRITE the current chat history! Continue?"
tr(
"This will OVERWRITE the current chat history! Continue?",
langCode
)
)
)
return;
@@ -426,14 +480,14 @@ export default (props: {
const file = importFileRef.current.files[0];
console.log("file to import", file);
if (!file || file.type !== "application/json") {
alert("Please select a json file");
alert(tr("Please select a json file", langCode));
return;
}
const reader = new FileReader();
reader.onload = () => {
console.log("import content", reader.result);
if (!reader) {
alert("Empty file");
alert(tr("Empty file", langCode));
return;
}
try {
@@ -441,11 +495,16 @@ export default (props: {
reader.result as string
);
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 });
} catch (e) {
alert(`Import error on parsing json: ${e}`);
alert(
tr(`Import error on parsing json:`, langCode) + `${e}`
);
}
};
reader.readAsText(file);
@@ -453,7 +512,7 @@ export default (props: {
/>
</p>
<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}
</p>
</div>

51
src/translate/index.tsx Normal file
View 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
View 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;