refactor: reorganize SetAPIsTemplate component and update imports for improved structure

This commit is contained in:
ecwu
2025-01-05 00:11:31 +08:00
parent 76d50317e9
commit 75a431360b
5 changed files with 100 additions and 121 deletions

View File

@@ -19,25 +19,18 @@ import {
interface APITemplateItemProps { interface APITemplateItemProps {
label: string; label: string;
shortLabel: string;
apiField: string; apiField: string;
keyField: string; keyField: string;
} }
function ListAPIs({ function ListAPIs({ label, apiField, keyField }: APITemplateItemProps) {
label,
shortLabel,
apiField,
keyField,
}: APITemplateItemProps) {
const ctx = useContext(AppContext); const ctx = useContext(AppContext);
if (ctx === null) return <></>; if (ctx === null) return <></>;
return ( return (
<NavigationMenuItem> <NavigationMenuItem>
<NavigationMenuTrigger> <NavigationMenuTrigger>
<span className="lg:hidden">{shortLabel}</span>
<span className="hidden lg:inline">
{label}{" "} {label}{" "}
<span className="hidden lg:inline">
{ctx.templateAPIs.find( {ctx.templateAPIs.find(
(t) => (t) =>
ctx.chatStore[apiField as keyof ChatStore] === t.endpoint && ctx.chatStore[apiField as keyof ChatStore] === t.endpoint &&
@@ -215,7 +208,6 @@ const ListAPI: React.FC = () => {
{ctx.templateAPIs.length > 0 && ( {ctx.templateAPIs.length > 0 && (
<ListAPIs <ListAPIs
label="Chat API" label="Chat API"
shortLabel="API"
apiField="apiEndpoint" apiField="apiEndpoint"
keyField="apiKey" keyField="apiKey"
/> />
@@ -223,23 +215,16 @@ const ListAPI: React.FC = () => {
{ctx.templateAPIsWhisper.length > 0 && ( {ctx.templateAPIsWhisper.length > 0 && (
<ListAPIs <ListAPIs
label="Whisper API" label="Whisper API"
shortLabel="Whisper"
apiField="whisper_api" apiField="whisper_api"
keyField="whisper_key" keyField="whisper_key"
/> />
)} )}
{ctx.templateAPIsTTS.length > 0 && ( {ctx.templateAPIsTTS.length > 0 && (
<ListAPIs <ListAPIs label="TTS API" apiField="tts_api" keyField="tts_key" />
label="TTS API"
shortLabel="TTS"
apiField="tts_api"
keyField="tts_key"
/>
)} )}
{ctx.templateAPIsImageGen.length > 0 && ( {ctx.templateAPIsImageGen.length > 0 && (
<ListAPIs <ListAPIs
label="Image Gen API" label="Image Gen API"
shortLabel="ImgGen"
apiField="image_gen_api" apiField="image_gen_api"
keyField="image_gen_key" keyField="image_gen_key"
/> />

View File

@@ -1,11 +1,11 @@
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 { useContext, useState } from "react"; import { useContext, useState, useMemo } from "react";
import { ChatStoreMessage } from "@/types/chatstore"; import { ChatStoreMessage } from "@/types/chatstore";
import { addTotalCost } from "@/utils/totalCost";
import { Tr } from "@/translate"; import { Tr } from "@/translate";
import { getMessageText } from "@/chatgpt"; import { getMessageText } from "@/chatgpt";
import TTSButton, { TTSPlay } from "@/tts";
import { EditMessage } from "@/editMessage"; import { EditMessage } from "@/editMessage";
import logprobToColor from "@/utils/logprob"; import logprobToColor from "@/utils/logprob";
import { import {
@@ -15,12 +15,15 @@ import {
ChatBubbleActionWrapper, ChatBubbleActionWrapper,
} from "@/components/ui/chat/chat-bubble"; } from "@/components/ui/chat/chat-bubble";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { useToast } from "@/hooks/use-toast"; import { useToast } from "@/hooks/use-toast";
import { import {
ClipboardIcon, ClipboardIcon,
PencilIcon, PencilIcon,
MessageSquareOffIcon, MessageSquareOffIcon,
MessageSquarePlusIcon, MessageSquarePlusIcon,
AudioLinesIcon,
LoaderCircleIcon,
} from "lucide-react"; } from "lucide-react";
import { AppContext } from "@/pages/App"; import { AppContext } from "@/pages/App";
@@ -143,6 +146,94 @@ function MessageToolResp({ chat, copyToClipboard }: ToolRespondMessageProps) {
); );
} }
interface TTSProps {
chat: ChatStoreMessage;
}
interface TTSPlayProps {
chat: ChatStoreMessage;
}
export function TTSPlay(props: TTSPlayProps) {
const src = useMemo(() => {
if (props.chat.audio instanceof Blob) {
return URL.createObjectURL(props.chat.audio);
}
return "";
}, [props.chat.audio]);
if (props.chat.hide) {
return <></>;
}
if (props.chat.audio instanceof Blob) {
return <audio className="w-64" src={src} controls />;
}
return <></>;
}
function TTSButton(props: TTSProps) {
const [generating, setGenerating] = useState(false);
const ctx = useContext(AppContext);
if (!ctx) return <div>error</div>;
return (
<Button
variant="ghost"
size="icon"
onClick={() => {
const api = ctx.chatStore.tts_api;
const api_key = ctx.chatStore.tts_key;
const model = "tts-1";
const input = getMessageText(props.chat);
const voice = ctx.chatStore.tts_voice;
const body: Record<string, any> = {
model,
input,
voice,
response_format: ctx.chatStore.tts_format || "mp3",
};
if (ctx.chatStore.tts_speed_enabled) {
body["speed"] = ctx.chatStore.tts_speed;
}
setGenerating(true);
fetch(api, {
method: "POST",
headers: {
Authorization: `Bearer ${api_key}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
})
.then((response) => response.blob())
.then((blob) => {
// update price
const cost = (input.length * 0.015) / 1000;
ctx.chatStore.cost += cost;
addTotalCost(cost);
ctx.setChatStore({ ...ctx.chatStore });
// save blob
props.chat.audio = blob;
ctx.setChatStore({ ...ctx.chatStore });
const url = URL.createObjectURL(blob);
const audio = new Audio(url);
audio.play();
})
.finally(() => {
setGenerating(false);
});
}}
>
{generating ? (
<LoaderCircleIcon className="h-4 w-4 animate-spin" />
) : (
<AudioLinesIcon className="h-4 w-4" />
)}
</Button>
);
}
export default function Message(props: { messageIndex: number }) { export default function Message(props: { messageIndex: number }) {
const ctx = useContext(AppContext); const ctx = useContext(AppContext);
if (ctx === null) return <></>; if (ctx === null) return <></>;

View File

@@ -12,7 +12,7 @@ import {
import { models } from "@/types/models"; import { models } from "@/types/models";
import { tr, Tr, langCodeContext, LANG_OPTIONS } from "@/translate"; import { tr, Tr, langCodeContext, LANG_OPTIONS } from "@/translate";
import { isVailedJSON } from "@/utils/isVailedJSON"; import { isVailedJSON } from "@/utils/isVailedJSON";
import { SetAPIsTemplate } from "@/setAPIsTemplate"; import { SetAPIsTemplate } from "@/components/setAPIsTemplate";
import { autoHeight } from "@/utils/textAreaHelp"; import { autoHeight } from "@/utils/textAreaHelp";
import { getDefaultParams } from "@/utils/getDefaultParam"; import { getDefaultParams } from "@/utils/getDefaultParam";

View File

@@ -11,7 +11,7 @@ import {
DialogTitle, DialogTitle,
DialogTrigger, DialogTrigger,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { Button } from "./components/ui/button"; import { Button } from "./ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { SaveIcon } from "lucide-react"; import { SaveIcon } from "lucide-react";
@@ -71,12 +71,12 @@ export function SetAPIsTemplate({
} }
return; return;
} }
const tmp: TemplateAPI = { const temp: TemplateAPI = {
name: name.value, name: name.value,
endpoint, endpoint,
key: APIkey, key: APIkey,
}; };
temps.push(tmp); temps.push(temp);
setTemps([...temps]); setTemps([...temps]);
}} }}
> >

View File

@@ -1,97 +0,0 @@
import { SpeakerWaveIcon } from "@heroicons/react/24/outline";
import { useContext, useMemo, useState } from "react";
import { addTotalCost } from "@/utils/totalCost";
import { ChatStore, ChatStoreMessage } from "@/types/chatstore";
import { Message, getMessageText } from "@/chatgpt";
import { AudioLinesIcon, LoaderCircleIcon } from "lucide-react";
import { Button } from "./components/ui/button";
import { AppContext } from "./pages/App";
interface TTSProps {
chat: ChatStoreMessage;
}
interface TTSPlayProps {
chat: ChatStoreMessage;
}
export function TTSPlay(props: TTSPlayProps) {
const src = useMemo(() => {
if (props.chat.audio instanceof Blob) {
return URL.createObjectURL(props.chat.audio);
}
return "";
}, [props.chat.audio]);
if (props.chat.hide) {
return <></>;
}
if (props.chat.audio instanceof Blob) {
return <audio className="w-64" src={src} controls />;
}
return <></>;
}
export default function TTSButton(props: TTSProps) {
const [generating, setGenerating] = useState(false);
const ctx = useContext(AppContext);
if (!ctx) return <div>error</div>;
return (
<Button
variant="ghost"
size="icon"
onClick={() => {
const api = ctx.chatStore.tts_api;
const api_key = ctx.chatStore.tts_key;
const model = "tts-1";
const input = getMessageText(props.chat);
const voice = ctx.chatStore.tts_voice;
const body: Record<string, any> = {
model,
input,
voice,
response_format: ctx.chatStore.tts_format || "mp3",
};
if (ctx.chatStore.tts_speed_enabled) {
body["speed"] = ctx.chatStore.tts_speed;
}
setGenerating(true);
fetch(api, {
method: "POST",
headers: {
Authorization: `Bearer ${api_key}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
})
.then((response) => response.blob())
.then((blob) => {
// update price
const cost = (input.length * 0.015) / 1000;
ctx.chatStore.cost += cost;
addTotalCost(cost);
ctx.setChatStore({ ...ctx.chatStore });
// save blob
props.chat.audio = blob;
ctx.setChatStore({ ...ctx.chatStore });
const url = URL.createObjectURL(blob);
const audio = new Audio(url);
audio.play();
})
.finally(() => {
setGenerating(false);
});
}}
>
{generating ? (
<LoaderCircleIcon className="h-4 w-4 animate-spin" />
) : (
<AudioLinesIcon className="h-4 w-4" />
)}
</Button>
);
}