- chatStore.updateCurrentSession(
+ chatStore.updateTargetSession(
+ session,
(session) => (session.clearContextIndex = undefined),
)
}
@@ -443,11 +461,13 @@ export function ChatActions(props: {
hitBottom: boolean;
uploading: boolean;
setShowShortcutKeyModal: React.Dispatch
>;
+ setUserInput: (input: string) => void;
}) {
const config = useAppConfig();
const navigate = useNavigate();
const chatStore = useChatStore();
const pluginStore = usePluginStore();
+ const session = chatStore.currentSession();
// switch themes
const theme = config.theme;
@@ -464,10 +484,9 @@ export function ChatActions(props: {
const stopAll = () => ChatControllerPool.stopAll();
// switch model
- const currentModel = chatStore.currentSession().mask.modelConfig.model;
+ const currentModel = session.mask.modelConfig.model;
const currentProviderName =
- chatStore.currentSession().mask.modelConfig?.providerName ||
- ServiceProvider.OpenAI;
+ session.mask.modelConfig?.providerName || ServiceProvider.OpenAI;
const allModels = useAllModels();
const models = useMemo(() => {
const filteredModels = allModels.filter((m) => m.available);
@@ -501,12 +520,9 @@ export function ChatActions(props: {
const dalle3Sizes: DalleSize[] = ["1024x1024", "1792x1024", "1024x1792"];
const dalle3Qualitys: DalleQuality[] = ["standard", "hd"];
const dalle3Styles: DalleStyle[] = ["vivid", "natural"];
- const currentSize =
- chatStore.currentSession().mask.modelConfig?.size ?? "1024x1024";
- const currentQuality =
- chatStore.currentSession().mask.modelConfig?.quality ?? "standard";
- const currentStyle =
- chatStore.currentSession().mask.modelConfig?.style ?? "vivid";
+ const currentSize = session.mask.modelConfig?.size ?? "1024x1024";
+ const currentQuality = session.mask.modelConfig?.quality ?? "standard";
+ const currentStyle = session.mask.modelConfig?.style ?? "vivid";
const isMobileScreen = useMobileScreen();
@@ -524,7 +540,7 @@ export function ChatActions(props: {
if (isUnavailableModel && models.length > 0) {
// show next model to default model if exist
let nextModel = models.find((model) => model.isDefault) || models[0];
- chatStore.updateCurrentSession((session) => {
+ chatStore.updateTargetSession(session, (session) => {
session.mask.modelConfig.model = nextModel.name;
session.mask.modelConfig.providerName = nextModel?.provider
?.providerName as ServiceProvider;
@@ -535,7 +551,7 @@ export function ChatActions(props: {
: nextModel.name,
);
}
- }, [chatStore, currentModel, models]);
+ }, [chatStore, currentModel, models, session]);
return (
@@ -602,7 +618,7 @@ export function ChatActions(props: {
text={Locale.Chat.InputActions.Clear}
icon={}
onClick={() => {
- chatStore.updateCurrentSession((session) => {
+ chatStore.updateTargetSession(session, (session) => {
if (session.clearContextIndex === session.messages.length) {
session.clearContextIndex = undefined;
} else {
@@ -633,8 +649,8 @@ export function ChatActions(props: {
onClose={() => setShowModelSelector(false)}
onSelection={(s) => {
if (s.length === 0) return;
- const [model, providerName] = s[0].split("@");
- chatStore.updateCurrentSession((session) => {
+ const [model, providerName] = getModelProvider(s[0]);
+ chatStore.updateTargetSession(session, (session) => {
session.mask.modelConfig.model = model as ModelType;
session.mask.modelConfig.providerName =
providerName as ServiceProvider;
@@ -672,7 +688,7 @@ export function ChatActions(props: {
onSelection={(s) => {
if (s.length === 0) return;
const size = s[0];
- chatStore.updateCurrentSession((session) => {
+ chatStore.updateTargetSession(session, (session) => {
session.mask.modelConfig.size = size;
});
showToast(size);
@@ -699,7 +715,7 @@ export function ChatActions(props: {
onSelection={(q) => {
if (q.length === 0) return;
const quality = q[0];
- chatStore.updateCurrentSession((session) => {
+ chatStore.updateTargetSession(session, (session) => {
session.mask.modelConfig.quality = quality;
});
showToast(quality);
@@ -726,7 +742,7 @@ export function ChatActions(props: {
onSelection={(s) => {
if (s.length === 0) return;
const style = s[0];
- chatStore.updateCurrentSession((session) => {
+ chatStore.updateTargetSession(session, (session) => {
session.mask.modelConfig.style = style;
});
showToast(style);
@@ -757,7 +773,7 @@ export function ChatActions(props: {
}))}
onClose={() => setShowPluginSelector(false)}
onSelection={(s) => {
- chatStore.updateCurrentSession((session) => {
+ chatStore.updateTargetSession(session, (session) => {
session.mask.plugin = s as string[];
});
}}
@@ -800,7 +816,8 @@ export function EditMessageModal(props: { onClose: () => void }) {
icon={}
key="ok"
onClick={() => {
- chatStore.updateCurrentSession(
+ chatStore.updateTargetSession(
+ session,
(session) => (session.messages = messages),
);
props.onClose();
@@ -817,7 +834,8 @@ export function EditMessageModal(props: { onClose: () => void }) {
type="text"
value={session.topic}
onInput={(e) =>
- chatStore.updateCurrentSession(
+ chatStore.updateTargetSession(
+ session,
(session) => (session.topic = e.currentTarget.value),
)
}
@@ -978,9 +996,11 @@ function _Chat() {
prev: () => chatStore.nextSession(-1),
next: () => chatStore.nextSession(1),
clear: () =>
- chatStore.updateCurrentSession(
+ chatStore.updateTargetSession(
+ session,
(session) => (session.clearContextIndex = session.messages.length),
),
+ fork: () => chatStore.forkSession(),
del: () => chatStore.deleteSession(chatStore.currentSessionIndex),
});
@@ -1005,7 +1025,7 @@ function _Chat() {
};
const doSubmit = (userInput: string) => {
- if (userInput.trim() === "") return;
+ if (userInput.trim() === "" && isEmpty(attachImages)) return;
const matchCommand = chatCommands.match(userInput);
if (matchCommand.matched) {
setUserInput("");
@@ -1048,7 +1068,7 @@ function _Chat() {
};
useEffect(() => {
- chatStore.updateCurrentSession((session) => {
+ chatStore.updateTargetSession(session, (session) => {
const stopTiming = Date.now() - REQUEST_TIMEOUT_MS;
session.messages.forEach((m) => {
// check if should stop all stale messages
@@ -1074,7 +1094,7 @@ function _Chat() {
}
});
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
+ }, [session]);
// check if should send message
const onInputKeyDown = (e: React.KeyboardEvent) => {
@@ -1105,7 +1125,8 @@ function _Chat() {
};
const deleteMessage = (msgId?: string) => {
- chatStore.updateCurrentSession(
+ chatStore.updateTargetSession(
+ session,
(session) =>
(session.messages = session.messages.filter((m) => m.id !== msgId)),
);
@@ -1172,7 +1193,7 @@ function _Chat() {
};
const onPinMessage = (message: ChatMessage) => {
- chatStore.updateCurrentSession((session) =>
+ chatStore.updateTargetSession(session, (session) =>
session.mask.context.push(message),
);
@@ -1184,10 +1205,55 @@ function _Chat() {
});
};
+ const accessStore = useAccessStore();
+ const [speechStatus, setSpeechStatus] = useState(false);
+ const [speechLoading, setSpeechLoading] = useState(false);
+ async function openaiSpeech(text: string) {
+ if (speechStatus) {
+ ttsPlayer.stop();
+ setSpeechStatus(false);
+ } else {
+ var api: ClientApi;
+ api = new ClientApi(ModelProvider.GPT);
+ const config = useAppConfig.getState();
+ setSpeechLoading(true);
+ ttsPlayer.init();
+ let audioBuffer: ArrayBuffer;
+ const { markdownToTxt } = require("markdown-to-txt");
+ const textContent = markdownToTxt(text);
+ if (config.ttsConfig.engine !== DEFAULT_TTS_ENGINE) {
+ const edgeVoiceName = accessStore.edgeVoiceName();
+ const tts = new MsEdgeTTS();
+ await tts.setMetadata(
+ edgeVoiceName,
+ OUTPUT_FORMAT.AUDIO_24KHZ_96KBITRATE_MONO_MP3,
+ );
+ audioBuffer = await tts.toArrayBuffer(textContent);
+ } else {
+ audioBuffer = await api.llm.speech({
+ model: config.ttsConfig.model,
+ input: textContent,
+ voice: config.ttsConfig.voice,
+ speed: config.ttsConfig.speed,
+ });
+ }
+ setSpeechStatus(true);
+ ttsPlayer
+ .play(audioBuffer, () => {
+ setSpeechStatus(false);
+ })
+ .catch((e) => {
+ console.error("[OpenAI Speech]", e);
+ showToast(prettyObject(e));
+ setSpeechStatus(false);
+ })
+ .finally(() => setSpeechLoading(false));
+ }
+ }
+
const context: RenderMessage[] = useMemo(() => {
return session.mask.hideContext ? [] : session.mask.context.slice();
}, [session.mask.context, session.mask.hideContext]);
- const accessStore = useAccessStore();
if (
context.length === 0 &&
@@ -1549,7 +1615,7 @@ function _Chat() {
title={Locale.Chat.Actions.RefreshTitle}
onClick={() => {
showToast(Locale.Chat.Actions.RefreshToast);
- chatStore.summarizeSession(true);
+ chatStore.summarizeSession(true, session);
}}
/>
@@ -1653,14 +1719,17 @@ function _Chat() {
});
}
}
- chatStore.updateCurrentSession((session) => {
- const m = session.mask.context
- .concat(session.messages)
- .find((m) => m.id === message.id);
- if (m) {
- m.content = newContent;
- }
- });
+ chatStore.updateTargetSession(
+ session,
+ (session) => {
+ const m = session.mask.context
+ .concat(session.messages)
+ .find((m) => m.id === message.id);
+ if (m) {
+ m.content = newContent;
+ }
+ },
+ );
}}
>