move status to AppContext

This commit is contained in:
2024-12-27 17:39:03 +08:00
parent 0aacbeccb2
commit 092ac46c15
8 changed files with 317 additions and 351 deletions

View File

@@ -1,4 +1,4 @@
import { useState } from "react";
import { useContext, useState } from "react";
import { ChatStore } from "@/types/chatstore";
import { MessageDetail } from "@/chatgpt";
import { Tr } from "@/translate";
@@ -20,10 +20,9 @@ import { Checkbox } from "./components/ui/checkbox";
import { Label } from "./components/ui/label";
import { Textarea } from "./components/ui/textarea";
import { Separator } from "./components/ui/separator";
import { AppContext } from "./pages/App";
interface Props {
chatStore: ChatStore;
setChatStore: (cs: ChatStore) => void;
images: MessageDetail[];
showAddImage: boolean;
setShowAddImage: (se: boolean) => void;
@@ -35,13 +34,14 @@ interface ImageResponse {
revised_prompt: string;
}
export function AddImage({
chatStore,
setChatStore,
showAddImage,
setShowAddImage,
setImages,
images,
}: Props) {
const ctx = useContext(AppContext);
if (ctx === null) return <></>;
const [enableHighResolution, setEnableHighResolution] = useState(true);
const [imageGenPrompt, setImageGenPrompt] = useState("");
const [imageGenModel, setImageGenModel] = useState("dall-e-3");
@@ -134,7 +134,7 @@ export function AddImage({
</div>
</div>
<Separator className="my-2" />
{chatStore.image_gen_api && chatStore.image_gen_key && (
{ctx.chatStore.image_gen_api && ctx.chatStore.image_gen_key && (
<div className="flex flex-col">
<h3>Generate Image</h3>
<span className="flex flex-col justify-between m-1 p-1">
@@ -240,11 +240,11 @@ export function AddImage({
body.style = imageGenStyle;
}
const resp: ImageResponse[] = (
await fetch(chatStore.image_gen_api, {
await fetch(ctx.chatStore.image_gen_api, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${chatStore.image_gen_key}`,
Authorization: `Bearer ${ctx.chatStore.image_gen_key}`,
},
body: JSON.stringify(body),
}).then((resp) => resp.json())
@@ -258,7 +258,7 @@ export function AddImage({
url = "data:image/png;base64," + image.b64_json;
if (!url) continue;
chatStore.history.push({
ctx.chatStore.history.push({
role: "assistant",
content: [
{
@@ -281,7 +281,7 @@ export function AddImage({
response_model_name: imageGenModel,
});
setChatStore({ ...chatStore });
ctx.setChatStore({ ...ctx.chatStore });
}
} catch (e) {
console.error(e);

View File

@@ -77,6 +77,7 @@ import {
import { Separator } from "@/components/ui/separator";
import { Slider } from "@/components/ui/slider";
import { ScrollArea } from "@/components/ui/scroll-area";
import { AppContext } from "@/pages/App";
const TTS_VOICES: string[] = [
"alloy",
@@ -96,14 +97,13 @@ const Help = (props: { children: any; help: string; field: string }) => {
);
};
const SelectModel = (props: {
chatStore: ChatStore;
setChatStore: (cs: ChatStore) => void;
help: string;
}) => {
const SelectModel = (props: { help: string }) => {
const ctx = useContext(AppContext);
if (ctx === null) return <></>;
let shouldIUseCustomModel: boolean = true;
for (const model in models) {
if (props.chatStore.model === model) {
if (ctx.chatStore.model === model) {
shouldIUseCustomModel = false;
}
}
@@ -143,22 +143,22 @@ const SelectModel = (props: {
{useCustomModel ? (
<Input
value={props.chatStore.model}
value={ctx.chatStore.model}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
props.chatStore.model = e.target.value;
props.setChatStore({ ...props.chatStore });
ctx.chatStore.model = e.target.value;
ctx.setChatStore({ ...ctx.chatStore });
}}
/>
) : (
<Select
value={props.chatStore.model}
value={ctx.chatStore.model}
onValueChange={(model: string) => {
props.chatStore.model = model;
props.chatStore.maxTokens = getDefaultParams(
ctx.chatStore.model = model;
ctx.chatStore.maxTokens = getDefaultParams(
"max",
models[model].maxToken
);
props.setChatStore({ ...props.chatStore });
ctx.setChatStore({ ...ctx.chatStore });
}}
>
<SelectTrigger className="w-full">
@@ -181,12 +181,12 @@ const SelectModel = (props: {
};
const LongInput = (props: {
chatStore: ChatStore;
setChatStore: (cs: ChatStore) => void;
field: "systemMessageContent" | "toolsString";
label: string;
help: string;
}) => {
const ctx = useContext(AppContext);
if (ctx === null) return <></>;
return (
<div>
<Label htmlFor="name" className="text-right">
@@ -208,10 +208,10 @@ const LongInput = (props: {
<Textarea
className="h-24 w-full"
value={props.chatStore[props.field]}
value={ctx.chatStore[props.field]}
onChange={(event: any) => {
props.chatStore[props.field] = event.target.value;
props.setChatStore({ ...props.chatStore });
ctx.chatStore[props.field] = event.target.value;
ctx.setChatStore({ ...ctx.chatStore });
autoHeight(event.target);
}}
onKeyPress={(event: any) => {
@@ -223,8 +223,6 @@ const LongInput = (props: {
};
const InputField = (props: {
chatStore: ChatStore;
setChatStore: (cs: ChatStore) => void;
field:
| "apiKey"
| "apiEndpoint"
@@ -236,6 +234,8 @@ const InputField = (props: {
| "image_gen_key";
help: string;
}) => {
const ctx = useContext(AppContext);
if (ctx === null) return <></>;
const [hideInput, setHideInput] = useState(true);
return (
<>
@@ -261,10 +261,10 @@ const InputField = (props: {
<div className="flex w-full items-center space-x-2">
<Input
type={hideInput ? "password" : "text"}
value={props.chatStore[props.field]}
value={ctx.chatStore[props.field]}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
props.chatStore[props.field] = event.target.value;
props.setChatStore({ ...props.chatStore });
ctx.chatStore[props.field] = event.target.value;
ctx.setChatStore({ ...ctx.chatStore });
}}
/>
<Button
@@ -285,30 +285,30 @@ const InputField = (props: {
};
const Slicer = (props: {
chatStore: ChatStore;
setChatStore: (cs: ChatStore) => void;
field: "temperature" | "top_p" | "tts_speed";
help: string;
min: number;
max: number;
}) => {
const ctx = useContext(AppContext);
if (ctx === null) return <></>;
const enable_filed_name: "temperature_enabled" | "top_p_enabled" =
`${props.field}_enabled` as any;
const enabled = props.chatStore[enable_filed_name];
const enabled = ctx.chatStore[enable_filed_name];
if (enabled === null || enabled === undefined) {
if (props.field === "temperature") {
props.chatStore[enable_filed_name] = true;
ctx.chatStore[enable_filed_name] = true;
}
if (props.field === "top_p") {
props.chatStore[enable_filed_name] = false;
ctx.chatStore[enable_filed_name] = false;
}
}
const setEnabled = (state: boolean) => {
props.chatStore[enable_filed_name] = state;
props.setChatStore({ ...props.chatStore });
ctx.chatStore[enable_filed_name] = state;
ctx.setChatStore({ ...ctx.chatStore });
};
return (
<div className="space-y-2">
@@ -329,10 +329,10 @@ const Slicer = (props: {
</DialogContent>
</Dialog>
<Checkbox
checked={props.chatStore[enable_filed_name]}
checked={ctx.chatStore[enable_filed_name]}
onCheckedChange={(checked: boolean) => setEnabled(!!checked)}
/>
{!props.chatStore[enable_filed_name] && (
{!ctx.chatStore[enable_filed_name] && (
<span className="text-xs text-muted-foreground">disabled</span>
)}
</Label>
@@ -345,10 +345,10 @@ const Slicer = (props: {
min={props.min}
max={props.max}
step={0.01}
value={[props.chatStore[props.field]]}
value={[ctx.chatStore[props.field]]}
onValueChange={(value) => {
props.chatStore[props.field] = value[0];
props.setChatStore({ ...props.chatStore });
ctx.chatStore[props.field] = value[0];
ctx.setChatStore({ ...ctx.chatStore });
}}
/>
</div>
@@ -356,11 +356,11 @@ const Slicer = (props: {
type="number"
disabled={!enabled}
className="w-24"
value={props.chatStore[props.field]}
value={ctx.chatStore[props.field]}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
const value = parseFloat(e.target.value);
props.chatStore[props.field] = value;
props.setChatStore({ ...props.chatStore });
ctx.chatStore[props.field] = value;
ctx.setChatStore({ ...ctx.chatStore });
}}
/>
</div>
@@ -370,8 +370,6 @@ const Slicer = (props: {
};
const Number = (props: {
chatStore: ChatStore;
setChatStore: (cs: ChatStore) => void;
field:
| "totalTokens"
| "maxTokens"
@@ -383,6 +381,8 @@ const Number = (props: {
readOnly: boolean;
help: string;
}) => {
const ctx = useContext(AppContext);
if (ctx === null) return <></>;
return (
<div className="space-y-2">
<Label className="flex items-center gap-2">
@@ -404,12 +404,12 @@ const Number = (props: {
{props.field === "maxGenTokens" && (
<Checkbox
checked={props.chatStore.maxGenTokens_enabled}
checked={ctx.chatStore.maxGenTokens_enabled}
onCheckedChange={() => {
const newChatStore = { ...props.chatStore };
const newChatStore = { ...ctx.chatStore };
newChatStore.maxGenTokens_enabled =
!newChatStore.maxGenTokens_enabled;
props.setChatStore({ ...newChatStore });
ctx.setChatStore({ ...newChatStore });
}}
/>
)}
@@ -419,15 +419,14 @@ const Number = (props: {
type="number"
readOnly={props.readOnly}
disabled={
props.field === "maxGenTokens" &&
!props.chatStore.maxGenTokens_enabled
props.field === "maxGenTokens" && !ctx.chatStore.maxGenTokens_enabled
}
value={props.chatStore[props.field]}
value={ctx.chatStore[props.field]}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
let newNumber = parseFloat(event.target.value);
if (newNumber < 0) newNumber = 0;
props.chatStore[props.field] = newNumber;
props.setChatStore({ ...props.chatStore });
ctx.chatStore[props.field] = newNumber;
ctx.setChatStore({ ...ctx.chatStore });
}}
/>
</div>
@@ -435,20 +434,21 @@ const Number = (props: {
};
const Choice = (props: {
chatStore: ChatStore;
setChatStore: (cs: ChatStore) => void;
field: "streamMode" | "develop_mode" | "json_mode" | "logprobs";
help: string;
}) => {
const ctx = useContext(AppContext);
if (ctx === null) return <></>;
return (
<div className="flex items-center space-x-2">
<div className="flex items-center">
<Checkbox
id={`${props.field}-checkbox`}
checked={props.chatStore[props.field]}
checked={ctx.chatStore[props.field]}
onCheckedChange={(checked: boolean) => {
props.chatStore[props.field] = checked;
props.setChatStore({ ...props.chatStore });
ctx.chatStore[props.field] = checked;
ctx.setChatStore({ ...ctx.chatStore });
}}
/>
</div>
@@ -475,37 +475,21 @@ const Choice = (props: {
);
};
export default (props: {
chatStore: ChatStore;
setChatStore: (cs: ChatStore) => void;
setShow: Dispatch<boolean>;
selectedChatStoreIndex: number;
templates: TemplateChatStore[];
setTemplates: (templates: TemplateChatStore[]) => void;
templateAPIs: TemplateAPI[];
setTemplateAPIs: (templateAPIs: TemplateAPI[]) => void;
templateAPIsWhisper: TemplateAPI[];
setTemplateAPIsWhisper: (templateAPIs: TemplateAPI[]) => void;
templateAPIsTTS: TemplateAPI[];
setTemplateAPIsTTS: (templateAPIs: TemplateAPI[]) => void;
templateAPIsImageGen: TemplateAPI[];
setTemplateAPIsImageGen: (templateAPIs: TemplateAPI[]) => void;
templateTools: TemplateTools[];
setTemplateTools: (templateTools: TemplateTools[]) => void;
}) => {
export default (props: { setShow: Dispatch<boolean> }) => {
const ctx = useContext(AppContext);
if (ctx === null) return <></>;
let link =
location.protocol +
"//" +
location.host +
location.pathname +
`?key=${encodeURIComponent(
props.chatStore.apiKey
)}&api=${encodeURIComponent(props.chatStore.apiEndpoint)}&mode=${
props.chatStore.streamMode ? "stream" : "fetch"
}&model=${props.chatStore.model}&sys=${encodeURIComponent(
props.chatStore.systemMessageContent
)}`;
if (props.chatStore.develop_mode) {
`?key=${encodeURIComponent(ctx.chatStore.apiKey)}&api=${encodeURIComponent(
ctx.chatStore.apiEndpoint
)}&mode=${ctx.chatStore.streamMode ? "stream" : "fetch"}&model=${
ctx.chatStore.model
}&sys=${encodeURIComponent(ctx.chatStore.systemMessageContent)}`;
if (ctx.chatStore.develop_mode) {
link = link + `&dev=true`;
}
@@ -562,7 +546,7 @@ export default (props: {
$ USD
</span>
<span className="text-lg font-bold leading-none sm:text-3xl">
{props.chatStore.cost?.toFixed(4)}
{ctx.chatStore.cost?.toFixed(4)}
</span>
</div>
</div>
@@ -583,7 +567,7 @@ export default (props: {
/>
<span className="pt-1">
JSON Check:{" "}
{isVailedJSON(props.chatStore.toolsString) ? (
{isVailedJSON(ctx.chatStore.toolsString) ? (
<CheckIcon className="inline w-3 h-3" />
) : (
<BanIcon className="inline w-3 h-3" />
@@ -591,7 +575,7 @@ export default (props: {
</span>
<div className="box">
<div className="flex justify-evenly flex-wrap">
{props.chatStore.toolsString.trim() && (
{ctx.chatStore.toolsString.trim() && (
<Button
onClick={() => {
const name = prompt(
@@ -603,10 +587,10 @@ export default (props: {
}
const newToolsTmp: TemplateTools = {
name,
toolsString: props.chatStore.toolsString,
toolsString: ctx.chatStore.toolsString,
};
props.templateTools.push(newToolsTmp);
props.setTemplateTools([...props.templateTools]);
ctx.templateTools.push(newToolsTmp);
ctx.setTemplateTools([...ctx.templateTools]);
}}
>
{Tr(`Save Tools`)}
@@ -710,12 +694,12 @@ export default (props: {
<Button
variant="destructive"
onClick={() => {
props.chatStore.history =
props.chatStore.history.filter(
ctx.chatStore.history =
ctx.chatStore.history.filter(
(msg) => msg.example && !msg.hide
);
props.chatStore.postBeginIndex = 0;
props.setChatStore({ ...props.chatStore });
ctx.chatStore.postBeginIndex = 0;
ctx.setChatStore({ ...ctx.chatStore });
}}
>
Yes, clear all history
@@ -731,14 +715,14 @@ export default (props: {
let dataStr =
"data:text/json;charset=utf-8," +
encodeURIComponent(
JSON.stringify(props.chatStore, null, "\t")
JSON.stringify(ctx.chatStore, null, "\t")
);
let downloadAnchorNode =
document.createElement("a");
downloadAnchorNode.setAttribute("href", dataStr);
downloadAnchorNode.setAttribute(
"download",
`chatgpt-api-web-${props.selectedChatStoreIndex}.json`
`chatgpt-api-web-${ctx.selectedChatIndex}.json`
);
document.body.appendChild(downloadAnchorNode);
downloadAnchorNode.click();
@@ -760,7 +744,7 @@ export default (props: {
return;
}
const tmp: ChatStore = structuredClone(
props.chatStore
ctx.chatStore
);
tmp.history = tmp.history.filter((h) => h.example);
tmp.apiEndpoint = "";
@@ -773,8 +757,8 @@ export default (props: {
tmp.image_gen_key = "";
// @ts-ignore
tmp.name = name;
props.templates.push(tmp as TemplateChatStore);
props.setTemplates([...props.templates]);
ctx.templates.push(tmp as TemplateChatStore);
ctx.setTemplates([...ctx.templates]);
}}
>
{Tr("As template")}
@@ -826,7 +810,7 @@ export default (props: {
langCode
);
}
props.setChatStore({ ...newChatStore });
ctx.setChatStore({ ...newChatStore });
} catch (e) {
alert(
tr(
@@ -868,10 +852,10 @@ export default (props: {
/>
<SetAPIsTemplate
label="Chat API"
endpoint={props.chatStore.apiEndpoint}
APIkey={props.chatStore.apiKey}
tmps={props.templateAPIs}
setTmps={props.setTemplateAPIs}
endpoint={ctx.chatStore.apiEndpoint}
APIkey={ctx.chatStore.apiKey}
tmps={ctx.templateAPIs}
setTmps={ctx.setTemplateAPIs}
/>
</CardContent>
</Card>
@@ -973,10 +957,10 @@ export default (props: {
/>
<SetAPIsTemplate
label="Whisper API"
endpoint={props.chatStore.whisper_api}
APIkey={props.chatStore.whisper_key}
tmps={props.templateAPIsWhisper}
setTmps={props.setTemplateAPIsWhisper}
endpoint={ctx.chatStore.whisper_api}
APIkey={ctx.chatStore.whisper_key}
tmps={ctx.templateAPIsWhisper}
setTmps={ctx.setTemplateAPIsWhisper}
/>
</CardContent>
</Card>
@@ -1006,10 +990,10 @@ export default (props: {
/>
<SetAPIsTemplate
label="TTS API"
endpoint={props.chatStore.tts_api}
APIkey={props.chatStore.tts_key}
tmps={props.templateAPIsTTS}
setTmps={props.setTemplateAPIsTTS}
endpoint={ctx.chatStore.tts_api}
APIkey={ctx.chatStore.tts_key}
tmps={ctx.templateAPIsTTS}
setTmps={ctx.setTemplateAPIsTTS}
/>
</CardContent>
</Card>
@@ -1034,10 +1018,10 @@ export default (props: {
</Dialog>
</Label>
<Select
value={props.chatStore.tts_voice}
value={ctx.chatStore.tts_voice}
onValueChange={(value) => {
props.chatStore.tts_voice = value;
props.setChatStore({ ...props.chatStore });
ctx.chatStore.tts_voice = value;
ctx.setChatStore({ ...ctx.chatStore });
}}
>
<SelectTrigger className="w-full">
@@ -1084,10 +1068,10 @@ export default (props: {
</Dialog>
</Label>
<Select
value={props.chatStore.tts_format}
value={ctx.chatStore.tts_format}
onValueChange={(value) => {
props.chatStore.tts_format = value;
props.setChatStore({ ...props.chatStore });
ctx.chatStore.tts_format = value;
ctx.setChatStore({ ...ctx.chatStore });
}}
>
<SelectTrigger className="w-full">
@@ -1131,10 +1115,10 @@ export default (props: {
/>
<SetAPIsTemplate
label="Image Gen API"
endpoint={props.chatStore.image_gen_api}
APIkey={props.chatStore.image_gen_key}
tmps={props.templateAPIsImageGen}
setTmps={props.setTemplateAPIsImageGen}
endpoint={ctx.chatStore.image_gen_api}
APIkey={ctx.chatStore.image_gen_key}
tmps={ctx.templateAPIsImageGen}
setTmps={ctx.setTemplateAPIsImageGen}
/>
</CardContent>
</Card>
@@ -1144,7 +1128,7 @@ export default (props: {
<div className="pt-4 space-y-2">
<p className="text-sm text-muted-foreground text-center">
chatgpt-api-web ChatStore {Tr("Version")}{" "}
{props.chatStore.chatgpt_api_web_version}
{ctx.chatStore.chatgpt_api_web_version}
</p>
<p className="text-sm text-muted-foreground text-center">
{Tr("Documents and source code are avaliable here")}:{" "}

View File

@@ -1,14 +1,14 @@
import { AppContext } from "@/pages/App";
import { TemplateChatStore } from "@/types/chatstore";
import { ChatStore } from "@/types/chatstore";
import { getDefaultParams } from "@/utils/getDefaultParam";
import { useContext } from "react";
const Templates = () => {
const ctx = useContext(AppContext);
if (ctx === null) return <></>;
const { templates, chatStore, setChatStore, setTemplates } = ctx;
const Templates = (props: {
templates: TemplateChatStore[];
chatStore: ChatStore;
setChatStore: (cs: ChatStore) => void;
setTemplates: (templates: TemplateChatStore[]) => void;
}) => {
const { templates, chatStore, setChatStore, setTemplates } = props;
return (
<>
{templates.map((t, index) => (

View File

@@ -13,61 +13,54 @@ import {
} from "@/components/ui/navigation-menu";
import { Button } from "./components/ui/button";
import { cn } from "@/lib/utils";
import { useContext } from "react";
import { AppContext } from "./pages/App";
interface Props {
chatStore: ChatStore;
setChatStore: (cs: ChatStore) => void;
tmps: TemplateAPI[];
setTmps: (tmps: TemplateAPI[]) => void;
label: string;
apiField: string;
keyField: string;
}
export function ListAPIs({
tmps,
setTmps,
chatStore,
setChatStore,
label,
apiField,
keyField,
}: Props) {
export function ListAPIs({ label, apiField, keyField }: Props) {
const ctx = useContext(AppContext);
if (ctx === null) return <></>;
return (
<NavigationMenuItem>
<NavigationMenuTrigger>
{label}{" "}
<span className="hidden lg:inline">
{tmps.find(
{ctx.templateAPIs.find(
(t) =>
chatStore[apiField as keyof ChatStore] === t.endpoint &&
chatStore[keyField as keyof ChatStore] === t.key
ctx.chatStore[apiField as keyof ChatStore] === t.endpoint &&
ctx.chatStore[keyField as keyof ChatStore] === t.key
)?.name &&
`: ${
tmps.find(
ctx.templateAPIs.find(
(t) =>
chatStore[apiField as keyof ChatStore] === t.endpoint &&
chatStore[keyField as keyof ChatStore] === t.key
ctx.chatStore[apiField as keyof ChatStore] === t.endpoint &&
ctx.chatStore[keyField as keyof ChatStore] === t.key
)?.name
}`}
</span>
</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px] ">
{tmps.map((t, index) => (
{ctx.templateAPIs.map((t, index) => (
<li>
<NavigationMenuLink asChild>
<a
onClick={() => {
// @ts-ignore
chatStore[apiField] = t.endpoint;
ctx.chatStore[apiField as keyof ChatStore] = t.endpoint;
// @ts-ignore
chatStore[keyField] = t.key;
setChatStore({ ...chatStore });
ctx.chatStore[keyField] = t.key;
ctx.setChatStore({ ...ctx.chatStore });
}}
className={cn(
"block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground",
chatStore[apiField as keyof ChatStore] === t.endpoint &&
chatStore[keyField as keyof ChatStore] === t.key
ctx.chatStore[apiField as keyof ChatStore] === t.endpoint &&
ctx.chatStore[keyField as keyof ChatStore] === t.key
? "bg-accent text-accent-foreground"
: ""
)}
@@ -88,7 +81,7 @@ export function ListAPIs({
const name = prompt(`Give **${label}** template a name`);
if (!name) return;
t.name = name;
setTmps(structuredClone(tmps));
ctx.setTemplateAPIs(structuredClone(ctx.templateAPIs));
}}
>
Edit
@@ -104,8 +97,8 @@ export function ListAPIs({
) {
return;
}
tmps.splice(index, 1);
setTmps(structuredClone(tmps));
ctx.templateAPIs.splice(index, 1);
ctx.setTemplateAPIs(structuredClone(ctx.templateAPIs));
}}
>
Delete

View File

@@ -1,6 +1,6 @@
import { XMarkIcon } from "@heroicons/react/24/outline";
import Markdown from "react-markdown";
import { useState } from "react";
import { useContext, useState } from "react";
import { Tr, langCodeContext, LANG_OPTIONS } from "@/translate";
import { ChatStore, ChatStoreMessage } from "@/types/chatstore";
@@ -26,6 +26,7 @@ import {
MessageSquareOffIcon,
MessageSquarePlusIcon,
} from "lucide-react";
import { AppContext } from "./pages/App";
export const isVailedJSON = (str: string): boolean => {
try {
@@ -36,14 +37,12 @@ export const isVailedJSON = (str: string): boolean => {
return true;
};
interface Props {
messageIndex: number;
chatStore: ChatStore;
setChatStore: (cs: ChatStore) => void;
}
export default function Message(props: { messageIndex: number }) {
const ctx = useContext(AppContext);
if (ctx === null) return <></>;
const { messageIndex } = props;
const { chatStore, setChatStore } = ctx;
export default function Message(props: Props) {
const { chatStore, messageIndex, setChatStore } = props;
const chat = chatStore.history[messageIndex];
const [showEdit, setShowEdit] = useState(false);
const [showCopiedHint, setShowCopiedHint] = useState(false);

View File

@@ -1,5 +1,5 @@
import { openDB } from "idb";
import { useEffect, useState } from "react";
import { IDBPDatabase, openDB } from "idb";
import { createContext, useEffect, useState } from "react";
import "@/global.css";
import { calculate_token_length } from "@/chatgpt";
@@ -14,6 +14,43 @@ import { STORAGE_NAME, STORAGE_NAME_SELECTED } from "@/const";
import { upgrade } from "@/indexedDB/upgrade";
import { getTotalCost } from "@/utils/totalCost";
import {
STORAGE_NAME_TEMPLATE,
STORAGE_NAME_TEMPLATE_API,
STORAGE_NAME_TEMPLATE_API_IMAGE_GEN,
STORAGE_NAME_TEMPLATE_API_TTS,
STORAGE_NAME_TEMPLATE_API_WHISPER,
STORAGE_NAME_TEMPLATE_TOOLS,
} from "@/const";
import {
ChatStoreMessage,
TemplateChatStore,
TemplateAPI,
TemplateTools,
} from "../types/chatstore";
interface AppContextType {
db: Promise<IDBPDatabase<ChatStore>>;
chatStore: ChatStore;
setChatStore: (cs: ChatStore) => void;
selectedChatIndex: number;
setSelectedChatIndex: (i: number) => void;
templates: TemplateChatStore[];
setTemplates: (t: TemplateChatStore[]) => void;
templateAPIs: TemplateAPI[];
setTemplateAPIs: (t: TemplateAPI[]) => void;
templateAPIsWhisper: TemplateAPI[];
setTemplateAPIsWhisper: (t: TemplateAPI[]) => void;
templateAPIsTTS: TemplateAPI[];
setTemplateAPIsTTS: (t: TemplateAPI[]) => void;
templateAPIsImageGen: TemplateAPI[];
setTemplateAPIsImageGen: (t: TemplateAPI[]) => void;
templateTools: TemplateTools[];
setTemplateTools: (t: TemplateTools[]) => void;
}
export const AppContext = createContext<AppContextType | null>(null);
import {
Sidebar,
SidebarContent,
@@ -247,6 +284,77 @@ export function App() {
run();
}, []);
const [templates, _setTemplates] = useState(
JSON.parse(
localStorage.getItem(STORAGE_NAME_TEMPLATE) || "[]"
) as TemplateChatStore[]
);
const [templateAPIs, _setTemplateAPIs] = useState(
JSON.parse(
localStorage.getItem(STORAGE_NAME_TEMPLATE_API) || "[]"
) as TemplateAPI[]
);
const [templateAPIsWhisper, _setTemplateAPIsWhisper] = useState(
JSON.parse(
localStorage.getItem(STORAGE_NAME_TEMPLATE_API_WHISPER) || "[]"
) as TemplateAPI[]
);
const [templateAPIsTTS, _setTemplateAPIsTTS] = useState(
JSON.parse(
localStorage.getItem(STORAGE_NAME_TEMPLATE_API_TTS) || "[]"
) as TemplateAPI[]
);
const [templateAPIsImageGen, _setTemplateAPIsImageGen] = useState(
JSON.parse(
localStorage.getItem(STORAGE_NAME_TEMPLATE_API_IMAGE_GEN) || "[]"
) as TemplateAPI[]
);
const [templateTools, _setTemplateTools] = useState(
JSON.parse(
localStorage.getItem(STORAGE_NAME_TEMPLATE_TOOLS) || "[]"
) as TemplateTools[]
);
const setTemplates = (templates: TemplateChatStore[]) => {
localStorage.setItem(STORAGE_NAME_TEMPLATE, JSON.stringify(templates));
_setTemplates(templates);
};
const setTemplateAPIs = (templateAPIs: TemplateAPI[]) => {
localStorage.setItem(
STORAGE_NAME_TEMPLATE_API,
JSON.stringify(templateAPIs)
);
_setTemplateAPIs(templateAPIs);
};
const setTemplateAPIsWhisper = (templateAPIWhisper: TemplateAPI[]) => {
localStorage.setItem(
STORAGE_NAME_TEMPLATE_API_WHISPER,
JSON.stringify(templateAPIWhisper)
);
_setTemplateAPIsWhisper(templateAPIWhisper);
};
const setTemplateAPIsTTS = (templateAPITTS: TemplateAPI[]) => {
localStorage.setItem(
STORAGE_NAME_TEMPLATE_API_TTS,
JSON.stringify(templateAPITTS)
);
_setTemplateAPIsTTS(templateAPITTS);
};
const setTemplateAPIsImageGen = (templateAPIImageGen: TemplateAPI[]) => {
localStorage.setItem(
STORAGE_NAME_TEMPLATE_API_IMAGE_GEN,
JSON.stringify(templateAPIImageGen)
);
_setTemplateAPIsImageGen(templateAPIImageGen);
};
const setTemplateTools = (templateTools: TemplateTools[]) => {
localStorage.setItem(
STORAGE_NAME_TEMPLATE_TOOLS,
JSON.stringify(templateTools)
);
_setTemplateTools(templateTools);
};
console.log("[PERFORMANCE!] reading localStorage");
return (
<>
<Sidebar>
@@ -410,13 +518,29 @@ export function App() {
</div>
</div>
</header>
<ChatBOX
db={db}
chatStore={chatStore}
setChatStore={setChatStore}
selectedChatIndex={selectedChatIndex}
setSelectedChatIndex={setSelectedChatIndex}
/>
<AppContext.Provider
value={{
db,
chatStore,
setChatStore,
selectedChatIndex,
setSelectedChatIndex,
templates,
setTemplates,
templateAPIs,
setTemplateAPIs,
templateAPIsWhisper,
setTemplateAPIsWhisper,
templateAPIsTTS,
setTemplateAPIsTTS,
templateAPIsImageGen,
setTemplateAPIsImageGen,
templateTools,
setTemplateTools,
}}
>
<ChatBOX />
</AppContext.Provider>
</SidebarInset>
</>
);

View File

@@ -1,15 +1,7 @@
import { IDBPDatabase } from "idb";
import { useRef } from "react";
import { useContext, useRef } from "react";
import { useEffect, useState, Dispatch } from "react";
import { Tr, langCodeContext, LANG_OPTIONS } from "@/translate";
import {
STORAGE_NAME_TEMPLATE,
STORAGE_NAME_TEMPLATE_API,
STORAGE_NAME_TEMPLATE_API_IMAGE_GEN,
STORAGE_NAME_TEMPLATE_API_TTS,
STORAGE_NAME_TEMPLATE_API_WHISPER,
STORAGE_NAME_TEMPLATE_TOOLS,
} from "@/const";
import { addTotalCost, getTotalCost } from "@/utils/totalCost";
import ChatGPT, {
calculate_token_length,
@@ -79,14 +71,18 @@ import {
navigationMenuTriggerStyle,
} from "@/components/ui/navigation-menu";
export default function ChatBOX(props: {
db: Promise<IDBPDatabase<ChatStore>>;
chatStore: ChatStore;
setChatStore: (cs: ChatStore) => void;
selectedChatIndex: number;
setSelectedChatIndex: Dispatch<number>;
}) {
const { chatStore, setChatStore } = props;
import { AppContext } from "./App";
export default function ChatBOX() {
const ctx = useContext(AppContext);
if (ctx === null) return <></>;
const {
db,
chatStore,
setChatStore,
selectedChatIndex,
setSelectedChatIndex,
} = ctx;
// prevent error
if (chatStore === undefined) return <div></div>;
const [inputMsg, setInputMsg] = useState("");
@@ -357,7 +353,7 @@ export default function ChatBOX(props: {
alert(error);
} finally {
setShowGenerating(false);
props.setSelectedChatIndex(props.selectedChatIndex);
setSelectedChatIndex(selectedChatIndex);
}
};
@@ -401,102 +397,13 @@ export default function ChatBOX(props: {
};
const [showSettings, setShowSettings] = useState(false);
const [templates, _setTemplates] = useState(
JSON.parse(
localStorage.getItem(STORAGE_NAME_TEMPLATE) || "[]"
) as TemplateChatStore[]
);
const [templateAPIs, _setTemplateAPIs] = useState(
JSON.parse(
localStorage.getItem(STORAGE_NAME_TEMPLATE_API) || "[]"
) as TemplateAPI[]
);
const [templateAPIsWhisper, _setTemplateAPIsWhisper] = useState(
JSON.parse(
localStorage.getItem(STORAGE_NAME_TEMPLATE_API_WHISPER) || "[]"
) as TemplateAPI[]
);
const [templateAPIsTTS, _setTemplateAPIsTTS] = useState(
JSON.parse(
localStorage.getItem(STORAGE_NAME_TEMPLATE_API_TTS) || "[]"
) as TemplateAPI[]
);
const [templateAPIsImageGen, _setTemplateAPIsImageGen] = useState(
JSON.parse(
localStorage.getItem(STORAGE_NAME_TEMPLATE_API_IMAGE_GEN) || "[]"
) as TemplateAPI[]
);
const [toolsTemplates, _setToolsTemplates] = useState(
JSON.parse(
localStorage.getItem(STORAGE_NAME_TEMPLATE_TOOLS) || "[]"
) as TemplateTools[]
);
const setTemplates = (templates: TemplateChatStore[]) => {
localStorage.setItem(STORAGE_NAME_TEMPLATE, JSON.stringify(templates));
_setTemplates(templates);
};
const setTemplateAPIs = (templateAPIs: TemplateAPI[]) => {
localStorage.setItem(
STORAGE_NAME_TEMPLATE_API,
JSON.stringify(templateAPIs)
);
_setTemplateAPIs(templateAPIs);
};
const setTemplateAPIsWhisper = (templateAPIWhisper: TemplateAPI[]) => {
localStorage.setItem(
STORAGE_NAME_TEMPLATE_API_WHISPER,
JSON.stringify(templateAPIWhisper)
);
_setTemplateAPIsWhisper(templateAPIWhisper);
};
const setTemplateAPIsTTS = (templateAPITTS: TemplateAPI[]) => {
localStorage.setItem(
STORAGE_NAME_TEMPLATE_API_TTS,
JSON.stringify(templateAPITTS)
);
_setTemplateAPIsTTS(templateAPITTS);
};
const setTemplateAPIsImageGen = (templateAPIImageGen: TemplateAPI[]) => {
localStorage.setItem(
STORAGE_NAME_TEMPLATE_API_IMAGE_GEN,
JSON.stringify(templateAPIImageGen)
);
_setTemplateAPIsImageGen(templateAPIImageGen);
};
const setTemplateTools = (templateTools: TemplateTools[]) => {
localStorage.setItem(
STORAGE_NAME_TEMPLATE_TOOLS,
JSON.stringify(templateTools)
);
_setToolsTemplates(templateTools);
};
const userInputRef = useRef<HTMLInputElement>(null);
return (
<>
<div className="flex flex-col p-2 gap-2 w-full">
<div className="flex items-center gap-2 justify-between">
{true && (
<Settings
chatStore={chatStore}
setChatStore={setChatStore}
setShow={setShowSettings}
selectedChatStoreIndex={props.selectedChatIndex}
templates={templates}
setTemplates={setTemplates}
templateAPIs={templateAPIs}
setTemplateAPIs={setTemplateAPIs}
templateAPIsWhisper={templateAPIsWhisper}
setTemplateAPIsWhisper={setTemplateAPIsWhisper}
templateAPIsTTS={templateAPIsTTS}
setTemplateAPIsTTS={setTemplateAPIsTTS}
templateAPIsImageGen={templateAPIsImageGen}
setTemplateAPIsImageGen={setTemplateAPIsImageGen}
templateTools={toolsTemplates}
setTemplateTools={setTemplateTools}
/>
)}
{true && <Settings setShow={setShowSettings} />}
<Button
variant="outline"
size="icon"
@@ -505,15 +412,7 @@ export default function ChatBOX(props: {
<SearchIcon />
</Button>
</div>
{showSearch && (
<Search
setSelectedChatIndex={props.setSelectedChatIndex}
db={props.db}
chatStore={chatStore}
show={showSearch}
setShow={setShowSearch}
/>
)}
{showSearch && <Search show={showSearch} setShow={setShowSearch} />}
{!chatStore.apiKey && (
<Alert>
@@ -535,54 +434,30 @@ export default function ChatBOX(props: {
)}
<NavigationMenu>
<NavigationMenuList>
{templateAPIs.length > 0 && (
<ListAPIs
label="API"
tmps={templateAPIs}
setTmps={setTemplateAPIs}
chatStore={chatStore}
setChatStore={setChatStore}
apiField="apiEndpoint"
keyField="apiKey"
/>
{ctx.templateAPIs.length > 0 && (
<ListAPIs label="API" apiField="apiEndpoint" keyField="apiKey" />
)}
{templateAPIsWhisper.length > 0 && (
{ctx.templateAPIsWhisper.length > 0 && (
<ListAPIs
label="Whisper API"
tmps={templateAPIsWhisper}
setTmps={setTemplateAPIsWhisper}
chatStore={chatStore}
setChatStore={setChatStore}
apiField="whisper_api"
keyField="whisper_key"
/>
)}
{templateAPIsTTS.length > 0 && (
<ListAPIs
label="TTS API"
tmps={templateAPIsTTS}
setTmps={setTemplateAPIsTTS}
chatStore={chatStore}
setChatStore={setChatStore}
apiField="tts_api"
keyField="tts_key"
/>
{ctx.templateAPIsTTS.length > 0 && (
<ListAPIs label="TTS API" apiField="tts_api" keyField="tts_key" />
)}
{templateAPIsImageGen.length > 0 && (
{ctx.templateAPIsImageGen.length > 0 && (
<ListAPIs
label="Image Gen API"
tmps={templateAPIsImageGen}
setTmps={setTemplateAPIsImageGen}
chatStore={chatStore}
setChatStore={setChatStore}
apiField="image_gen_api"
keyField="image_gen_key"
/>
)}
{toolsTemplates.length > 0 && (
{ctx.templateTools.length > 0 && (
<ListToolsTempaltes
templateTools={toolsTemplates}
setTemplateTools={setTemplateTools}
templateTools={ctx.templateTools}
setTemplateTools={ctx.setTemplateTools}
chatStore={chatStore}
setChatStore={setChatStore}
/>
@@ -611,12 +486,7 @@ export default function ChatBOX(props: {
</h2>
<div className="divider"></div>
<div className="flex flex-wrap">
<Templates
templates={templates}
setTemplates={setTemplates}
chatStore={chatStore}
setChatStore={setChatStore}
/>
<Templates />
</div>
</div>
)}
@@ -675,11 +545,7 @@ export default function ChatBOX(props: {
</ChatBubble>
)}
{chatStore.history.map((_, messageIndex) => (
<Message
chatStore={chatStore}
setChatStore={setChatStore}
messageIndex={messageIndex}
/>
<Message messageIndex={messageIndex} />
))}
{showGenerating && (
<ChatBubble variant="received">
@@ -841,8 +707,6 @@ export default function ChatBOX(props: {
</form>
<AddImage
chatStore={chatStore}
setChatStore={setChatStore}
setShowAddImage={setShowAddImage}
images={images}
showAddImage={showAddImage}

View File

@@ -1,5 +1,5 @@
import { IDBPDatabase } from "idb";
import { useRef, useState, Dispatch } from "react";
import { useRef, useState, Dispatch, useContext } from "react";
import { ChatStore } from "@/types/chatstore";
import { MessageDetail } from "./chatgpt";
@@ -24,6 +24,7 @@ import {
} from "@/components/ui/pagination";
import { Input } from "./components/ui/input";
import { AppContext } from "./pages/App";
interface ChatStoreSearchResult {
key: IDBValidKey;
@@ -33,12 +34,13 @@ interface ChatStoreSearchResult {
}
export default function Search(props: {
db: Promise<IDBPDatabase<ChatStore>>;
setSelectedChatIndex: Dispatch<number>;
chatStore: ChatStore;
show: boolean;
setShow: (show: boolean) => void;
}) {
const ctx = useContext(AppContext);
if (ctx === null) return <></>;
const { setSelectedChatIndex, db } = ctx;
const [searchResult, setSearchResult] = useState<ChatStoreSearchResult[]>([]);
const [searching, setSearching] = useState<boolean>(false);
const [searchingNow, setSearchingNow] = useState<number>(0);
@@ -76,8 +78,8 @@ export default function Search(props: {
setSearching(true);
const db = await props.db;
const resultKeys = await db.getAllKeys("chatgpt-api-web");
const idb = await db;
const resultKeys = await idb.getAllKeys("chatgpt-api-web");
const result: ChatStoreSearchResult[] = [];
for (const key of resultKeys) {
@@ -91,7 +93,7 @@ export default function Search(props: {
);
if (now !== searchingNow) setSearchingNow(now);
const value: ChatStore = await db.get("chatgpt-api-web", key);
const value: ChatStore = await idb.get("chatgpt-api-web", key);
let preview: string = "";
for (const msg of value.history) {
@@ -157,7 +159,7 @@ export default function Search(props: {
className="flex justify-start p-1 m-1 rounded border bg-base-200 cursor-pointer"
key={result.key as number}
onClick={() => {
props.setSelectedChatIndex(parseInt(result.key.toString()));
setSelectedChatIndex(parseInt(result.key.toString()));
props.setShow(false);
}}
>