refactor: seperate AppContext and AppChatStoreContext

This commit is contained in:
2025-01-08 01:09:50 +08:00
parent 20a152b899
commit b68224b13b
14 changed files with 328 additions and 290 deletions

View File

@@ -15,7 +15,7 @@ import {
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
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 { AppContext } from "@/pages/App"; import { AppChatStoreContext, AppContext } from "@/pages/App";
import { PaintBucketIcon } from "lucide-react"; import { PaintBucketIcon } from "lucide-react";
interface Props { interface Props {
@@ -27,7 +27,7 @@ interface ImageResponse {
revised_prompt: string; revised_prompt: string;
} }
export function ImageGenDrawer({ disableFactor }: Props) { export function ImageGenDrawer({ disableFactor }: Props) {
const ctx = useContext(AppContext); const { chatStore, setChatStore } = useContext(AppChatStoreContext);
const [showGenImage, setShowGenImage] = useState(false); const [showGenImage, setShowGenImage] = useState(false);
const [imageGenPrompt, setImageGenPrompt] = useState(""); const [imageGenPrompt, setImageGenPrompt] = useState("");
@@ -42,7 +42,7 @@ export function ImageGenDrawer({ disableFactor }: Props) {
useState("b64_json"); useState("b64_json");
return ( return (
<> <>
{ctx.chatStore.image_gen_api && ctx.chatStore.image_gen_key ? ( {chatStore.image_gen_api && chatStore.image_gen_key ? (
<Drawer open={showGenImage} onOpenChange={setShowGenImage}> <Drawer open={showGenImage} onOpenChange={setShowGenImage}>
<DrawerTrigger> <DrawerTrigger>
<Button <Button
@@ -169,11 +169,11 @@ export function ImageGenDrawer({ disableFactor }: Props) {
body.style = imageGenStyle; body.style = imageGenStyle;
} }
const resp: ImageResponse[] = ( const resp: ImageResponse[] = (
await fetch(ctx.chatStore.image_gen_api, { await fetch(chatStore.image_gen_api, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: `Bearer ${ctx.chatStore.image_gen_key}`, Authorization: `Bearer ${chatStore.image_gen_key}`,
}, },
body: JSON.stringify(body), body: JSON.stringify(body),
}).then((resp) => resp.json()) }).then((resp) => resp.json())
@@ -187,7 +187,7 @@ export function ImageGenDrawer({ disableFactor }: Props) {
url = "data:image/png;base64," + image.b64_json; url = "data:image/png;base64," + image.b64_json;
if (!url) continue; if (!url) continue;
ctx.chatStore.history.push({ chatStore.history.push({
role: "assistant", role: "assistant",
content: [ content: [
{ {
@@ -210,7 +210,7 @@ export function ImageGenDrawer({ disableFactor }: Props) {
response_model_name: imageGenModel, response_model_name: imageGenModel,
}); });
ctx.setChatStore({ ...ctx.chatStore }); setChatStore({ ...chatStore });
} }
} catch (e) { } catch (e) {
console.error(e); console.error(e);

View File

@@ -11,7 +11,7 @@ import {
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 { useContext } from "react";
import { AppContext } from "@/pages/App"; import { AppChatStoreContext, AppContext } from "@/pages/App";
import { import {
NavigationMenu, NavigationMenu,
NavigationMenuList, NavigationMenuList,
@@ -37,26 +37,26 @@ import { newChatStore } from "@/types/newChatstore";
interface APITemplateDropdownProps { interface APITemplateDropdownProps {
label: string; label: string;
shortLabel: string; shortLabel: string;
ctx: any;
apiField: string; apiField: string;
keyField: string; keyField: string;
} }
function APIsDropdownList({ function APIsDropdownList({
label, label,
shortLabel, shortLabel,
ctx,
apiField, apiField,
keyField, keyField,
}: APITemplateDropdownProps) { }: APITemplateDropdownProps) {
let API = ctx.templateAPIs; const ctxAppChatStore = useContext(AppChatStoreContext);
const ctxApp = useContext(AppContext);
let API = ctxApp.templateAPIs;
if (label === "Chat API") { if (label === "Chat API") {
API = ctx.templateAPIs; API = ctxApp.templateAPIs;
} else if (label === "Whisper API") { } else if (label === "Whisper API") {
API = ctx.templateAPIsWhisper; API = ctxApp.templateAPIsWhisper;
} else if (label === "TTS API") { } else if (label === "TTS API") {
API = ctx.templateAPIsTTS; API = ctxApp.templateAPIsTTS;
} else if (label === "Image Gen API") { } else if (label === "Image Gen API") {
API = ctx.templateAPIsImageGen; API = ctxApp.templateAPIsImageGen;
} }
return ( return (
@@ -67,14 +67,17 @@ function APIsDropdownList({
{label}{" "} {label}{" "}
{API.find( {API.find(
(t: TemplateAPI) => (t: TemplateAPI) =>
ctx.chatStore[apiField as keyof ChatStore] === t.endpoint && ctxAppChatStore.chatStore[apiField as keyof ChatStore] ===
ctx.chatStore[keyField as keyof ChatStore] === t.key t.endpoint &&
ctxAppChatStore.chatStore[keyField as keyof ChatStore] === t.key
)?.name && )?.name &&
`: ${ `: ${
API.find( API.find(
(t: TemplateAPI) => (t: TemplateAPI) =>
ctx.chatStore[apiField as keyof ChatStore] === t.endpoint && ctxAppChatStore.chatStore[apiField as keyof ChatStore] ===
ctx.chatStore[keyField as keyof ChatStore] === t.key t.endpoint &&
ctxAppChatStore.chatStore[keyField as keyof ChatStore] ===
t.key
)?.name )?.name
}`} }`}
</span> </span>
@@ -87,15 +90,20 @@ function APIsDropdownList({
<a <a
onClick={() => { onClick={() => {
// @ts-ignore // @ts-ignore
ctx.chatStore[apiField as keyof ChatStore] = t.endpoint; ctxAppChatStore.chatStore[apiField as keyof ChatStore] =
t.endpoint;
// @ts-ignore // @ts-ignore
ctx.chatStore[keyField] = t.key; ctxAppChatStore.chatStore[keyField] = t.key;
ctx.setChatStore({ ...ctx.chatStore }); ctxAppChatStore.setChatStore({
...ctxAppChatStore.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",
ctx.chatStore[apiField as keyof ChatStore] === t.endpoint && ctxAppChatStore.chatStore[apiField as keyof ChatStore] ===
ctx.chatStore[keyField as keyof ChatStore] === t.key t.endpoint &&
ctxAppChatStore.chatStore[keyField as keyof ChatStore] ===
t.key
? "bg-accent text-accent-foreground" ? "bg-accent text-accent-foreground"
: "" : ""
)} )}
@@ -117,11 +125,11 @@ function APIsDropdownList({
} }
function ToolsDropdownList() { function ToolsDropdownList() {
const ctx = useContext(AppContext); const { chatStore, setChatStore } = useContext(AppChatStoreContext);
const { toast } = useToast(); const { toast } = useToast();
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
const { chatStore, setChatStore } = ctx; const ctx = useContext(AppContext);
return ( return (
<div className="flex items-center space-x-4 mx-3"> <div className="flex items-center space-x-4 mx-3">
@@ -189,7 +197,8 @@ function ToolsDropdownList() {
function ChatTemplateDropdownList() { function ChatTemplateDropdownList() {
const ctx = useContext(AppContext); const ctx = useContext(AppContext);
const { chatStore, setChatStore, templates } = ctx; const { chatStore, setChatStore } = useContext(AppChatStoreContext);
const { templates } = useContext(AppContext);
return ( return (
<NavigationMenuItem> <NavigationMenuItem>
@@ -245,7 +254,6 @@ const APIListMenu: React.FC = () => {
<APIsDropdownList <APIsDropdownList
label="Chat API" label="Chat API"
shortLabel="Chat" shortLabel="Chat"
ctx={ctx}
apiField="apiEndpoint" apiField="apiEndpoint"
keyField="apiKey" keyField="apiKey"
/> />
@@ -254,7 +262,6 @@ const APIListMenu: React.FC = () => {
<APIsDropdownList <APIsDropdownList
label="Whisper API" label="Whisper API"
shortLabel="Whisper" shortLabel="Whisper"
ctx={ctx}
apiField="whisper_api" apiField="whisper_api"
keyField="whisper_key" keyField="whisper_key"
/> />
@@ -263,7 +270,6 @@ const APIListMenu: React.FC = () => {
<APIsDropdownList <APIsDropdownList
label="TTS API" label="TTS API"
shortLabel="TTS" shortLabel="TTS"
ctx={ctx}
apiField="tts_api" apiField="tts_api"
keyField="tts_key" keyField="tts_key"
/> />
@@ -272,7 +278,6 @@ const APIListMenu: React.FC = () => {
<APIsDropdownList <APIsDropdownList
label="Image Gen API" label="Image Gen API"
shortLabel="ImgGen" shortLabel="ImgGen"
ctx={ctx}
apiField="image_gen_api" apiField="image_gen_api"
keyField="image_gen_key" keyField="image_gen_key"
/> />

View File

@@ -25,7 +25,7 @@ import {
AudioLinesIcon, AudioLinesIcon,
LoaderCircleIcon, LoaderCircleIcon,
} from "lucide-react"; } from "lucide-react";
import { AppContext } from "@/pages/App"; import { AppChatStoreContext, AppContext } from "@/pages/App";
interface HideMessageProps { interface HideMessageProps {
chat: ChatStoreMessage; chat: ChatStoreMessage;
@@ -171,27 +171,27 @@ export function TTSPlay(props: TTSPlayProps) {
} }
function TTSButton(props: TTSProps) { function TTSButton(props: TTSProps) {
const [generating, setGenerating] = useState(false); const [generating, setGenerating] = useState(false);
const ctx = useContext(AppContext); const { chatStore, setChatStore } = useContext(AppChatStoreContext);
return ( return (
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
onClick={() => { onClick={() => {
const api = ctx.chatStore.tts_api; const api = chatStore.tts_api;
const api_key = ctx.chatStore.tts_key; const api_key = chatStore.tts_key;
const model = "tts-1"; const model = "tts-1";
const input = getMessageText(props.chat); const input = getMessageText(props.chat);
const voice = ctx.chatStore.tts_voice; const voice = chatStore.tts_voice;
const body: Record<string, any> = { const body: Record<string, any> = {
model, model,
input, input,
voice, voice,
response_format: ctx.chatStore.tts_format || "mp3", response_format: chatStore.tts_format || "mp3",
}; };
if (ctx.chatStore.tts_speed_enabled) { if (chatStore.tts_speed_enabled) {
body["speed"] = ctx.chatStore.tts_speed; body["speed"] = chatStore.tts_speed;
} }
setGenerating(true); setGenerating(true);
@@ -208,13 +208,13 @@ function TTSButton(props: TTSProps) {
.then((blob) => { .then((blob) => {
// update price // update price
const cost = (input.length * 0.015) / 1000; const cost = (input.length * 0.015) / 1000;
ctx.chatStore.cost += cost; chatStore.cost += cost;
addTotalCost(cost); addTotalCost(cost);
ctx.setChatStore({ ...ctx.chatStore }); setChatStore({ ...chatStore });
// save blob // save blob
props.chat.audio = blob; props.chat.audio = blob;
ctx.setChatStore({ ...ctx.chatStore }); setChatStore({ ...chatStore });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const audio = new Audio(url); const audio = new Audio(url);
@@ -235,9 +235,8 @@ function TTSButton(props: TTSProps) {
} }
export default function Message(props: { messageIndex: number }) { export default function Message(props: { messageIndex: number }) {
const ctx = useContext(AppContext);
const { messageIndex } = props; const { messageIndex } = props;
const { chatStore, setChatStore } = ctx; const { chatStore, setChatStore } = useContext(AppChatStoreContext);
const chat = chatStore.history[messageIndex]; const chat = chatStore.history[messageIndex];
const [showEdit, setShowEdit] = useState(false); const [showEdit, setShowEdit] = useState(false);
@@ -301,8 +300,7 @@ export default function Message(props: { messageIndex: number }) {
<Markdown>{getMessageText(chat)}</Markdown> <Markdown>{getMessageText(chat)}</Markdown>
) : ( ) : (
<div className="message-content"> <div className="message-content">
{ctx && {chat.content &&
chat.content &&
(chat.logprobs && renderColor (chat.logprobs && renderColor
? chat.logprobs.content ? chat.logprobs.content
.filter((c) => c.token) .filter((c) => c.token)

View File

@@ -21,7 +21,7 @@ import {
} from "@/components/ui/pagination"; } from "@/components/ui/pagination";
import { Input } from "./ui/input"; import { Input } from "./ui/input";
import { AppContext } from "../pages/App"; import { App, AppContext } from "../pages/App";
import { Button } from "./ui/button"; import { Button } from "./ui/button";
import { SearchIcon } from "lucide-react"; import { SearchIcon } from "lucide-react";
@@ -33,8 +33,7 @@ interface ChatStoreSearchResult {
} }
export default function Search() { export default function Search() {
const ctx = useContext(AppContext); const { setSelectedChatIndex, db } = useContext(AppContext);
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);

View File

@@ -75,7 +75,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 { NonOverflowScrollArea, ScrollArea } from "@/components/ui/scroll-area"; import { NonOverflowScrollArea, ScrollArea } from "@/components/ui/scroll-area";
import { AppContext } from "@/pages/App"; import { AppChatStoreContext, AppContext } from "@/pages/App";
const TTS_VOICES: string[] = [ const TTS_VOICES: string[] = [
"alloy", "alloy",
@@ -96,11 +96,11 @@ const Help = (props: { children: any; help: string; field: string }) => {
}; };
const SelectModel = (props: { help: string }) => { const SelectModel = (props: { help: string }) => {
const ctx = useContext(AppContext); const { chatStore, setChatStore } = useContext(AppChatStoreContext);
let shouldIUseCustomModel: boolean = true; let shouldIUseCustomModel: boolean = true;
for (const model in models) { for (const model in models) {
if (ctx.chatStore.model === model) { if (chatStore.model === model) {
shouldIUseCustomModel = false; shouldIUseCustomModel = false;
} }
} }
@@ -140,22 +140,22 @@ const SelectModel = (props: { help: string }) => {
{useCustomModel ? ( {useCustomModel ? (
<Input <Input
value={ctx.chatStore.model} value={chatStore.model}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => { onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
ctx.chatStore.model = e.target.value; chatStore.model = e.target.value;
ctx.setChatStore({ ...ctx.chatStore }); setChatStore({ ...chatStore });
}} }}
/> />
) : ( ) : (
<Select <Select
value={ctx.chatStore.model} value={chatStore.model}
onValueChange={(model: string) => { onValueChange={(model: string) => {
ctx.chatStore.model = model; chatStore.model = model;
ctx.chatStore.maxTokens = getDefaultParams( chatStore.maxTokens = getDefaultParams(
"max", "max",
models[model].maxToken models[model].maxToken
); );
ctx.setChatStore({ ...ctx.chatStore }); setChatStore({ ...chatStore });
}} }}
> >
<SelectTrigger className="w-full"> <SelectTrigger className="w-full">
@@ -182,7 +182,7 @@ const LongInput = (props: {
label: string; label: string;
help: string; help: string;
}) => { }) => {
const ctx = useContext(AppContext); const { chatStore, setChatStore } = useContext(AppChatStoreContext);
return ( return (
<div> <div>
<Label htmlFor="name" className="text-right"> <Label htmlFor="name" className="text-right">
@@ -204,10 +204,10 @@ const LongInput = (props: {
<Textarea <Textarea
className="h-24 w-full" className="h-24 w-full"
value={ctx.chatStore[props.field]} value={chatStore[props.field]}
onChange={(event: any) => { onChange={(event: any) => {
ctx.chatStore[props.field] = event.target.value; chatStore[props.field] = event.target.value;
ctx.setChatStore({ ...ctx.chatStore }); setChatStore({ ...chatStore });
autoHeight(event.target); autoHeight(event.target);
}} }}
onKeyPress={(event: any) => { onKeyPress={(event: any) => {
@@ -230,7 +230,7 @@ const InputField = (props: {
| "image_gen_key"; | "image_gen_key";
help: string; help: string;
}) => { }) => {
const ctx = useContext(AppContext); const { chatStore, setChatStore } = useContext(AppChatStoreContext);
const [hideInput, setHideInput] = useState(true); const [hideInput, setHideInput] = useState(true);
return ( return (
<> <>
@@ -256,10 +256,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={ctx.chatStore[props.field]} value={chatStore[props.field]}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => { onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
ctx.chatStore[props.field] = event.target.value; chatStore[props.field] = event.target.value;
ctx.setChatStore({ ...ctx.chatStore }); setChatStore({ ...chatStore });
}} }}
/> />
<Button <Button
@@ -285,24 +285,24 @@ const Slicer = (props: {
min: number; min: number;
max: number; max: number;
}) => { }) => {
const ctx = useContext(AppContext); const { chatStore, setChatStore } = useContext(AppChatStoreContext);
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 = ctx.chatStore[enable_filed_name]; const enabled = chatStore[enable_filed_name];
if (enabled === null || enabled === undefined) { if (enabled === null || enabled === undefined) {
if (props.field === "temperature") { if (props.field === "temperature") {
ctx.chatStore[enable_filed_name] = true; chatStore[enable_filed_name] = true;
} }
if (props.field === "top_p") { if (props.field === "top_p") {
ctx.chatStore[enable_filed_name] = false; chatStore[enable_filed_name] = false;
} }
} }
const setEnabled = (state: boolean) => { const setEnabled = (state: boolean) => {
ctx.chatStore[enable_filed_name] = state; chatStore[enable_filed_name] = state;
ctx.setChatStore({ ...ctx.chatStore }); setChatStore({ ...chatStore });
}; };
return ( return (
<div className="space-y-2"> <div className="space-y-2">
@@ -323,10 +323,10 @@ const Slicer = (props: {
</DialogContent> </DialogContent>
</Dialog> </Dialog>
<Checkbox <Checkbox
checked={ctx.chatStore[enable_filed_name]} checked={chatStore[enable_filed_name]}
onCheckedChange={(checked: boolean) => setEnabled(!!checked)} onCheckedChange={(checked: boolean) => setEnabled(!!checked)}
/> />
{!ctx.chatStore[enable_filed_name] && ( {!chatStore[enable_filed_name] && (
<span className="text-xs text-muted-foreground">disabled</span> <span className="text-xs text-muted-foreground">disabled</span>
)} )}
</Label> </Label>
@@ -339,10 +339,10 @@ const Slicer = (props: {
min={props.min} min={props.min}
max={props.max} max={props.max}
step={0.01} step={0.01}
value={[ctx.chatStore[props.field]]} value={[chatStore[props.field]]}
onValueChange={(value) => { onValueChange={(value) => {
ctx.chatStore[props.field] = value[0]; chatStore[props.field] = value[0];
ctx.setChatStore({ ...ctx.chatStore }); setChatStore({ ...chatStore });
}} }}
/> />
</div> </div>
@@ -350,11 +350,11 @@ const Slicer = (props: {
type="number" type="number"
disabled={!enabled} disabled={!enabled}
className="w-24" className="w-24"
value={ctx.chatStore[props.field]} value={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);
ctx.chatStore[props.field] = value; chatStore[props.field] = value;
ctx.setChatStore({ ...ctx.chatStore }); setChatStore({ ...chatStore });
}} }}
/> />
</div> </div>
@@ -375,7 +375,7 @@ const Number = (props: {
readOnly: boolean; readOnly: boolean;
help: string; help: string;
}) => { }) => {
const ctx = useContext(AppContext); const { chatStore, setChatStore } = useContext(AppChatStoreContext);
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">
@@ -397,12 +397,12 @@ const Number = (props: {
{props.field === "maxGenTokens" && ( {props.field === "maxGenTokens" && (
<Checkbox <Checkbox
checked={ctx.chatStore.maxGenTokens_enabled} checked={chatStore.maxGenTokens_enabled}
onCheckedChange={() => { onCheckedChange={() => {
const newChatStore = { ...ctx.chatStore }; const newChatStore = { ...chatStore };
newChatStore.maxGenTokens_enabled = newChatStore.maxGenTokens_enabled =
!newChatStore.maxGenTokens_enabled; !newChatStore.maxGenTokens_enabled;
ctx.setChatStore({ ...newChatStore }); setChatStore({ ...newChatStore });
}} }}
/> />
)} )}
@@ -412,14 +412,14 @@ const Number = (props: {
type="number" type="number"
readOnly={props.readOnly} readOnly={props.readOnly}
disabled={ disabled={
props.field === "maxGenTokens" && !ctx.chatStore.maxGenTokens_enabled props.field === "maxGenTokens" && !chatStore.maxGenTokens_enabled
} }
value={ctx.chatStore[props.field]} value={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;
ctx.chatStore[props.field] = newNumber; chatStore[props.field] = newNumber;
ctx.setChatStore({ ...ctx.chatStore }); setChatStore({ ...chatStore });
}} }}
/> />
</div> </div>
@@ -430,17 +430,17 @@ const Choice = (props: {
field: "streamMode" | "develop_mode" | "json_mode" | "logprobs"; field: "streamMode" | "develop_mode" | "json_mode" | "logprobs";
help: string; help: string;
}) => { }) => {
const ctx = useContext(AppContext); const { chatStore, setChatStore } = useContext(AppChatStoreContext);
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={ctx.chatStore[props.field]} checked={chatStore[props.field]}
onCheckedChange={(checked: boolean) => { onCheckedChange={(checked: boolean) => {
ctx.chatStore[props.field] = checked; chatStore[props.field] = checked;
ctx.setChatStore({ ...ctx.chatStore }); setChatStore({ ...chatStore });
}} }}
/> />
</div> </div>
@@ -468,13 +468,27 @@ const Choice = (props: {
}; };
const APIShowBlock = (props: { const APIShowBlock = (props: {
ctx: any;
index: number; index: number;
label: string; label: string;
type: string; type: string;
apiField: string; apiField: string;
keyField: string; keyField: string;
}) => { }) => {
const {
templates,
setTemplates,
templateAPIs,
setTemplateAPIs,
templateAPIsWhisper,
setTemplateAPIsWhisper,
templateAPIsTTS,
setTemplateAPIsTTS,
templateAPIsImageGen,
setTemplateAPIsImageGen,
templateTools,
setTemplateTools,
selectedChatIndex,
} = useContext(AppContext);
return ( return (
<div className="border-b border-gray-200 pb-4 pt-4"> <div className="border-b border-gray-200 pb-4 pt-4">
<Badge variant="outline">{props.type}</Badge> <Label>{props.label}</Label> <Badge variant="outline">{props.type}</Badge> <Label>{props.label}</Label>
@@ -506,23 +520,17 @@ const APIShowBlock = (props: {
const name = prompt(`Give template ${props.label} a new name`); const name = prompt(`Give template ${props.label} a new name`);
if (!name) return; if (!name) return;
if (props.type === "Chat") { if (props.type === "Chat") {
props.ctx.templateAPIs[props.index].name = name; templateAPIs[props.index].name = name;
props.ctx.setTemplateAPIs(structuredClone(props.ctx.templateAPIs)); setTemplateAPIs(structuredClone(templateAPIs));
} else if (props.type === "Whisper") { } else if (props.type === "Whisper") {
props.ctx.templateAPIsWhisper[props.index].name = name; templateAPIsWhisper[props.index].name = name;
props.ctx.setTemplateAPIsWhisper( setTemplateAPIsWhisper(structuredClone(templateAPIsWhisper));
structuredClone(props.ctx.templateAPIsWhisper)
);
} else if (props.type === "TTS") { } else if (props.type === "TTS") {
props.ctx.templateAPIsTTS[props.index].name = name; templateAPIsTTS[props.index].name = name;
props.ctx.setTemplateAPIsTTS( setTemplateAPIsTTS(structuredClone(templateAPIsTTS));
structuredClone(props.ctx.templateAPIsTTS)
);
} else if (props.type === "ImgGen") { } else if (props.type === "ImgGen") {
props.ctx.templateAPIsImageGen[props.index].name = name; templateAPIsImageGen[props.index].name = name;
props.ctx.setTemplateAPIsImageGen( setTemplateAPIsImageGen(structuredClone(templateAPIsImageGen));
structuredClone(props.ctx.templateAPIsImageGen)
);
} }
}} }}
> >
@@ -533,7 +541,6 @@ const APIShowBlock = (props: {
size="sm" size="sm"
className="mt-2" className="mt-2"
onClick={() => { onClick={() => {
if (!props.ctx) return;
if ( if (
!confirm( !confirm(
`Are you sure to delete ${props.label}(${props.type}) API?` `Are you sure to delete ${props.label}(${props.type}) API?`
@@ -542,23 +549,17 @@ const APIShowBlock = (props: {
return; return;
} }
if (props.type === "Chat") { if (props.type === "Chat") {
props.ctx.templateAPIs.splice(props.index, 1); templateAPIs.splice(props.index, 1);
props.ctx.setTemplateAPIs(structuredClone(props.ctx.templateAPIs)); setTemplateAPIs(structuredClone(templateAPIs));
} else if (props.type === "Whisper") { } else if (props.type === "Whisper") {
props.ctx.templateAPIsWhisper.splice(props.index, 1); templateAPIsWhisper.splice(props.index, 1);
props.ctx.setTemplateAPIsWhisper( setTemplateAPIsWhisper(structuredClone(templateAPIsWhisper));
structuredClone(props.ctx.templateAPIsWhisper)
);
} else if (props.type === "TTS") { } else if (props.type === "TTS") {
props.ctx.templateAPIsTTS.splice(props.index, 1); templateAPIsTTS.splice(props.index, 1);
props.ctx.setTemplateAPIsTTS( setTemplateAPIsTTS(structuredClone(templateAPIsTTS));
structuredClone(props.ctx.templateAPIsTTS)
);
} else if (props.type === "ImgGen") { } else if (props.type === "ImgGen") {
props.ctx.templateAPIsImageGen.splice(props.index, 1); templateAPIsImageGen.splice(props.index, 1);
props.ctx.setTemplateAPIsImageGen( setTemplateAPIsImageGen(structuredClone(templateAPIsImageGen));
structuredClone(props.ctx.templateAPIsImageGen)
);
} }
}} }}
> >
@@ -569,11 +570,25 @@ const APIShowBlock = (props: {
}; };
const ToolsShowBlock = (props: { const ToolsShowBlock = (props: {
ctx: any;
index: number; index: number;
label: string; label: string;
content: string; content: string;
}) => { }) => {
const {
templates,
setTemplates,
templateAPIs,
setTemplateAPIs,
templateAPIsWhisper,
setTemplateAPIsWhisper,
templateAPIsTTS,
setTemplateAPIsTTS,
templateAPIsImageGen,
setTemplateAPIsImageGen,
templateTools,
setTemplateTools,
selectedChatIndex,
} = useContext(AppContext);
return ( return (
<div className="border-b border-gray-200 pb-4 pt-4"> <div className="border-b border-gray-200 pb-4 pt-4">
<Badge variant="outline">Tool</Badge> <Label>{props.label}</Label> <Badge variant="outline">Tool</Badge> <Label>{props.label}</Label>
@@ -594,8 +609,8 @@ const ToolsShowBlock = (props: {
onClick={() => { onClick={() => {
const name = prompt(`Give the tool ${props.label} a new name`); const name = prompt(`Give the tool ${props.label} a new name`);
if (!name) return; if (!name) return;
props.ctx.templateTools[props.index].name = name; templateTools[props.index].name = name;
props.ctx.setTemplateTools(structuredClone(props.ctx.templateTools)); setTemplateTools(structuredClone(templateTools));
}} }}
> >
Edit Edit
@@ -605,12 +620,11 @@ const ToolsShowBlock = (props: {
size="sm" size="sm"
className="mt-2" className="mt-2"
onClick={() => { onClick={() => {
if (!props.ctx) return;
if (!confirm(`Are you sure to delete ${props.label} Tool?`)) { if (!confirm(`Are you sure to delete ${props.label} Tool?`)) {
return; return;
} }
props.ctx.templateTools.splice(props.index, 1); templateTools.splice(props.index, 1);
props.ctx.setTemplateTools(structuredClone(props.ctx.templateTools)); setTemplateTools(structuredClone(templateTools));
}} }}
> >
Delete Delete
@@ -620,19 +634,34 @@ const ToolsShowBlock = (props: {
}; };
export default (props: {}) => { export default (props: {}) => {
const ctx = useContext(AppContext); const { chatStore, setChatStore } = useContext(AppChatStoreContext);
const {
templates,
setTemplates,
templateAPIs,
setTemplateAPIs,
templateAPIsWhisper,
setTemplateAPIsWhisper,
templateAPIsTTS,
setTemplateAPIsTTS,
templateAPIsImageGen,
setTemplateAPIsImageGen,
templateTools,
setTemplateTools,
selectedChatIndex,
} = useContext(AppContext);
let link = let link =
location.protocol + location.protocol +
"//" + "//" +
location.host + location.host +
location.pathname + location.pathname +
`?key=${encodeURIComponent(ctx.chatStore.apiKey)}&api=${encodeURIComponent( `?key=${encodeURIComponent(chatStore.apiKey)}&api=${encodeURIComponent(
ctx.chatStore.apiEndpoint chatStore.apiEndpoint
)}&mode=${ctx.chatStore.streamMode ? "stream" : "fetch"}&model=${ )}&mode=${chatStore.streamMode ? "stream" : "fetch"}&model=${
ctx.chatStore.model chatStore.model
}&sys=${encodeURIComponent(ctx.chatStore.systemMessageContent)}`; }&sys=${encodeURIComponent(chatStore.systemMessageContent)}`;
if (ctx.chatStore.develop_mode) { if (chatStore.develop_mode) {
link = link + `&dev=true`; link = link + `&dev=true`;
} }
@@ -662,7 +691,7 @@ export default (props: {}) => {
<SheetTrigger asChild> <SheetTrigger asChild>
<Button variant="outline" className="flex-grow"> <Button variant="outline" className="flex-grow">
{Tr("Settings")} {Tr("Settings")}
{(!ctx.chatStore.apiKey || !ctx.chatStore.apiEndpoint) && ( {(!chatStore.apiKey || !chatStore.apiEndpoint) && (
<TriangleAlertIcon className="w-4 h-4 ml-1 text-yellow-500" /> <TriangleAlertIcon className="w-4 h-4 ml-1 text-yellow-500" />
)} )}
</Button> </Button>
@@ -693,7 +722,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">
{ctx.chatStore.cost?.toFixed(4)} {chatStore.cost?.toFixed(4)}
</span> </span>
</div> </div>
</div> </div>
@@ -714,7 +743,7 @@ export default (props: {}) => {
/> />
<span className="pt-1"> <span className="pt-1">
JSON Check:{" "} JSON Check:{" "}
{isVailedJSON(ctx.chatStore.toolsString) ? ( {isVailedJSON(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" />
@@ -722,7 +751,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">
{ctx.chatStore.toolsString.trim() && ( {chatStore.toolsString.trim() && (
<Dialog> <Dialog>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="outline">{Tr(`Save Tools`)}</Button> <Button variant="outline">{Tr(`Save Tools`)}</Button>
@@ -772,10 +801,10 @@ export default (props: {}) => {
} }
const newToolsTmp: TemplateTools = { const newToolsTmp: TemplateTools = {
name: name.value, name: name.value,
toolsString: ctx.chatStore.toolsString, toolsString: chatStore.toolsString,
}; };
ctx.templateTools.push(newToolsTmp); templateTools.push(newToolsTmp);
ctx.setTemplateTools([...ctx.templateTools]); setTemplateTools([...templateTools]);
}} }}
> >
<SaveIcon className="w-4 h-4" /> Save <SaveIcon className="w-4 h-4" /> Save
@@ -884,12 +913,11 @@ export default (props: {}) => {
<Button <Button
variant="destructive" variant="destructive"
onClick={() => { onClick={() => {
ctx.chatStore.history = chatStore.history = chatStore.history.filter(
ctx.chatStore.history.filter( (msg) => msg.example && !msg.hide
(msg) => msg.example && !msg.hide );
); chatStore.postBeginIndex = 0;
ctx.chatStore.postBeginIndex = 0; setChatStore({ ...chatStore });
ctx.setChatStore({ ...ctx.chatStore });
}} }}
> >
Yes, clear all history Yes, clear all history
@@ -905,14 +933,14 @@ export default (props: {}) => {
let dataStr = let dataStr =
"data:text/json;charset=utf-8," + "data:text/json;charset=utf-8," +
encodeURIComponent( encodeURIComponent(
JSON.stringify(ctx.chatStore, null, "\t") JSON.stringify(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-${ctx.selectedChatIndex}.json` `chatgpt-api-web-${selectedChatIndex}.json`
); );
document.body.appendChild(downloadAnchorNode); document.body.appendChild(downloadAnchorNode);
downloadAnchorNode.click(); downloadAnchorNode.click();
@@ -933,14 +961,12 @@ export default (props: {}) => {
alert(tr("No template name specified", langCode)); alert(tr("No template name specified", langCode));
return; return;
} }
const tmp: ChatStore = structuredClone( const tmp: ChatStore = structuredClone(chatStore);
ctx.chatStore
);
tmp.history = tmp.history.filter((h) => h.example); tmp.history = tmp.history.filter((h) => h.example);
// @ts-ignore // @ts-ignore
tmp.name = name; tmp.name = name;
ctx.templates.push(tmp as TemplateChatStore); templates.push(tmp as TemplateChatStore);
ctx.setTemplates([...ctx.templates]); setTemplates([...templates]);
}} }}
> >
{Tr("As template")} {Tr("As template")}
@@ -992,7 +1018,7 @@ export default (props: {}) => {
langCode langCode
); );
} }
ctx.setChatStore({ ...newChatStore }); setChatStore({ ...newChatStore });
} catch (e) { } catch (e) {
alert( alert(
tr( tr(
@@ -1034,10 +1060,10 @@ export default (props: {}) => {
/> />
<SetAPIsTemplate <SetAPIsTemplate
label="Chat API" label="Chat API"
endpoint={ctx.chatStore.apiEndpoint} endpoint={chatStore.apiEndpoint}
APIkey={ctx.chatStore.apiKey} APIkey={chatStore.apiKey}
temps={ctx.templateAPIs} temps={templateAPIs}
setTemps={ctx.setTemplateAPIs} setTemps={setTemplateAPIs}
/> />
</CardContent> </CardContent>
</Card> </Card>
@@ -1139,10 +1165,10 @@ export default (props: {}) => {
/> />
<SetAPIsTemplate <SetAPIsTemplate
label="Whisper API" label="Whisper API"
endpoint={ctx.chatStore.whisper_api} endpoint={chatStore.whisper_api}
APIkey={ctx.chatStore.whisper_key} APIkey={chatStore.whisper_key}
temps={ctx.templateAPIsWhisper} temps={templateAPIsWhisper}
setTemps={ctx.setTemplateAPIsWhisper} setTemps={setTemplateAPIsWhisper}
/> />
</CardContent> </CardContent>
</Card> </Card>
@@ -1172,10 +1198,10 @@ export default (props: {}) => {
/> />
<SetAPIsTemplate <SetAPIsTemplate
label="TTS API" label="TTS API"
endpoint={ctx.chatStore.tts_api} endpoint={chatStore.tts_api}
APIkey={ctx.chatStore.tts_key} APIkey={chatStore.tts_key}
temps={ctx.templateAPIsTTS} temps={templateAPIsTTS}
setTemps={ctx.setTemplateAPIsTTS} setTemps={setTemplateAPIsTTS}
/> />
</CardContent> </CardContent>
</Card> </Card>
@@ -1200,10 +1226,10 @@ export default (props: {}) => {
</Dialog> </Dialog>
</Label> </Label>
<Select <Select
value={ctx.chatStore.tts_voice} value={chatStore.tts_voice}
onValueChange={(value) => { onValueChange={(value) => {
ctx.chatStore.tts_voice = value; chatStore.tts_voice = value;
ctx.setChatStore({ ...ctx.chatStore }); setChatStore({ ...chatStore });
}} }}
> >
<SelectTrigger className="w-full"> <SelectTrigger className="w-full">
@@ -1250,10 +1276,10 @@ export default (props: {}) => {
</Dialog> </Dialog>
</Label> </Label>
<Select <Select
value={ctx.chatStore.tts_format} value={chatStore.tts_format}
onValueChange={(value) => { onValueChange={(value) => {
ctx.chatStore.tts_format = value; chatStore.tts_format = value;
ctx.setChatStore({ ...ctx.chatStore }); setChatStore({ ...chatStore });
}} }}
> >
<SelectTrigger className="w-full"> <SelectTrigger className="w-full">
@@ -1297,10 +1323,10 @@ export default (props: {}) => {
/> />
<SetAPIsTemplate <SetAPIsTemplate
label="Image Gen API" label="Image Gen API"
endpoint={ctx.chatStore.image_gen_api} endpoint={chatStore.image_gen_api}
APIkey={ctx.chatStore.image_gen_key} APIkey={chatStore.image_gen_key}
temps={ctx.templateAPIsImageGen} temps={templateAPIsImageGen}
setTemps={ctx.setTemplateAPIsImageGen} setTemps={setTemplateAPIsImageGen}
/> />
</CardContent> </CardContent>
</Card> </Card>
@@ -1309,10 +1335,9 @@ export default (props: {}) => {
<AccordionItem value="templates"> <AccordionItem value="templates">
<AccordionTrigger>Saved Template</AccordionTrigger> <AccordionTrigger>Saved Template</AccordionTrigger>
<AccordionContent> <AccordionContent>
{ctx.templateAPIs.map((template, index) => ( {templateAPIs.map((template, index) => (
<div key={index}> <div key={index}>
<APIShowBlock <APIShowBlock
ctx={ctx}
index={index} index={index}
label={template.name} label={template.name}
type="Chat" type="Chat"
@@ -1321,10 +1346,9 @@ export default (props: {}) => {
/> />
</div> </div>
))} ))}
{ctx.templateAPIsWhisper.map((template, index) => ( {templateAPIsWhisper.map((template, index) => (
<div key={index}> <div key={index}>
<APIShowBlock <APIShowBlock
ctx={ctx}
index={index} index={index}
label={template.name} label={template.name}
type="Whisper" type="Whisper"
@@ -1333,10 +1357,9 @@ export default (props: {}) => {
/> />
</div> </div>
))} ))}
{ctx.templateAPIsTTS.map((template, index) => ( {templateAPIsTTS.map((template, index) => (
<div key={index}> <div key={index}>
<APIShowBlock <APIShowBlock
ctx={ctx}
index={index} index={index}
label={template.name} label={template.name}
type="TTS" type="TTS"
@@ -1345,10 +1368,9 @@ export default (props: {}) => {
/> />
</div> </div>
))} ))}
{ctx.templateAPIsImageGen.map((template, index) => ( {templateAPIsImageGen.map((template, index) => (
<div key={index}> <div key={index}>
<APIShowBlock <APIShowBlock
ctx={ctx}
index={index} index={index}
label={template.name} label={template.name}
type="ImgGen" type="ImgGen"
@@ -1357,10 +1379,9 @@ export default (props: {}) => {
/> />
</div> </div>
))} ))}
{ctx.templateTools.map((template, index) => ( {templateTools.map((template, index) => (
<div key={index}> <div key={index}>
<ToolsShowBlock <ToolsShowBlock
ctx={ctx}
index={index} index={index}
label={template.name} label={template.name}
content={template.toolsString} content={template.toolsString}
@@ -1373,7 +1394,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")}{" "}
{ctx.chatStore.chatgpt_api_web_version} {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,4 +1,4 @@
import { AppContext } from "@/pages/App"; import { AppChatStoreContext, 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";
@@ -6,7 +6,8 @@ import { useContext } from "react";
const Templates = () => { const Templates = () => {
const ctx = useContext(AppContext); const ctx = useContext(AppContext);
const { templates, chatStore, setChatStore, setTemplates } = ctx; const { templates, setTemplates } = useContext(AppContext);
const { chatStore, setChatStore } = useContext(AppChatStoreContext);
return ( return (
<> <>

View File

@@ -1,12 +1,10 @@
import { ChatStore } from "@/types/chatstore"; import { ChatStore } from "@/types/chatstore";
import { Tr } from "@/translate"; import { Tr } from "@/translate";
import { useContext } from "react"; import { useContext } from "react";
import { AppContext } from "@/pages/App"; import { AppChatStoreContext, AppContext } from "@/pages/App";
const VersionHint = () => { const VersionHint = () => {
const ctx = useContext(AppContext); const { chatStore } = useContext(AppChatStoreContext);
const { chatStore } = ctx;
return ( return (
<> <>
{chatStore.chatgpt_api_web_version < "v1.3.0" && ( {chatStore.chatgpt_api_web_version < "v1.3.0" && (

View File

@@ -3,14 +3,13 @@ import { createRef, useContext } from "react";
import { useState, Dispatch } from "react"; import { useState, Dispatch } from "react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { AudioWaveformIcon, CircleStopIcon, MicIcon } from "lucide-react"; import { AudioWaveformIcon, CircleStopIcon, MicIcon } from "lucide-react";
import { AppContext } from "@/pages/App"; import { AppChatStoreContext, AppContext } from "@/pages/App";
const WhisperButton = (props: { const WhisperButton = (props: {
inputMsg: string; inputMsg: string;
setInputMsg: Dispatch<string>; setInputMsg: Dispatch<string>;
}) => { }) => {
const ctx = useContext(AppContext); const { chatStore } = useContext(AppChatStoreContext);
const { chatStore } = ctx;
const { inputMsg, setInputMsg } = props; const { inputMsg, setInputMsg } = props;
const mediaRef = createRef(); const mediaRef = createRef();

View File

@@ -13,7 +13,7 @@ import {
DialogTrigger, DialogTrigger,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { Button } from "./ui/button"; import { Button } from "./ui/button";
import { AppContext } from "../pages/App"; import { AppChatStoreContext, AppContext } from "../pages/App";
interface EditMessageProps { interface EditMessageProps {
chat: ChatStoreMessage; chat: ChatStoreMessage;
@@ -21,7 +21,7 @@ interface EditMessageProps {
setShowEdit: Dispatch<boolean>; setShowEdit: Dispatch<boolean>;
} }
export function EditMessage(props: EditMessageProps) { export function EditMessage(props: EditMessageProps) {
const ctx = useContext(AppContext); const { chatStore, setChatStore } = useContext(AppChatStoreContext);
const { showEdit, setShowEdit, chat } = props; const { showEdit, setShowEdit, chat } = props;
@@ -42,7 +42,7 @@ export function EditMessage(props: EditMessageProps) {
) : ( ) : (
<EditMessageDetail chat={chat} setShowEdit={setShowEdit} /> <EditMessageDetail chat={chat} setShowEdit={setShowEdit} />
)} )}
{ctx.chatStore.develop_mode && ( {chatStore.develop_mode && (
<Button <Button
variant="destructive" variant="destructive"
className="w-full" className="w-full"
@@ -57,7 +57,7 @@ export function EditMessage(props: EditMessageProps) {
} else { } else {
chat.content = ""; chat.content = "";
} }
ctx.setChatStore({ ...ctx.chatStore }); setChatStore({ ...chatStore });
}} }}
> >
Switch to{" "} Switch to{" "}

View File

@@ -15,16 +15,14 @@ import {
import { Button } from "./ui/button"; import { Button } from "./ui/button";
import { useContext } from "react"; import { useContext } from "react";
import { AppContext } from "../pages/App"; import { AppChatStoreContext, AppContext } from "../pages/App";
interface Props { interface Props {
chat: ChatStoreMessage; chat: ChatStoreMessage;
setShowEdit: (se: boolean) => void; setShowEdit: (se: boolean) => void;
} }
export function EditMessageDetail({ chat, setShowEdit }: Props) { export function EditMessageDetail({ chat, setShowEdit }: Props) {
const ctx = useContext(AppContext); const { chatStore, setChatStore } = useContext(AppChatStoreContext);
const { chatStore, setChatStore } = ctx;
if (typeof chat.content !== "object") return <div>error</div>; if (typeof chat.content !== "object") return <div>error</div>;
return ( return (

View File

@@ -5,16 +5,14 @@ import { Tr } from "@/translate";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { useContext } from "react"; import { useContext } from "react";
import { AppContext } from "../pages/App"; import { AppChatStoreContext, AppContext } from "../pages/App";
interface Props { interface Props {
chat: ChatStoreMessage; chat: ChatStoreMessage;
setShowEdit: (se: boolean) => void; setShowEdit: (se: boolean) => void;
} }
export function EditMessageString({ chat, setShowEdit }: Props) { export function EditMessageString({ chat, setShowEdit }: Props) {
const ctx = useContext(AppContext); const { chatStore, setChatStore } = useContext(AppChatStoreContext);
const { chatStore, setChatStore } = ctx;
if (typeof chat.content !== "string") return <div>error</div>; if (typeof chat.content !== "string") return <div>error</div>;
return ( return (

View File

@@ -26,7 +26,7 @@ import {
ArrowUpDownIcon, ArrowUpDownIcon,
ScissorsIcon, ScissorsIcon,
} from "lucide-react"; } from "lucide-react";
import { AppContext } from "@/pages/App"; import { AppChatStoreContext, AppContext } from "@/pages/App";
import { models } from "@/types/models"; import { models } from "@/types/models";
import { getTotalCost } from "@/utils/totalCost"; import { getTotalCost } from "@/utils/totalCost";
import { Tr } from "@/translate"; import { Tr } from "@/translate";
@@ -36,9 +36,7 @@ import Search from "@/components/Search";
import Settings from "@/components/Settings"; import Settings from "@/components/Settings";
const Navbar: React.FC = () => { const Navbar: React.FC = () => {
const ctx = useContext(AppContext); const { chatStore, setChatStore } = useContext(AppChatStoreContext);
if (!ctx) return <div>error</div>;
const { chatStore, setChatStore } = ctx;
return ( return (
<header className="flex sticky top-0 bg-background h-14 shrink-0 items-center border-b z-50"> <header className="flex sticky top-0 bg-background h-14 shrink-0 items-center border-b z-50">

View File

@@ -1,5 +1,5 @@
import { IDBPDatabase, openDB } from "idb"; import { IDBPDatabase, openDB } from "idb";
import { createContext, useEffect, useState } from "react"; import { createContext, useContext, useEffect, useState } from "react";
import "@/global.css"; import "@/global.css";
import { calculate_token_length } from "@/chatgpt"; import { calculate_token_length } from "@/chatgpt";
@@ -31,8 +31,6 @@ import {
interface AppContextType { interface AppContextType {
db: Promise<IDBPDatabase<ChatStore>>; db: Promise<IDBPDatabase<ChatStore>>;
chatStore: ChatStore;
setChatStore: (cs: ChatStore) => void;
selectedChatIndex: number; selectedChatIndex: number;
setSelectedChatIndex: (i: number) => void; setSelectedChatIndex: (i: number) => void;
templates: TemplateChatStore[]; templates: TemplateChatStore[];
@@ -49,7 +47,15 @@ interface AppContextType {
setTemplateTools: (t: TemplateTools[]) => void; setTemplateTools: (t: TemplateTools[]) => void;
} }
interface AppChatStoreContextType {
chatStore: ChatStore;
setChatStore: (cs: ChatStore) => void;
}
export const AppContext = createContext<AppContextType>(null as any); export const AppContext = createContext<AppContextType>(null as any);
export const AppChatStoreContext = createContext<AppChatStoreContextType>(
null as any
);
import { import {
Sidebar, Sidebar,
@@ -123,57 +129,6 @@ export function App() {
return ret; return ret;
}; };
const [chatStore, _setChatStore] = useState(newChatStore({}));
const setChatStore = async (chatStore: ChatStore) => {
console.log("recalculate postBeginIndex");
const max = chatStore.maxTokens - chatStore.tokenMargin;
let sum = 0;
chatStore.postBeginIndex = chatStore.history.filter(
({ hide }) => !hide
).length;
for (const msg of chatStore.history
.filter(({ hide }) => !hide)
.slice()
.reverse()) {
if (sum + msg.token > max) break;
sum += msg.token;
chatStore.postBeginIndex -= 1;
}
chatStore.postBeginIndex =
chatStore.postBeginIndex < 0 ? 0 : chatStore.postBeginIndex;
// manually estimate token
chatStore.totalTokens = calculate_token_length(
chatStore.systemMessageContent
);
for (const msg of chatStore.history
.filter(({ hide }) => !hide)
.slice(chatStore.postBeginIndex)) {
chatStore.totalTokens += msg.token;
}
console.log("saved chat", selectedChatIndex, chatStore);
(await db).put(STORAGE_NAME, chatStore, selectedChatIndex);
// update total tokens
chatStore.totalTokens = calculate_token_length(
chatStore.systemMessageContent
);
for (const msg of chatStore.history
.filter(({ hide }) => !hide)
.slice(chatStore.postBeginIndex)) {
chatStore.totalTokens += msg.token;
}
_setChatStore(chatStore);
};
useEffect(() => {
const run = async () => {
_setChatStore(await getChatStoreByIndex(selectedChatIndex));
};
run();
}, [selectedChatIndex]);
// all chat store indexes // all chat store indexes
const [allChatStoreIndexes, setAllChatStoreIndexes] = useState<IDBValidKey>( const [allChatStoreIndexes, setAllChatStoreIndexes] = useState<IDBValidKey>(
[] []
@@ -189,7 +144,8 @@ export function App() {
}); });
}; };
const handleNewChatStore = async () => { const handleNewChatStore = async () => {
return handleNewChatStoreWithOldOne(chatStore); let currentChatStore = await getChatStoreByIndex(selectedChatIndex);
return handleNewChatStoreWithOldOne(currentChatStore);
}; };
const handleDEL = async () => { const handleDEL = async () => {
@@ -198,7 +154,7 @@ export function App() {
const newAllChatStoreIndexes = await (await db).getAllKeys(STORAGE_NAME); const newAllChatStoreIndexes = await (await db).getAllKeys(STORAGE_NAME);
if (newAllChatStoreIndexes.length === 0) { if (newAllChatStoreIndexes.length === 0) {
handleNewChatStore(); await handleNewChatStore();
return; return;
} }
@@ -333,8 +289,6 @@ export function App() {
<AppContext.Provider <AppContext.Provider
value={{ value={{
db, db,
chatStore,
setChatStore,
selectedChatIndex, selectedChatIndex,
setSelectedChatIndex, setSelectedChatIndex,
templates, templates,
@@ -407,19 +361,93 @@ export function App() {
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>
{chatStore.develop_mode && (
<Button onClick={handleCLS} variant="destructive">
<span>{Tr("CLS")}</span>
</Button>
)}
</SidebarFooter> </SidebarFooter>
<SidebarRail /> <SidebarRail />
</Sidebar> </Sidebar>
<SidebarInset> <SidebarInset>
<Navbar /> <AppChatStoreProvider
<ChatBOX /> selectedChatIndex={selectedChatIndex}
getChatStoreByIndex={getChatStoreByIndex}
>
<Navbar />
<ChatBOX />
</AppChatStoreProvider>
</SidebarInset> </SidebarInset>
</AppContext.Provider> </AppContext.Provider>
); );
} }
const AppChatStoreProvider = ({
children,
selectedChatIndex,
getChatStoreByIndex,
}: {
children: React.ReactNode;
selectedChatIndex: number;
getChatStoreByIndex: (index: number) => Promise<ChatStore>;
}) => {
console.log("[Render] AppChatStoreProvider");
const ctx = useContext(AppContext);
const [chatStore, _setChatStore] = useState(newChatStore({}));
const setChatStore = async (chatStore: ChatStore) => {
console.log("recalculate postBeginIndex");
const max = chatStore.maxTokens - chatStore.tokenMargin;
let sum = 0;
chatStore.postBeginIndex = chatStore.history.filter(
({ hide }) => !hide
).length;
for (const msg of chatStore.history
.filter(({ hide }) => !hide)
.slice()
.reverse()) {
if (sum + msg.token > max) break;
sum += msg.token;
chatStore.postBeginIndex -= 1;
}
chatStore.postBeginIndex =
chatStore.postBeginIndex < 0 ? 0 : chatStore.postBeginIndex;
// manually estimate token
chatStore.totalTokens = calculate_token_length(
chatStore.systemMessageContent
);
for (const msg of chatStore.history
.filter(({ hide }) => !hide)
.slice(chatStore.postBeginIndex)) {
chatStore.totalTokens += msg.token;
}
console.log("saved chat", selectedChatIndex, chatStore);
(await ctx.db).put(STORAGE_NAME, chatStore, selectedChatIndex);
// update total tokens
chatStore.totalTokens = calculate_token_length(
chatStore.systemMessageContent
);
for (const msg of chatStore.history
.filter(({ hide }) => !hide)
.slice(chatStore.postBeginIndex)) {
chatStore.totalTokens += msg.token;
}
_setChatStore(chatStore);
};
useEffect(() => {
const run = async () => {
_setChatStore(await getChatStoreByIndex(selectedChatIndex));
};
run();
}, [selectedChatIndex]);
return (
<AppChatStoreContext.Provider
value={{
chatStore,
setChatStore,
}}
>
{children}
</AppChatStoreContext.Provider>
);
};

View File

@@ -39,19 +39,14 @@ import {
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { AppContext } from "./App"; import { AppChatStoreContext, AppContext } from "./App";
import APIListMenu from "@/components/ListAPI"; import APIListMenu from "@/components/ListAPI";
import { ImageGenDrawer } from "@/components/ImageGenDrawer"; import { ImageGenDrawer } from "@/components/ImageGenDrawer";
export default function ChatBOX() { export default function ChatBOX() {
const ctx = useContext(AppContext); const { db, selectedChatIndex, setSelectedChatIndex } =
const { useContext(AppContext);
db, const { chatStore, setChatStore } = useContext(AppChatStoreContext);
chatStore,
setChatStore,
selectedChatIndex,
setSelectedChatIndex,
} = ctx;
// prevent error // prevent error
const [inputMsg, setInputMsg] = useState(""); const [inputMsg, setInputMsg] = useState("");
const [images, setImages] = useState<MessageDetail[]>([]); const [images, setImages] = useState<MessageDetail[]>([]);