diff --git a/frontend/src/pages/GeneralSettings/ChatEmbedWidgets/EmbedChats/ChatRow/index.jsx b/frontend/src/pages/GeneralSettings/ChatEmbedWidgets/EmbedChats/ChatRow/index.jsx
index bf50e61f57d..714e041df65 100644
--- a/frontend/src/pages/GeneralSettings/ChatEmbedWidgets/EmbedChats/ChatRow/index.jsx
+++ b/frontend/src/pages/GeneralSettings/ChatEmbedWidgets/EmbedChats/ChatRow/index.jsx
@@ -66,7 +66,7 @@ export default function ChatRow({ chat, onDelete }) {
onClick={openResponseModal}
className="px-6 cursor-pointer hover:shadow-lg"
>
- {truncate(JSON.parse(chat.response)?.text, 40)}
+ {truncate(safeJsonParse(chat.response, {})?.text, 40)}
{chat.createdAt} |
diff --git a/frontend/src/pages/GeneralSettings/ChatEmbedWidgets/EmbedChats/MarkdownRenderer.jsx b/frontend/src/pages/GeneralSettings/ChatEmbedWidgets/EmbedChats/MarkdownRenderer.jsx
index 11b4ca51c1e..cfaffc43535 100644
--- a/frontend/src/pages/GeneralSettings/ChatEmbedWidgets/EmbedChats/MarkdownRenderer.jsx
+++ b/frontend/src/pages/GeneralSettings/ChatEmbedWidgets/EmbedChats/MarkdownRenderer.jsx
@@ -1,5 +1,6 @@
import { useState } from "react";
import MarkdownIt from "markdown-it";
+import hljs from "highlight.js";
import { CaretDown } from "@phosphor-icons/react";
import "highlight.js/styles/github-dark.css";
import DOMPurify from "@/utils/chat/purify";
diff --git a/frontend/src/pages/GeneralSettings/ChatEmbedWidgets/EmbedChats/index.jsx b/frontend/src/pages/GeneralSettings/ChatEmbedWidgets/EmbedChats/index.jsx
index cd242422e71..9c9e7e1be68 100644
--- a/frontend/src/pages/GeneralSettings/ChatEmbedWidgets/EmbedChats/index.jsx
+++ b/frontend/src/pages/GeneralSettings/ChatEmbedWidgets/EmbedChats/index.jsx
@@ -46,16 +46,15 @@ const exportOptions = {
};
export default function EmbedChatsView() {
- const [showMenu, setShowMenu] = useState(false);
+ const { t } = useTranslation();
const menuRef = useRef();
+ const query = useQuery();
const openMenuButton = useRef();
- const { t } = useTranslation();
+ const [showMenu, setShowMenu] = useState(false);
const [loading, setLoading] = useState(true);
const [chats, setChats] = useState([]);
- const query = useQuery();
const [offset, setOffset] = useState(Number(query.get("offset") || 0));
const [canNext, setCanNext] = useState(false);
- const [showThinking, setShowThinking] = useState(true);
const handleDumpChats = async (exportType) => {
const chats = await System.exportChats(exportType, "embed");
diff --git a/frontend/src/pages/GeneralSettings/Chats/ChatRow/index.jsx b/frontend/src/pages/GeneralSettings/Chats/ChatRow/index.jsx
index 52cb02dcd7a..91054cd000b 100644
--- a/frontend/src/pages/GeneralSettings/Chats/ChatRow/index.jsx
+++ b/frontend/src/pages/GeneralSettings/Chats/ChatRow/index.jsx
@@ -3,22 +3,8 @@ import { X, Trash } from "@phosphor-icons/react";
import System from "@/models/system";
import ModalWrapper from "@/components/ModalWrapper";
import { useModal } from "@/hooks/useModal";
-
-// Some LLMs may return a "valid" response that truncation fails to truncate because
-// it stored an Object as opposed to a string for the `text` field.
-function parseText(jsonResponse = "") {
- try {
- const json = JSON.parse(jsonResponse);
- if (!json.hasOwnProperty("text"))
- throw new Error('JSON response has no property "text".');
- return typeof json.text !== "string"
- ? JSON.stringify(json.text)
- : json.text;
- } catch (e) {
- console.error(e);
- return "--failed to parse--";
- }
-}
+import MarkdownRenderer from "../MarkdownRenderer";
+import { safeJsonParse } from "@/utils/request";
export default function ChatRow({ chat, onDelete }) {
const {
@@ -63,7 +49,7 @@ export default function ChatRow({ chat, onDelete }) {
onClick={openResponseModal}
className="px-6 cursor-pointer transform transition-transform duration-200 hover:scale-105 hover:shadow-lg"
>
- {truncate(parseText(chat.response), 40)}
+ {truncate(safeJsonParse(chat.response, {})?.text, 40)}
|
{chat.createdAt} |
@@ -80,7 +66,11 @@ export default function ChatRow({ chat, onDelete }) {
+ }
closeModal={closeResponseModal}
/>
diff --git a/frontend/src/pages/GeneralSettings/Chats/MarkdownRenderer.jsx b/frontend/src/pages/GeneralSettings/Chats/MarkdownRenderer.jsx
new file mode 100644
index 00000000000..cfaffc43535
--- /dev/null
+++ b/frontend/src/pages/GeneralSettings/Chats/MarkdownRenderer.jsx
@@ -0,0 +1,88 @@
+import { useState } from "react";
+import MarkdownIt from "markdown-it";
+import hljs from "highlight.js";
+import { CaretDown } from "@phosphor-icons/react";
+import "highlight.js/styles/github-dark.css";
+import DOMPurify from "@/utils/chat/purify";
+
+const md = new MarkdownIt({
+ html: true,
+ breaks: true,
+ highlight: function (str, lang) {
+ if (lang && hljs.getLanguage(lang)) {
+ try {
+ return hljs.highlight(str, { language: lang }).value;
+ } catch (__) {}
+ }
+ return ""; // use external default escaping
+ },
+});
+
+const ThoughtBubble = ({ thought }) => {
+ const [isExpanded, setIsExpanded] = useState(false);
+
+ if (!thought) return null;
+
+ const cleanThought = thought.replace(/<\/?think>/g, "").trim();
+ if (!cleanThought) return null;
+
+ return (
+
+ setIsExpanded(!isExpanded)}
+ className="cursor-pointer flex items-center gap-x-2 text-theme-text-secondary hover:text-theme-text-primary transition-colors mb-2"
+ >
+
+ View thoughts
+
+ {isExpanded && (
+
+ )}
+
+ );
+};
+
+function parseContent(content) {
+ const parts = [];
+ let lastIndex = 0;
+ content.replace(/([^]*?)<\/think>/g, (match, thinkContent, offset) => {
+ if (offset > lastIndex) {
+ parts.push({ type: "normal", text: content.slice(lastIndex, offset) });
+ }
+ parts.push({ type: "think", text: thinkContent });
+ lastIndex = offset + match.length;
+ });
+ if (lastIndex < content.length) {
+ parts.push({ type: "normal", text: content.slice(lastIndex) });
+ }
+ return parts;
+}
+
+export default function MarkdownRenderer({ content }) {
+ if (!content) return null;
+
+ const parts = parseContent(content);
+ return (
+
+ {parts.map((part, index) => {
+ const html = md.render(part.text);
+ if (part.type === "think")
+ return ;
+ return (
+
+ );
+ })}
+
+ );
+}
|