diff --git a/README.md b/README.md index fce62ba3750..0c06b73f05b 100644 --- a/README.md +++ b/README.md @@ -96,10 +96,11 @@ For enterprise inquiries, please contact: **business@nextchat.dev** - [x] Artifacts: Easily preview, copy and share generated content/webpages through a separate window [#5092](https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web/pull/5092) - [x] Plugins: support network search, calculator, any other apis etc. [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165) [#5353](https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web/issues/5353) - [x] network search, calculator, any other apis etc. [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165) [#5353](https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web/issues/5353) +- [x] Supports Realtime Chat [#5672](https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web/issues/5672) - [ ] local knowledge base ## What's New - +- 🚀 v2.15.8 Now supports Realtime Chat [#5672](https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web/issues/5672) - 🚀 v2.15.4 The Application supports using Tauri fetch LLM API, MORE SECURITY! [#5379](https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web/issues/5379) - 🚀 v2.15.0 Now supports Plugins! Read this: [NextChat-Awesome-Plugins](https://github.com/ChatGPTNextWeb/NextChat-Awesome-Plugins) - 🚀 v2.14.0 Now supports Artifacts & SD @@ -134,10 +135,11 @@ For enterprise inquiries, please contact: **business@nextchat.dev** - [x] Artifacts: 通过独立窗口,轻松预览、复制和分享生成的内容/可交互网页 [#5092](https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web/pull/5092) - [x] 插件机制,支持`联网搜索`、`计算器`、调用其他平台 api [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165) [#5353](https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web/issues/5353) - [x] 支持联网搜索、计算器、调用其他平台 api [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165) [#5353](https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web/issues/5353) + - [x] 支持 Realtime Chat [#5672](https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web/issues/5672) - [ ] 本地知识库 ## 最新动态 - +- 🚀 v2.15.8 现在支持Realtime Chat [#5672](https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web/issues/5672) - 🚀 v2.15.4 客户端支持Tauri本地直接调用大模型API,更安全![#5379](https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web/issues/5379) - 🚀 v2.15.0 现在支持插件功能了!了解更多:[NextChat-Awesome-Plugins](https://github.com/ChatGPTNextWeb/NextChat-Awesome-Plugins) - 🚀 v2.14.0 现在支持 Artifacts & SD 了。 diff --git a/app/api/proxy.ts b/app/api/proxy.ts index 731003aa1ea..b3e5e7b7b93 100644 --- a/app/api/proxy.ts +++ b/app/api/proxy.ts @@ -1,4 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; +import { getServerSideConfig } from "@/app/config/server"; export async function handle( req: NextRequest, @@ -9,6 +10,7 @@ export async function handle( if (req.method === "OPTIONS") { return NextResponse.json({ body: "OK" }, { status: 200 }); } + const serverConfig = getServerSideConfig(); // remove path params from searchParams req.nextUrl.searchParams.delete("path"); @@ -31,6 +33,18 @@ export async function handle( return true; }), ); + // if dalle3 use openai api key + const baseUrl = req.headers.get("x-base-url"); + if (baseUrl?.includes("api.openai.com")) { + if (!serverConfig.apiKey) { + return NextResponse.json( + { error: "OpenAI API key not configured" }, + { status: 500 }, + ); + } + headers.set("Authorization", `Bearer ${serverConfig.apiKey}`); + } + const controller = new AbortController(); const fetchOptions: RequestInit = { headers, diff --git a/app/components/chat.module.scss b/app/components/chat.module.scss index 73542fc67f1..7560d030533 100644 --- a/app/components/chat.module.scss +++ b/app/components/chat.module.scss @@ -45,6 +45,14 @@ .chat-input-actions { display: flex; flex-wrap: wrap; + justify-content: space-between; + gap: 5px; + + &-end { + display: flex; + margin-left: auto; + gap: 5px; + } .chat-input-action { display: inline-flex; @@ -62,10 +70,6 @@ width: var(--icon-width); overflow: hidden; - &:not(:last-child) { - margin-right: 5px; - } - .text { white-space: nowrap; padding-left: 5px; @@ -231,10 +235,12 @@ animation: slide-in ease 0.3s; - $linear: linear-gradient(to right, - rgba(0, 0, 0, 0), - rgba(0, 0, 0, 1), - rgba(0, 0, 0, 0)); + $linear: linear-gradient( + to right, + rgba(0, 0, 0, 0), + rgba(0, 0, 0, 1), + rgba(0, 0, 0, 0) + ); mask-image: $linear; @mixin show { @@ -373,7 +379,7 @@ } } -.chat-message-user>.chat-message-container { +.chat-message-user > .chat-message-container { align-items: flex-end; } @@ -443,6 +449,25 @@ transition: all ease 0.3s; } +.chat-message-audio { + display: flex; + align-items: center; + justify-content: space-between; + border-radius: 10px; + background-color: rgba(0, 0, 0, 0.05); + border: var(--border-in-light); + position: relative; + transition: all ease 0.3s; + margin-top: 10px; + font-size: 14px; + user-select: text; + word-break: break-word; + box-sizing: border-box; + audio { + height: 30px; /* 调整高度 */ + } +} + .chat-message-item-image { width: 100%; margin-top: 10px; @@ -471,23 +496,27 @@ border: rgba($color: #888, $alpha: 0.2) 1px solid; } - @media only screen and (max-width: 600px) { - $calc-image-width: calc(100vw/3*2/var(--image-count)); + $calc-image-width: calc(100vw / 3 * 2 / var(--image-count)); .chat-message-item-image-multi { width: $calc-image-width; height: $calc-image-width; } - + .chat-message-item-image { - max-width: calc(100vw/3*2); + max-width: calc(100vw / 3 * 2); } } @media screen and (min-width: 600px) { - $max-image-width: calc(calc(1200px - var(--sidebar-width))/3*2/var(--image-count)); - $image-width: calc(calc(var(--window-width) - var(--sidebar-width))/3*2/var(--image-count)); + $max-image-width: calc( + calc(1200px - var(--sidebar-width)) / 3 * 2 / var(--image-count) + ); + $image-width: calc( + calc(var(--window-width) - var(--sidebar-width)) / 3 * 2 / + var(--image-count) + ); .chat-message-item-image-multi { width: $image-width; @@ -497,7 +526,7 @@ } .chat-message-item-image { - max-width: calc(calc(1200px - var(--sidebar-width))/3*2); + max-width: calc(calc(1200px - var(--sidebar-width)) / 3 * 2); } } @@ -515,7 +544,7 @@ z-index: 1; } -.chat-message-user>.chat-message-container>.chat-message-item { +.chat-message-user > .chat-message-container > .chat-message-item { background-color: var(--second); &:hover { @@ -626,7 +655,8 @@ min-height: 68px; } -.chat-input:focus {} +.chat-input:focus { +} .chat-input-send { background-color: var(--primary); @@ -693,4 +723,31 @@ .shortcut-key span { font-size: 12px; color: var(--black); -} \ No newline at end of file +} + +.chat-main { + display: flex; + height: 100%; + width: 100%; + position: relative; + overflow: hidden; + .chat-body-container { + height: 100%; + display: flex; + flex-direction: column; + flex: 1; + width: 100%; + } + .chat-side-panel { + position: absolute; + inset: 0; + background: var(--white); + overflow: hidden; + z-index: 10; + transform: translateX(100%); + transition: all ease 0.3s; + &-show { + transform: translateX(0); + } + } +} diff --git a/app/components/chat.tsx b/app/components/chat.tsx index cddf0e33515..ed51d926f4e 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -46,7 +46,7 @@ import StyleIcon from "../icons/palette.svg"; import PluginIcon from "../icons/plugin.svg"; import ShortcutkeyIcon from "../icons/shortcutkey.svg"; import ReloadIcon from "../icons/reload.svg"; - +import HeadphoneIcon from "../icons/headphone.svg"; import { ChatMessage, SubmitKey, @@ -121,6 +121,7 @@ import { MsEdgeTTS, OUTPUT_FORMAT } from "../utils/ms_edge_tts"; import { isEmpty } from "lodash-es"; import { getModelProvider } from "../utils/model"; +import { RealtimeChat } from "@/app/components/realtime-chat"; import clsx from "clsx"; const localStorage = safeLocalStorage(); @@ -462,6 +463,7 @@ export function ChatActions(props: { uploading: boolean; setShowShortcutKeyModal: React.Dispatch>; setUserInput: (input: string) => void; + setShowChatSidePanel: React.Dispatch>; }) { const config = useAppConfig(); const navigate = useNavigate(); @@ -555,238 +557,250 @@ export function ChatActions(props: { return (
- {couldStop && ( - } - /> - )} - {!props.hitBottom && ( + <> + {couldStop && ( + } + /> + )} + {!props.hitBottom && ( + } + /> + )} + {props.hitBottom && ( + } + /> + )} + + {showUploadImage && ( + : } + /> + )} } + onClick={nextTheme} + text={Locale.Chat.InputActions.Theme[theme]} + icon={ + <> + {theme === Theme.Auto ? ( + + ) : theme === Theme.Light ? ( + + ) : theme === Theme.Dark ? ( + + ) : null} + + } /> - )} - {props.hitBottom && ( + } + onClick={props.showPromptHints} + text={Locale.Chat.InputActions.Prompt} + icon={} /> - )} - {showUploadImage && ( : } - /> - )} - - {theme === Theme.Auto ? ( - - ) : theme === Theme.Light ? ( - - ) : theme === Theme.Dark ? ( - - ) : null} - - } - /> - - } - /> - - { - navigate(Path.Masks); - }} - text={Locale.Chat.InputActions.Masks} - icon={} - /> - - } - onClick={() => { - chatStore.updateTargetSession(session, (session) => { - if (session.clearContextIndex === session.messages.length) { - session.clearContextIndex = undefined; - } else { - session.clearContextIndex = session.messages.length; - session.memoryPrompt = ""; // will clear memory - } - }); - }} - /> - - setShowModelSelector(true)} - text={currentModelName} - icon={} - /> - - {showModelSelector && ( - ({ - title: `${m.displayName}${ - m?.provider?.providerName - ? " (" + m?.provider?.providerName + ")" - : "" - }`, - value: `${m.name}@${m?.provider?.providerName}`, - }))} - onClose={() => setShowModelSelector(false)} - onSelection={(s) => { - if (s.length === 0) return; - const [model, providerName] = getModelProvider(s[0]); - chatStore.updateTargetSession(session, (session) => { - session.mask.modelConfig.model = model as ModelType; - session.mask.modelConfig.providerName = - providerName as ServiceProvider; - session.mask.syncGlobalConfig = false; - }); - if (providerName == "ByteDance") { - const selectedModel = models.find( - (m) => - m.name == model && m?.provider?.providerName == providerName, - ); - showToast(selectedModel?.displayName ?? ""); - } else { - showToast(model); - } + onClick={() => { + navigate(Path.Masks); }} + text={Locale.Chat.InputActions.Masks} + icon={} /> - )} - {isDalle3(currentModel) && ( setShowSizeSelector(true)} - text={currentSize} - icon={} - /> - )} - - {showSizeSelector && ( - ({ - title: m, - value: m, - }))} - onClose={() => setShowSizeSelector(false)} - onSelection={(s) => { - if (s.length === 0) return; - const size = s[0]; + text={Locale.Chat.InputActions.Clear} + icon={} + onClick={() => { chatStore.updateTargetSession(session, (session) => { - session.mask.modelConfig.size = size; + if (session.clearContextIndex === session.messages.length) { + session.clearContextIndex = undefined; + } else { + session.clearContextIndex = session.messages.length; + session.memoryPrompt = ""; // will clear memory + } }); - showToast(size); }} /> - )} - {isDalle3(currentModel) && ( setShowQualitySelector(true)} - text={currentQuality} - icon={} + onClick={() => setShowModelSelector(true)} + text={currentModelName} + icon={} /> - )} - {showQualitySelector && ( - ({ - title: m, - value: m, - }))} - onClose={() => setShowQualitySelector(false)} - onSelection={(q) => { - if (q.length === 0) return; - const quality = q[0]; - chatStore.updateTargetSession(session, (session) => { - session.mask.modelConfig.quality = quality; - }); - showToast(quality); - }} - /> - )} + {showModelSelector && ( + ({ + title: `${m.displayName}${ + m?.provider?.providerName + ? " (" + m?.provider?.providerName + ")" + : "" + }`, + value: `${m.name}@${m?.provider?.providerName}`, + }))} + onClose={() => setShowModelSelector(false)} + onSelection={(s) => { + if (s.length === 0) return; + const [model, providerName] = getModelProvider(s[0]); + chatStore.updateTargetSession(session, (session) => { + session.mask.modelConfig.model = model as ModelType; + session.mask.modelConfig.providerName = + providerName as ServiceProvider; + session.mask.syncGlobalConfig = false; + }); + if (providerName == "ByteDance") { + const selectedModel = models.find( + (m) => + m.name == model && + m?.provider?.providerName == providerName, + ); + showToast(selectedModel?.displayName ?? ""); + } else { + showToast(model); + } + }} + /> + )} - {isDalle3(currentModel) && ( - setShowStyleSelector(true)} - text={currentStyle} - icon={} - /> - )} + {isDalle3(currentModel) && ( + setShowSizeSelector(true)} + text={currentSize} + icon={} + /> + )} - {showStyleSelector && ( - ({ - title: m, - value: m, - }))} - onClose={() => setShowStyleSelector(false)} - onSelection={(s) => { - if (s.length === 0) return; - const style = s[0]; - chatStore.updateTargetSession(session, (session) => { - session.mask.modelConfig.style = style; - }); - showToast(style); - }} - /> - )} + {showSizeSelector && ( + ({ + title: m, + value: m, + }))} + onClose={() => setShowSizeSelector(false)} + onSelection={(s) => { + if (s.length === 0) return; + const size = s[0]; + chatStore.updateTargetSession(session, (session) => { + session.mask.modelConfig.size = size; + }); + showToast(size); + }} + /> + )} - {showPlugins(currentProviderName, currentModel) && ( - { - if (pluginStore.getAll().length == 0) { - navigate(Path.Plugins); - } else { - setShowPluginSelector(true); - } - }} - text={Locale.Plugin.Name} - icon={} - /> - )} - {showPluginSelector && ( - ({ - title: `${item?.title}@${item?.version}`, - value: item?.id, - }))} - onClose={() => setShowPluginSelector(false)} - onSelection={(s) => { - chatStore.updateTargetSession(session, (session) => { - session.mask.plugin = s as string[]; - }); - }} - /> - )} + {isDalle3(currentModel) && ( + setShowQualitySelector(true)} + text={currentQuality} + icon={} + /> + )} - {!isMobileScreen && ( - props.setShowShortcutKeyModal(true)} - text={Locale.Chat.ShortcutKey.Title} - icon={} - /> - )} + {showQualitySelector && ( + ({ + title: m, + value: m, + }))} + onClose={() => setShowQualitySelector(false)} + onSelection={(q) => { + if (q.length === 0) return; + const quality = q[0]; + chatStore.updateTargetSession(session, (session) => { + session.mask.modelConfig.quality = quality; + }); + showToast(quality); + }} + /> + )} + + {isDalle3(currentModel) && ( + setShowStyleSelector(true)} + text={currentStyle} + icon={} + /> + )} + + {showStyleSelector && ( + ({ + title: m, + value: m, + }))} + onClose={() => setShowStyleSelector(false)} + onSelection={(s) => { + if (s.length === 0) return; + const style = s[0]; + chatStore.updateTargetSession(session, (session) => { + session.mask.modelConfig.style = style; + }); + showToast(style); + }} + /> + )} + + {showPlugins(currentProviderName, currentModel) && ( + { + if (pluginStore.getAll().length == 0) { + navigate(Path.Plugins); + } else { + setShowPluginSelector(true); + } + }} + text={Locale.Plugin.Name} + icon={} + /> + )} + {showPluginSelector && ( + ({ + title: `${item?.title}@${item?.version}`, + value: item?.id, + }))} + onClose={() => setShowPluginSelector(false)} + onSelection={(s) => { + chatStore.updateTargetSession(session, (session) => { + session.mask.plugin = s as string[]; + }); + }} + /> + )} + + {!isMobileScreen && ( + props.setShowShortcutKeyModal(true)} + text={Locale.Chat.ShortcutKey.Title} + icon={} + /> + )} + +
+ {config.realtimeConfig.enable && ( + props.setShowChatSidePanel(true)} + text={"Realtime Chat"} + icon={} + /> + )} +
); } @@ -1580,420 +1594,462 @@ function _Chat() { }; }, [messages, chatStore, navigate]); + const [showChatSidePanel, setShowChatSidePanel] = useState(false); + return ( -
-
- {isMobileScreen && ( -
-
- } - bordered - title={Locale.Chat.Actions.ChatList} - onClick={() => navigate(Path.Home)} - /> + <> +
+
+ {isMobileScreen && ( +
+
+ } + bordered + title={Locale.Chat.Actions.ChatList} + onClick={() => navigate(Path.Home)} + /> +
-
- )} + )} -
setIsEditingMessage(true)} + className={clsx("window-header-title", styles["chat-body-title"])} > - {!session.topic ? DEFAULT_TOPIC : session.topic} -
-
- {Locale.Chat.SubTitle(session.messages.length)} -
-
-
-
- } - bordered - title={Locale.Chat.Actions.RefreshTitle} - onClick={() => { - showToast(Locale.Chat.Actions.RefreshToast); - chatStore.summarizeSession(true, session); - }} - /> +
setIsEditingMessage(true)} + > + {!session.topic ? DEFAULT_TOPIC : session.topic} +
+
+ {Locale.Chat.SubTitle(session.messages.length)} +
- {!isMobileScreen && ( +
} + icon={} bordered - title={Locale.Chat.EditMessage.Title} - aria={Locale.Chat.EditMessage.Title} - onClick={() => setIsEditingMessage(true)} + title={Locale.Chat.Actions.RefreshTitle} + onClick={() => { + showToast(Locale.Chat.Actions.RefreshToast); + chatStore.summarizeSession(true, session); + }} />
- )} -
- } - bordered - title={Locale.Chat.Actions.Export} - onClick={() => { - setShowExport(true); - }} - /> -
- {showMaxIcon && ( + {!isMobileScreen && ( +
+ } + bordered + title={Locale.Chat.EditMessage.Title} + aria={Locale.Chat.EditMessage.Title} + onClick={() => setIsEditingMessage(true)} + /> +
+ )}
: } + icon={} bordered - title={Locale.Chat.Actions.FullScreen} - aria={Locale.Chat.Actions.FullScreen} + title={Locale.Chat.Actions.Export} onClick={() => { - config.update( - (config) => (config.tightBorder = !config.tightBorder), - ); + setShowExport(true); }} />
- )} -
+ {showMaxIcon && ( +
+ : } + bordered + title={Locale.Chat.Actions.FullScreen} + aria={Locale.Chat.Actions.FullScreen} + onClick={() => { + config.update( + (config) => (config.tightBorder = !config.tightBorder), + ); + }} + /> +
+ )} +
- -
+ +
+
+
+
onChatBodyScroll(e.currentTarget)} + onMouseDown={() => inputRef.current?.blur()} + onTouchStart={() => { + inputRef.current?.blur(); + setAutoScroll(false); + }} + > + {messages.map((message, i) => { + const isUser = message.role === "user"; + const isContext = i < context.length; + const showActions = + i > 0 && + !(message.preview || message.content.length === 0) && + !isContext; + const showTyping = message.preview || message.streaming; + + const shouldShowClearContextDivider = + i === clearContextIndex - 1; -
onChatBodyScroll(e.currentTarget)} - onMouseDown={() => inputRef.current?.blur()} - onTouchStart={() => { - inputRef.current?.blur(); - setAutoScroll(false); - }} - > - {messages.map((message, i) => { - const isUser = message.role === "user"; - const isContext = i < context.length; - const showActions = - i > 0 && - !(message.preview || message.content.length === 0) && - !isContext; - const showTyping = message.preview || message.streaming; - - const shouldShowClearContextDivider = i === clearContextIndex - 1; - - return ( - -
-
-
-
-
- } - aria={Locale.Chat.Actions.Edit} - onClick={async () => { - const newMessage = await showPrompt( - Locale.Chat.Actions.Edit, - getMessageTextContent(message), - 10, - ); - let newContent: string | MultimodalContent[] = - newMessage; - const images = getMessageImages(message); - if (images.length > 0) { - newContent = [{ type: "text", text: newMessage }]; - for (let i = 0; i < images.length; i++) { - newContent.push({ - type: "image_url", - image_url: { - url: images[i], - }, - }); - } - } - chatStore.updateTargetSession( - session, - (session) => { - const m = session.mask.context - .concat(session.messages) - .find((m) => m.id === message.id); - if (m) { - m.content = newContent; - } - }, - ); - }} - > -
- {isUser ? ( - - ) : ( - <> - {["system"].includes(message.role) ? ( - - ) : ( - - )} - - )} -
- {!isUser && ( -
- {message.model} -
- )} - - {showActions && ( -
-
- {message.streaming ? ( - } - onClick={() => onUserStop(message.id ?? i)} - /> - ) : ( - <> - } - onClick={() => onResend(message)} - /> - - } - onClick={() => onDelete(message.id ?? i)} - /> - - } - onClick={() => onPinMessage(message)} - /> - } - onClick={() => - copyToClipboard( + return ( + +
+
+
+
+
+ } + aria={Locale.Chat.Actions.Edit} + onClick={async () => { + const newMessage = await showPrompt( + Locale.Chat.Actions.Edit, getMessageTextContent(message), - ) - } - /> - {config.ttsConfig.enable && ( - - ) : ( - - ) - } - onClick={() => - openaiSpeech(getMessageTextContent(message)) + 10, + ); + let newContent: string | MultimodalContent[] = + newMessage; + const images = getMessageImages(message); + if (images.length > 0) { + newContent = [ + { type: "text", text: newMessage }, + ]; + for (let i = 0; i < images.length; i++) { + newContent.push({ + type: "image_url", + image_url: { + url: images[i], + }, + }); + } } - /> - )} - + chatStore.updateTargetSession( + session, + (session) => { + const m = session.mask.context + .concat(session.messages) + .find((m) => m.id === message.id); + if (m) { + m.content = newContent; + } + }, + ); + }} + > +
+ {isUser ? ( + + ) : ( + <> + {["system"].includes(message.role) ? ( + + ) : ( + + )} + + )} +
+ {!isUser && ( +
+ {message.model} +
)} -
-
- )} -
- {message?.tools?.length == 0 && showTyping && ( -
- {Locale.Chat.Typing} -
- )} - {/*@ts-ignore*/} - {message?.tools?.length > 0 && ( -
- {message?.tools?.map((tool) => ( -
- {tool.isError === false ? ( - - ) : tool.isError === true ? ( - - ) : ( - + + {showActions && ( +
+
+ {message.streaming ? ( + } + onClick={() => onUserStop(message.id ?? i)} + /> + ) : ( + <> + } + onClick={() => onResend(message)} + /> + + } + onClick={() => onDelete(message.id ?? i)} + /> + + } + onClick={() => onPinMessage(message)} + /> + } + onClick={() => + copyToClipboard( + getMessageTextContent(message), + ) + } + /> + {config.ttsConfig.enable && ( + + ) : ( + + ) + } + onClick={() => + openaiSpeech( + getMessageTextContent(message), + ) + } + /> + )} + + )} +
+
)} - {tool?.function?.name}
- ))} -
- )} -
- onRightClick(e, message)} // hard to use - onDoubleClickCapture={() => { - if (!isMobileScreen) return; - setUserInput(getMessageTextContent(message)); - }} - fontSize={fontSize} - fontFamily={fontFamily} - parentRef={scrollRef} - defaultShow={i >= messages.length - 6} - /> - {getMessageImages(message).length == 1 && ( - - )} - {getMessageImages(message).length > 1 && ( -
- {getMessageImages(message).map((image, index) => { - return ( + {message?.tools?.length == 0 && showTyping && ( +
+ {Locale.Chat.Typing} +
+ )} + {/*@ts-ignore*/} + {message?.tools?.length > 0 && ( +
+ {message?.tools?.map((tool) => ( +
+ {tool.isError === false ? ( + + ) : tool.isError === true ? ( + + ) : ( + + )} + {tool?.function?.name} +
+ ))} +
+ )} +
+ onRightClick(e, message)} // hard to use + onDoubleClickCapture={() => { + if (!isMobileScreen) return; + setUserInput(getMessageTextContent(message)); + }} + fontSize={fontSize} + fontFamily={fontFamily} + parentRef={scrollRef} + defaultShow={i >= messages.length - 6} + /> + {getMessageImages(message).length == 1 && ( - ); - })} + )} + {getMessageImages(message).length > 1 && ( +
+ {getMessageImages(message).map((image, index) => { + return ( + + ); + })} +
+ )} +
+ {message?.audio_url && ( +
+
+ )} + +
+ {isContext + ? Locale.Chat.IsContext + : message.date.toLocaleString()} +
- )} -
- -
- {isContext - ? Locale.Chat.IsContext - : message.date.toLocaleString()} -
-
-
- {shouldShowClearContextDivider && } - - ); - })} -
- -
- - - setShowPromptModal(true)} - scrollToBottom={scrollToBottom} - hitBottom={hitBottom} - uploading={uploading} - showPromptHints={() => { - // Click again to close - if (promptHints.length > 0) { - setPromptHints([]); - return; - } - - inputRef.current?.focus(); - setUserInput("/"); - onSearch(""); - }} - setShowShortcutKeyModal={setShowShortcutKeyModal} - setUserInput={setUserInput} - /> -