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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
import { openDB } from "idb"; import { IDBPDatabase, openDB } from "idb";
import { useEffect, useState } from "react"; import { createContext, useEffect, useState } from "react";
import "@/global.css"; import "@/global.css";
import { calculate_token_length } from "@/chatgpt"; import { calculate_token_length } from "@/chatgpt";
@@ -14,6 +14,43 @@ import { STORAGE_NAME, STORAGE_NAME_SELECTED } from "@/const";
import { upgrade } from "@/indexedDB/upgrade"; import { upgrade } from "@/indexedDB/upgrade";
import { getTotalCost } from "@/utils/totalCost"; 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 { import {
Sidebar, Sidebar,
SidebarContent, SidebarContent,
@@ -247,6 +284,77 @@ export function App() {
run(); 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 ( return (
<> <>
<Sidebar> <Sidebar>
@@ -410,13 +518,29 @@ export function App() {
</div> </div>
</div> </div>
</header> </header>
<ChatBOX <AppContext.Provider
db={db} value={{
chatStore={chatStore} db,
setChatStore={setChatStore} chatStore,
selectedChatIndex={selectedChatIndex} setChatStore,
setSelectedChatIndex={setSelectedChatIndex} selectedChatIndex,
/> setSelectedChatIndex,
templates,
setTemplates,
templateAPIs,
setTemplateAPIs,
templateAPIsWhisper,
setTemplateAPIsWhisper,
templateAPIsTTS,
setTemplateAPIsTTS,
templateAPIsImageGen,
setTemplateAPIsImageGen,
templateTools,
setTemplateTools,
}}
>
<ChatBOX />
</AppContext.Provider>
</SidebarInset> </SidebarInset>
</> </>
); );

View File

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

View File

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