reconstitution ui with daisyui
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html data-theme="cupcake" lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta
|
||||
|
||||
2268
package-lock.json
generated
Normal file
2268
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,7 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@heroicons/react": "^2.1.5",
|
||||
"@types/ungap__structured-clone": "^0.3.1",
|
||||
"@ungap/structured-clone": "^1.2.0",
|
||||
"autoprefixer": "^10.4.16",
|
||||
@@ -21,6 +22,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@preact/preset-vite": "^2.6.0",
|
||||
"daisyui": "^4.12.10",
|
||||
"theme-change": "^2.5.0",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^4.5.0"
|
||||
}
|
||||
|
||||
31
src/app.tsx
31
src/app.tsx
@@ -228,7 +228,9 @@ export function App() {
|
||||
|
||||
if (oldVersion < 11) {
|
||||
if (oldVersion < 11 && oldVersion >= 1) {
|
||||
alert("Start upgrading storage, just a sec... (Click OK to continue)");
|
||||
alert(
|
||||
"Start upgrading storage, just a sec... (Click OK to continue)"
|
||||
);
|
||||
}
|
||||
if (
|
||||
transaction
|
||||
@@ -401,16 +403,16 @@ export function App() {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex text-sm h-full bg-slate-200 dark:bg-slate-800 dark:text-white">
|
||||
<div className="flex flex-col h-full p-2 border-r-indigo-500 border-2 dark:border-slate-800 dark:border-r-indigo-500 dark:text-black">
|
||||
<div className="flex text-sm h-full">
|
||||
<div className="flex flex-col h-full p-2 bg-primary">
|
||||
<div className="grow overflow-scroll">
|
||||
<button
|
||||
className="w-full bg-violet-300 p-1 rounded hover:bg-violet-400"
|
||||
className="btn btn-sm btn-info p-1 my-1 w-full"
|
||||
onClick={handleNewChatStore}
|
||||
>
|
||||
{Tr("NEW")}
|
||||
</button>
|
||||
<ul>
|
||||
<ul class="pt-2">
|
||||
{(allChatStoreIndexes as number[])
|
||||
.slice()
|
||||
.reverse()
|
||||
@@ -419,8 +421,8 @@ export function App() {
|
||||
return (
|
||||
<li>
|
||||
<button
|
||||
className={`w-full my-1 p-1 rounded hover:bg-blue-500 ${
|
||||
i === selectedChatIndex ? "bg-blue-500" : "bg-blue-200"
|
||||
className={`w-full my-1 p-1 btn btn-sm ${
|
||||
i === selectedChatIndex ? "btn-accent" : "btn-secondary"
|
||||
}`}
|
||||
onClick={() => {
|
||||
setSelectedChatIndex(i);
|
||||
@@ -433,12 +435,18 @@ export function App() {
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
className="rounded bg-rose-400 p-1 my-1 w-full"
|
||||
className="btn btn-warning btn-sm p-1 my-1 w-full"
|
||||
onClick={async () => {
|
||||
if (!confirm("Are you sure you want to delete this chat history?"))
|
||||
if (
|
||||
!confirm("Are you sure you want to delete this chat history?")
|
||||
)
|
||||
return;
|
||||
console.log("remove item", `${STORAGE_NAME}-${selectedChatIndex}`);
|
||||
console.log(
|
||||
"remove item",
|
||||
`${STORAGE_NAME}-${selectedChatIndex}`
|
||||
);
|
||||
(await db).delete(STORAGE_NAME, selectedChatIndex);
|
||||
const newAllChatStoreIndexes = await (
|
||||
await db
|
||||
@@ -461,7 +469,7 @@ export function App() {
|
||||
</button>
|
||||
{chatStore.develop_mode && (
|
||||
<button
|
||||
className="rounded bg-rose-800 p-1 my-1 w-full text-white"
|
||||
className="btn btn-sm btn-warning p-1 my-1 w-full"
|
||||
onClick={async () => {
|
||||
if (
|
||||
!confirm(
|
||||
@@ -480,6 +488,7 @@ export function App() {
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<ChatBOX
|
||||
db={db}
|
||||
chatStore={chatStore}
|
||||
|
||||
@@ -34,6 +34,7 @@ import { ListToolsTempaltes } from "./listToolsTemplates";
|
||||
import { autoHeight } from "./textarea";
|
||||
import Search from "./search";
|
||||
import { IDBPDatabase } from "idb";
|
||||
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
|
||||
|
||||
export interface TemplateChatStore extends ChatStore {
|
||||
name: string;
|
||||
@@ -434,7 +435,7 @@ export default function ChatBOX(props: {
|
||||
const userInputRef = createRef();
|
||||
|
||||
return (
|
||||
<div className="grow flex flex-col p-2 dark:text-black">
|
||||
<div className="grow flex flex-col p-2">
|
||||
{showSettings && (
|
||||
<Settings
|
||||
chatStore={chatStore}
|
||||
@@ -464,11 +465,11 @@ export default function ChatBOX(props: {
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className="relative cursor-pointer rounded bg-cyan-300 dark:text-white p-1 dark:bg-cyan-800"
|
||||
className="relative cursor-pointer rounded p-2 bg-base-200"
|
||||
onClick={() => setShowSettings(true)}
|
||||
>
|
||||
<button
|
||||
className="absolute right-1 bg-gray-300 rounded p-1 m-1"
|
||||
className="absolute right-1 rounded p-1 m-1"
|
||||
onClick={(event) => {
|
||||
// stop propagation to parent
|
||||
event.stopPropagation();
|
||||
@@ -476,7 +477,7 @@ export default function ChatBOX(props: {
|
||||
setShowSearch(true);
|
||||
}}
|
||||
>
|
||||
🔍
|
||||
<MagnifyingGlassIcon class="w-5 h-5" />
|
||||
</button>
|
||||
<div>
|
||||
<button className="underline">
|
||||
@@ -514,12 +515,12 @@ export default function ChatBOX(props: {
|
||||
</div>
|
||||
<div className="grow overflow-scroll">
|
||||
{!chatStore.apiKey && (
|
||||
<p className="opacity-60 p-6 rounded bg-white my-3 text-left dark:text-black">
|
||||
<p className="bg-base-200 p-6 rounded my-3 text-left">
|
||||
{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">
|
||||
<p className="bg-base-200 p-6 rounded my-3 text-left">
|
||||
{Tr("Please click above to set")} API Endpoint
|
||||
</p>
|
||||
)}
|
||||
@@ -581,7 +582,7 @@ export default function ChatBOX(props: {
|
||||
)}
|
||||
|
||||
{chatStore.history.filter((msg) => !msg.example).length == 0 && (
|
||||
<div className="break-all opacity-80 p-3 rounded bg-white my-3 text-left dark:text-black">
|
||||
<div className="bg-base-200 break-all p-3 my-3 text-left">
|
||||
<h2>
|
||||
<span>{Tr("Saved prompt templates")}</span>
|
||||
<button
|
||||
@@ -596,7 +597,7 @@ export default function ChatBOX(props: {
|
||||
{Tr("Reset Current")}
|
||||
</button>
|
||||
</h2>
|
||||
<hr className="my-2" />
|
||||
<div class="divider"></div>
|
||||
<div className="flex flex-wrap">
|
||||
{templates.map((t, index) => (
|
||||
<div
|
||||
@@ -729,7 +730,7 @@ export default function ChatBOX(props: {
|
||||
<p className="text-center">
|
||||
{chatStore.history.length > 0 && (
|
||||
<button
|
||||
className="disabled:line-through disabled:bg-slate-500 rounded m-2 p-2 border-2 bg-teal-500 hover:bg-teal-600"
|
||||
className="btn btn-warning disabled:line-through disabled:btn-neutral disabled:text-white m-2 p-2"
|
||||
disabled={showGenerating}
|
||||
onClick={async () => {
|
||||
const messageIndex = chatStore.history.length - 1;
|
||||
@@ -749,7 +750,7 @@ export default function ChatBOX(props: {
|
||||
)}
|
||||
{chatStore.develop_mode && chatStore.history.length > 0 && (
|
||||
<button
|
||||
className="disabled:line-through disabled:bg-slate-500 rounded m-2 p-2 border-2 bg-yellow-500 hover:bg-yellow-600"
|
||||
className="btn btn-warning disabled:line-through disabled:bg-neural m-2 p-2"
|
||||
disabled={showGenerating}
|
||||
onClick={async () => {
|
||||
await complete();
|
||||
@@ -852,15 +853,16 @@ export default function ChatBOX(props: {
|
||||
<input type="checkbox" checked={follow} />
|
||||
</span>
|
||||
)}
|
||||
|
||||
<div className="flex justify-between">
|
||||
<button
|
||||
className="disabled:line-through disabled:bg-slate-500 rounded m-1 p-1 border-2 bg-cyan-400 hover:bg-cyan-600"
|
||||
className="btn btn-primary disabled:line-through disabled:text-white disabled:bg-neutral m-1 p-1"
|
||||
disabled={showGenerating || !chatStore.apiKey}
|
||||
onClick={() => {
|
||||
setShowAddImage(!showAddImage);
|
||||
}}
|
||||
>
|
||||
Img
|
||||
Image
|
||||
</button>
|
||||
{showAddImage && (
|
||||
<AddImage
|
||||
@@ -891,11 +893,11 @@ export default function ChatBOX(props: {
|
||||
autoHeight(event.target);
|
||||
setInputMsg(event.target.value);
|
||||
}}
|
||||
className="rounded grow m-1 p-1 border-2 border-gray-400 w-0"
|
||||
className="textarea textarea-bordered textarea-xs grow m-1 p-1 w-0"
|
||||
placeholder="Type here..."
|
||||
></textarea>
|
||||
<button
|
||||
className="disabled:line-through disabled:bg-slate-500 rounded m-1 p-1 border-2 bg-cyan-400 hover:bg-cyan-600"
|
||||
className="btn btn-primary disabled:btn-neutral disabled:line-through m-1 p-1"
|
||||
disabled={showGenerating}
|
||||
onClick={() => {
|
||||
send(inputMsg, true);
|
||||
@@ -909,10 +911,8 @@ export default function ChatBOX(props: {
|
||||
chatStore.whisper_key &&
|
||||
(chatStore.whisper_key || chatStore.apiKey) && (
|
||||
<button
|
||||
className={`disabled:line-through disabled:bg-slate-500 rounded m-1 p-1 border-2 ${
|
||||
isRecording === "Recording"
|
||||
? "bg-red-400 hover:bg-red-600"
|
||||
: "bg-cyan-400 hover:bg-cyan-600"
|
||||
className={`btn disabled:line-through disabled:btn-neutral disabled:text-white m-1 p-1 ${
|
||||
isRecording === "Recording" ? "btn-error" : "btn-success"
|
||||
} ${isRecording !== "Mic" ? "animate-pulse" : ""}`}
|
||||
disabled={isRecording === "Transcribing"}
|
||||
ref={mediaRef}
|
||||
@@ -1027,7 +1027,7 @@ export default function ChatBOX(props: {
|
||||
)}
|
||||
{chatStore.develop_mode && (
|
||||
<button
|
||||
className="disabled:line-through disabled:bg-slate-500 rounded m-1 p-1 border-2 bg-cyan-400 hover:bg-cyan-600"
|
||||
className="btn disabled:line-through disabled:btn-neutral disabled:text-white m-1 p-1"
|
||||
disabled={showGenerating || !chatStore.apiKey}
|
||||
onClick={() => {
|
||||
chatStore.history.push({
|
||||
@@ -1051,7 +1051,7 @@ export default function ChatBOX(props: {
|
||||
)}
|
||||
{chatStore.develop_mode && (
|
||||
<button
|
||||
className="disabled:line-through disabled:bg-slate-500 rounded m-1 p-1 border-2 bg-cyan-400 hover:bg-cyan-600"
|
||||
className="btn disabled:line-through disabled:btn-neutral disabled:text-white m-1 p-1"
|
||||
disabled={showGenerating || !chatStore.apiKey}
|
||||
onClick={() => {
|
||||
send(inputMsg, false);
|
||||
@@ -1062,7 +1062,7 @@ export default function ChatBOX(props: {
|
||||
)}
|
||||
{chatStore.develop_mode && (
|
||||
<button
|
||||
className="disabled:line-through disabled:bg-slate-500 rounded m-1 p-1 border-2 bg-cyan-400 hover:bg-cyan-600"
|
||||
className="btn disabled:line-through disabled:btn-neutral disabled:text-white m-1 p-1"
|
||||
disabled={showGenerating || !chatStore.apiKey}
|
||||
onClick={() => {
|
||||
setShowAddToolMsg(true);
|
||||
|
||||
19
src/main.tsx
19
src/main.tsx
@@ -3,25 +3,28 @@ import { App } from "./app";
|
||||
import { useState, useEffect } from "preact/hooks";
|
||||
import { Tr, langCodeContext, LANG_OPTIONS } from "./translate";
|
||||
|
||||
import { themeChange } from "theme-change";
|
||||
|
||||
function Base() {
|
||||
const [langCode, _setLangCode] = useState("en-US");
|
||||
|
||||
const setLangCode = (langCode: string) => {
|
||||
_setLangCode(langCode)
|
||||
if (!localStorage) return
|
||||
_setLangCode(langCode);
|
||||
if (!localStorage) return;
|
||||
|
||||
localStorage.setItem('chatgpt-api-web-lang', langCode)
|
||||
}
|
||||
localStorage.setItem("chatgpt-api-web-lang", langCode);
|
||||
};
|
||||
|
||||
// select language
|
||||
useEffect(() => {
|
||||
themeChange(false);
|
||||
// query localStorage
|
||||
if (localStorage) {
|
||||
const lang = localStorage.getItem('chatgpt-api-web-lang')
|
||||
const lang = localStorage.getItem("chatgpt-api-web-lang");
|
||||
if (lang) {
|
||||
console.log(`query langCode ${lang} from localStorage`)
|
||||
_setLangCode(lang)
|
||||
return
|
||||
console.log(`query langCode ${lang} from localStorage`);
|
||||
_setLangCode(lang);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
482
src/settings.tsx
482
src/settings.tsx
@@ -16,6 +16,14 @@ import { SetAPIsTemplate } from "./setAPIsTemplate";
|
||||
import { autoHeight } from "./textarea";
|
||||
import getDefaultParams from "./getDefaultParam";
|
||||
|
||||
import {
|
||||
InformationCircleIcon,
|
||||
CheckIcon,
|
||||
NoSymbolIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
|
||||
import { themeChange } from "theme-change";
|
||||
|
||||
const TTS_VOICES: string[] = [
|
||||
"alloy",
|
||||
"echo",
|
||||
@@ -29,14 +37,6 @@ const TTS_FORMAT: string[] = ["mp3", "opus", "aac", "flac"];
|
||||
const Help = (props: { children: any; help: string }) => {
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
className="absolute"
|
||||
onClick={() => {
|
||||
alert(props.help);
|
||||
}}
|
||||
>
|
||||
❓
|
||||
</button>
|
||||
<p className="flex justify-between">{props.children}</p>
|
||||
</div>
|
||||
);
|
||||
@@ -103,12 +103,25 @@ const LongInput = (props: {
|
||||
chatStore: ChatStore;
|
||||
setChatStore: (cs: ChatStore) => void;
|
||||
field: "systemMessageContent" | "toolsString";
|
||||
label: string;
|
||||
help: string;
|
||||
}) => {
|
||||
return (
|
||||
<Help help={props.help}>
|
||||
<label class="form-control">
|
||||
<div class="label">
|
||||
<span class="label-text">{props.label}</span>
|
||||
<span class="label-text-alt">
|
||||
<button
|
||||
onClick={() => {
|
||||
alert(props.help);
|
||||
}}
|
||||
>
|
||||
<InformationCircleIcon className="w-4 h-4" />
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<textarea
|
||||
className="m-2 p-2 border rounded focus w-full"
|
||||
className="textarea textarea-bordered h-24 w-full"
|
||||
value={props.chatStore[props.field]}
|
||||
onChange={(event: any) => {
|
||||
props.chatStore[props.field] = event.target.value;
|
||||
@@ -119,7 +132,7 @@ const LongInput = (props: {
|
||||
autoHeight(event.target);
|
||||
}}
|
||||
></textarea>
|
||||
</Help>
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -142,7 +155,6 @@ const Input = (props: {
|
||||
<Help help={props.help}>
|
||||
<label className="m-2 p-2">{props.field}</label>
|
||||
<button
|
||||
className="p-2"
|
||||
onClick={() => {
|
||||
setHideInput(!hideInput);
|
||||
console.log("clicked", hideInput);
|
||||
@@ -343,6 +355,7 @@ export default (props: {
|
||||
const { langCode, setLangCode } = useContext(langCodeContext);
|
||||
|
||||
useEffect(() => {
|
||||
themeChange(false);
|
||||
const handleKeyPress = (event: any) => {
|
||||
if (event.keyCode === 27) {
|
||||
// keyCode for ESC key is 27
|
||||
@@ -360,17 +373,133 @@ export default (props: {
|
||||
return (
|
||||
<div
|
||||
onClick={() => props.setShow(false)}
|
||||
className="left-0 top-0 overflow-scroll flex justify-center absolute w-screen h-full bg-black bg-opacity-50 z-10"
|
||||
className="left-0 top-0 overflow-scroll flex justify-center absolute mt-6 w-screen h-full z-10"
|
||||
>
|
||||
<div
|
||||
onClick={(event: any) => {
|
||||
event.stopPropagation();
|
||||
}}
|
||||
className="m-2 p-2 bg-white rounded-lg h-fit lg:w-2/3 z-20"
|
||||
className="px-6 pt-2 pb-4 rounded-lg h-fit lg:w-2/3 z-20 shadow-2xl bg-base-200"
|
||||
>
|
||||
<h3 className="text-xl text-center flex justify-between">
|
||||
<div className="flex justify-between items-center">
|
||||
<h3 className="text-xl">
|
||||
<span>{Tr("Settings")}</span>
|
||||
<select>
|
||||
</h3>
|
||||
<button
|
||||
className="btn"
|
||||
onClick={() => {
|
||||
props.setShow(false);
|
||||
}}
|
||||
>
|
||||
{Tr("Close")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<hr className="pt-2" />
|
||||
<div role="tablist" class="tabs tabs-bordered pt-2 w-full">
|
||||
<input
|
||||
type="radio"
|
||||
name="setting_tab"
|
||||
role="tab"
|
||||
class="tab"
|
||||
aria-label="Session"
|
||||
/>
|
||||
<div
|
||||
role="tabpanel"
|
||||
class="tab-content bg-base-100 border-base-300 rounded-box p-6"
|
||||
>
|
||||
<p>
|
||||
{Tr("Total cost in this session")} $
|
||||
{props.chatStore.cost.toFixed(4)}
|
||||
</p>
|
||||
<LongInput
|
||||
label="System Prompt"
|
||||
field="systemMessageContent"
|
||||
help="系统消息,用于指示ChatGPT的角色和一些前置条件,例如“你是一个有帮助的人工智能助理”,或者“你是一个专业英语翻译,把我的话全部翻译成英语”,详情参考 OPEAN AI API 文档"
|
||||
{...props}
|
||||
/>
|
||||
|
||||
<LongInput
|
||||
label="Tools String"
|
||||
field="toolsString"
|
||||
help="function call tools, should be valid json format in list"
|
||||
{...props}
|
||||
/>
|
||||
<span className="pt-1">
|
||||
JSON Check:{" "}
|
||||
{isVailedJSON(props.chatStore.toolsString) ? (
|
||||
<CheckIcon className="inline w-4 h-4" />
|
||||
) : (
|
||||
<NoSymbolIcon className="inline w-4 h-4" />
|
||||
)}
|
||||
</span>
|
||||
<div className="box">
|
||||
<div className="flex justify-evenly flex-wrap">
|
||||
{props.chatStore.toolsString.trim() && (
|
||||
<button
|
||||
className="btn"
|
||||
onClick={() => {
|
||||
const name = prompt(
|
||||
`Give this **Tools** template a name:`
|
||||
);
|
||||
if (!name) {
|
||||
alert("No template name specified");
|
||||
return;
|
||||
}
|
||||
const newToolsTmp: TemplateTools = {
|
||||
name,
|
||||
toolsString: props.chatStore.toolsString,
|
||||
};
|
||||
props.templateTools.push(newToolsTmp);
|
||||
props.setTemplateTools([...props.templateTools]);
|
||||
}}
|
||||
>
|
||||
{Tr(`Save Tools`)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
type="radio"
|
||||
name="setting_tab"
|
||||
role="tab"
|
||||
class="tab"
|
||||
aria-label="System"
|
||||
/>
|
||||
<div
|
||||
role="tabpanel"
|
||||
class="tab-content bg-base-100 border-base-300 rounded-box p-6"
|
||||
>
|
||||
<div className="flex justify-between">
|
||||
<p>
|
||||
{Tr("Accumulated cost in all sessions")} ${totalCost.toFixed(4)}
|
||||
</p>
|
||||
<button
|
||||
onClick={() => {
|
||||
clearTotalCost();
|
||||
setTotalCost(getTotalCost());
|
||||
}}
|
||||
>
|
||||
{Tr("Reset")}
|
||||
</button>
|
||||
</div>
|
||||
<label class="form-control w-full max-w-xs">
|
||||
<div class="label">
|
||||
<span class="label-text">Theme Switch</span>
|
||||
</div>
|
||||
<select data-choose-theme class="select select-bordered">
|
||||
<option value="light">Light</option>
|
||||
<option value="dark">Dark</option>
|
||||
<option value="cyberpunk">Cyberpunk</option>
|
||||
<option value="black">Black</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="form-control w-full max-w-xs">
|
||||
<div class="label">
|
||||
<span class="label-text">Language</span>
|
||||
</div>
|
||||
<select class="select select-bordered">
|
||||
{Object.keys(LANG_OPTIONS).map((opt) => (
|
||||
<option
|
||||
value={opt}
|
||||
@@ -384,11 +513,10 @@ export default (props: {
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</h3>
|
||||
<hr />
|
||||
<div className="flex justify-between">
|
||||
</label>
|
||||
<div class="join pt-2">
|
||||
<button
|
||||
className="p-2 m-2 rounded bg-purple-600 text-white"
|
||||
class="btn join-item"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(link);
|
||||
alert(tr(`Copied link:`, langCode) + `${link}`);
|
||||
@@ -397,9 +525,11 @@ export default (props: {
|
||||
{Tr("Copy Setting Link")}
|
||||
</button>
|
||||
<button
|
||||
className="p-2 m-2 rounded bg-rose-600 text-white"
|
||||
class="btn join-item"
|
||||
onClick={() => {
|
||||
if (!confirm(tr("Are you sure to clear all history?", langCode)))
|
||||
if (
|
||||
!confirm(tr("Are you sure to clear all history?", langCode))
|
||||
)
|
||||
return;
|
||||
props.chatStore.history = props.chatStore.history.filter(
|
||||
(msg) => msg.example && !msg.hide
|
||||
@@ -411,33 +541,125 @@ export default (props: {
|
||||
{Tr("Clear History")}
|
||||
</button>
|
||||
<button
|
||||
className="p-2 m-2 rounded bg-cyan-600 text-white"
|
||||
class="btn join-item"
|
||||
onClick={() => {
|
||||
props.setShow(false);
|
||||
let dataStr =
|
||||
"data:text/json;charset=utf-8," +
|
||||
encodeURIComponent(
|
||||
JSON.stringify(props.chatStore, null, "\t")
|
||||
);
|
||||
let downloadAnchorNode = document.createElement("a");
|
||||
downloadAnchorNode.setAttribute("href", dataStr);
|
||||
downloadAnchorNode.setAttribute(
|
||||
"download",
|
||||
`chatgpt-api-web-${props.selectedChatStoreIndex}.json`
|
||||
);
|
||||
document.body.appendChild(downloadAnchorNode); // required for firefox
|
||||
downloadAnchorNode.click();
|
||||
downloadAnchorNode.remove();
|
||||
}}
|
||||
>
|
||||
{Tr("Close")}
|
||||
{Tr("Export")}
|
||||
</button>
|
||||
<button
|
||||
class="btn join-item"
|
||||
onClick={() => {
|
||||
const name = prompt(
|
||||
tr("Give this template a name:", langCode)
|
||||
);
|
||||
if (!name) {
|
||||
alert(tr("No template name specified", langCode));
|
||||
return;
|
||||
}
|
||||
const tmp: ChatStore = structuredClone(props.chatStore);
|
||||
tmp.history = tmp.history.filter((h) => h.example);
|
||||
// clear api because it is stored in the API template
|
||||
tmp.apiEndpoint = "";
|
||||
tmp.apiKey = "";
|
||||
tmp.whisper_api = "";
|
||||
tmp.whisper_key = "";
|
||||
tmp.tts_api = "";
|
||||
tmp.tts_key = "";
|
||||
tmp.image_gen_api = "";
|
||||
tmp.image_gen_key = "";
|
||||
// @ts-ignore
|
||||
tmp.name = name;
|
||||
props.templates.push(tmp as TemplateChatStore);
|
||||
props.setTemplates([...props.templates]);
|
||||
}}
|
||||
>
|
||||
{Tr("As template")}
|
||||
</button>
|
||||
<button
|
||||
class="btn join-item"
|
||||
onClick={() => {
|
||||
if (
|
||||
!confirm(
|
||||
tr(
|
||||
"This will OVERWRITE the current chat history! Continue?",
|
||||
langCode
|
||||
)
|
||||
)
|
||||
)
|
||||
return;
|
||||
console.log("importFileRef", importFileRef);
|
||||
importFileRef.current.click();
|
||||
}}
|
||||
>
|
||||
Import
|
||||
</button>
|
||||
<input
|
||||
className="hidden"
|
||||
ref={importFileRef}
|
||||
type="file"
|
||||
onChange={() => {
|
||||
const file = importFileRef.current.files[0];
|
||||
console.log("file to import", file);
|
||||
if (!file || file.type !== "application/json") {
|
||||
alert(tr("Please select a json file", langCode));
|
||||
return;
|
||||
}
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
console.log("import content", reader.result);
|
||||
if (!reader) {
|
||||
alert(tr("Empty file", langCode));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const newChatStore: ChatStore = JSON.parse(
|
||||
reader.result as string
|
||||
);
|
||||
if (!newChatStore.chatgpt_api_web_version) {
|
||||
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(
|
||||
tr(`Import error on parsing json:`, langCode) + `${e}`
|
||||
);
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<p className="m-2 p-2">
|
||||
{Tr("Total cost in this session")} ${props.chatStore.cost.toFixed(4)}
|
||||
</p>
|
||||
<hr />
|
||||
<div className="box">
|
||||
<LongInput
|
||||
field="systemMessageContent"
|
||||
help="系统消息,用于指示ChatGPT的角色和一些前置条件,例如“你是一个有帮助的人工智能助理”,或者“你是一个专业英语翻译,把我的话全部翻译成英语”,详情参考 OPEAN AI API 文档"
|
||||
{...props}
|
||||
/>
|
||||
<span>
|
||||
Valied JSON:{" "}
|
||||
{isVailedJSON(props.chatStore.toolsString) ? "🆗" : "❌"}
|
||||
</span>
|
||||
<LongInput
|
||||
field="toolsString"
|
||||
help="function call tools, should be valied json format in list"
|
||||
{...props}
|
||||
</div>
|
||||
|
||||
<input
|
||||
type="radio"
|
||||
name="setting_tab"
|
||||
role="tab"
|
||||
class="tab"
|
||||
aria-label="Chat"
|
||||
/>
|
||||
<div
|
||||
role="tabpanel"
|
||||
class="tab-content bg-base-100 border-base-300 rounded-box p-6"
|
||||
>
|
||||
<div className="relative border-slate-300 border rounded">
|
||||
<div className="flex justify-between">
|
||||
<strong className="p-1 m-1">Chat API</strong>
|
||||
@@ -533,7 +755,19 @@ export default (props: {
|
||||
readOnly={false}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<input
|
||||
type="radio"
|
||||
name="setting_tab"
|
||||
role="tab"
|
||||
class="tab"
|
||||
aria-label="TTS"
|
||||
/>
|
||||
<div
|
||||
role="tabpanel"
|
||||
class="tab-content bg-base-100 border-base-300 rounded-box p-6"
|
||||
>
|
||||
<div className="relative border-slate-300 border rounded">
|
||||
<div className="flex justify-between">
|
||||
<strong className="p-1 m-1">Whisper API</strong>
|
||||
@@ -616,7 +850,18 @@ export default (props: {
|
||||
))}
|
||||
</select>
|
||||
</Help>
|
||||
|
||||
</div>
|
||||
<input
|
||||
type="radio"
|
||||
name="setting_tab"
|
||||
role="tab"
|
||||
class="tab"
|
||||
aria-label="Image Gen"
|
||||
/>
|
||||
<div
|
||||
role="tabpanel"
|
||||
class="tab-content bg-base-100 border-base-300 rounded-box p-6"
|
||||
>
|
||||
<div className="relative border-slate-300 border rounded">
|
||||
<div className="flex justify-between">
|
||||
<strong className="p-1 m-1">Image Gen API</strong>
|
||||
@@ -640,155 +885,15 @@ export default (props: {
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<p className="m-2 p-2">
|
||||
{Tr("Accumulated cost in all sessions")} ${totalCost.toFixed(4)}
|
||||
</p>
|
||||
<button
|
||||
className="p-2 m-2 rounded bg-emerald-500"
|
||||
onClick={() => {
|
||||
clearTotalCost();
|
||||
setTotalCost(getTotalCost());
|
||||
}}
|
||||
>
|
||||
{Tr("Reset")}
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex justify-evenly flex-wrap">
|
||||
{props.chatStore.toolsString.trim() && (
|
||||
<button
|
||||
className="p-2 m-2 rounded bg-blue-300"
|
||||
onClick={() => {
|
||||
const name = prompt(`Give this **Tools** template a name:`);
|
||||
if (!name) {
|
||||
alert("No template name specified");
|
||||
return;
|
||||
}
|
||||
const newToolsTmp: TemplateTools = {
|
||||
name,
|
||||
toolsString: props.chatStore.toolsString,
|
||||
};
|
||||
props.templateTools.push(newToolsTmp);
|
||||
props.setTemplateTools([...props.templateTools]);
|
||||
}}
|
||||
>
|
||||
{Tr(`Save Tools`)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<p className="flex justify-evenly">
|
||||
<button
|
||||
className="p-2 m-2 rounded bg-amber-500"
|
||||
onClick={() => {
|
||||
let dataStr =
|
||||
"data:text/json;charset=utf-8," +
|
||||
encodeURIComponent(
|
||||
JSON.stringify(props.chatStore, null, "\t")
|
||||
);
|
||||
let downloadAnchorNode = document.createElement("a");
|
||||
downloadAnchorNode.setAttribute("href", dataStr);
|
||||
downloadAnchorNode.setAttribute(
|
||||
"download",
|
||||
`chatgpt-api-web-${props.selectedChatStoreIndex}.json`
|
||||
);
|
||||
document.body.appendChild(downloadAnchorNode); // required for firefox
|
||||
downloadAnchorNode.click();
|
||||
downloadAnchorNode.remove();
|
||||
}}
|
||||
>
|
||||
{Tr("Export")}
|
||||
</button>
|
||||
<button
|
||||
className="p-2 m-2 rounded bg-amber-500"
|
||||
onClick={() => {
|
||||
const name = prompt(tr("Give this template a name:", langCode));
|
||||
if (!name) {
|
||||
alert(tr("No template name specified", langCode));
|
||||
return;
|
||||
}
|
||||
const tmp: ChatStore = structuredClone(props.chatStore);
|
||||
tmp.history = tmp.history.filter((h) => h.example);
|
||||
// clear api because it is stored in the API template
|
||||
tmp.apiEndpoint = "";
|
||||
tmp.apiKey = "";
|
||||
tmp.whisper_api = "";
|
||||
tmp.whisper_key = "";
|
||||
tmp.tts_api = "";
|
||||
tmp.tts_key = "";
|
||||
tmp.image_gen_api = "";
|
||||
tmp.image_gen_key = "";
|
||||
// @ts-ignore
|
||||
tmp.name = name;
|
||||
props.templates.push(tmp as TemplateChatStore);
|
||||
props.setTemplates([...props.templates]);
|
||||
}}
|
||||
>
|
||||
{Tr("As template")}
|
||||
</button>
|
||||
<button
|
||||
className="p-2 m-2 rounded bg-amber-500"
|
||||
onClick={() => {
|
||||
if (
|
||||
!confirm(
|
||||
tr(
|
||||
"This will OVERWRITE the current chat history! Continue?",
|
||||
langCode
|
||||
)
|
||||
)
|
||||
)
|
||||
return;
|
||||
console.log("importFileRef", importFileRef);
|
||||
importFileRef.current.click();
|
||||
}}
|
||||
>
|
||||
Import
|
||||
</button>
|
||||
<input
|
||||
className="hidden"
|
||||
ref={importFileRef}
|
||||
type="file"
|
||||
onChange={() => {
|
||||
const file = importFileRef.current.files[0];
|
||||
console.log("file to import", file);
|
||||
if (!file || file.type !== "application/json") {
|
||||
alert(tr("Please select a json file", langCode));
|
||||
return;
|
||||
}
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
console.log("import content", reader.result);
|
||||
if (!reader) {
|
||||
alert(tr("Empty file", langCode));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const newChatStore: ChatStore = JSON.parse(
|
||||
reader.result as string
|
||||
);
|
||||
if (!newChatStore.chatgpt_api_web_version) {
|
||||
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(
|
||||
tr(`Import error on parsing json:`, langCode) + `${e}`
|
||||
);
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<p className="text-center m-2 p-2">
|
||||
<div className="pt-4 pb-2">
|
||||
<p className="text-center">
|
||||
chatgpt-api-web ChatStore {Tr("Version")}{" "}
|
||||
{props.chatStore.chatgpt_api_web_version}
|
||||
</p>
|
||||
<p>
|
||||
⚠{Tr("Documents and source code are avaliable here")}:{" "}
|
||||
<p className="text-center">
|
||||
{Tr("Documents and source code are avaliable here")}:{" "}
|
||||
<a
|
||||
className="underline"
|
||||
href="https://github.com/heimoshuiyu/chatgpt-api-web"
|
||||
@@ -798,7 +903,6 @@ export default (props: {
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<hr />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,8 +1,42 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
||||
daisyui: {
|
||||
themes: ["light",
|
||||
"dark",
|
||||
"cupcake",
|
||||
"bumblebee",
|
||||
"emerald",
|
||||
"corporate",
|
||||
"synthwave",
|
||||
"retro",
|
||||
"cyberpunk",
|
||||
"valentine",
|
||||
"halloween",
|
||||
"garden",
|
||||
"forest",
|
||||
"aqua",
|
||||
"lofi",
|
||||
"pastel",
|
||||
"fantasy",
|
||||
"wireframe",
|
||||
"black",
|
||||
"luxury",
|
||||
"dracula",
|
||||
"cmyk",
|
||||
"autumn",
|
||||
"business",
|
||||
"acid",
|
||||
"lemonade",
|
||||
"night",
|
||||
"coffee",
|
||||
"winter",
|
||||
"dim",
|
||||
"nord",
|
||||
"sunset",],
|
||||
},
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
plugins: [require('daisyui')],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user