Enhanced Chat Embed History View (#4281)
* Enhanced Chat Embed History View * Robust Markdown Rendering Improved "Thinking" View * feat: Improve markdown rendering in chat embed history * update ui for show/hide thoughts in embed chat history * refactor -always show thoughts if available * patch unused imports and use safeJsonParse * update fallback for loading state to always reset --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> Co-authored-by: shatfield4 <seanhatfield5@gmail.com>
This commit is contained in:
parent
226802d35a
commit
01a3cc92d0
@ -1,9 +1,11 @@
|
|||||||
import truncate from "truncate";
|
import truncate from "truncate";
|
||||||
import { X, Trash, LinkSimple } from "@phosphor-icons/react";
|
import { X } from "@phosphor-icons/react";
|
||||||
import ModalWrapper from "@/components/ModalWrapper";
|
import ModalWrapper from "@/components/ModalWrapper";
|
||||||
import { useModal } from "@/hooks/useModal";
|
import { useModal } from "@/hooks/useModal";
|
||||||
import paths from "@/utils/paths";
|
import paths from "@/utils/paths";
|
||||||
import Embed from "@/models/embed";
|
import Embed from "@/models/embed";
|
||||||
|
import MarkdownRenderer from "../MarkdownRenderer";
|
||||||
|
import { safeJsonParse } from "@/utils/request";
|
||||||
|
|
||||||
export default function ChatRow({ chat, onDelete }) {
|
export default function ChatRow({ chat, onDelete }) {
|
||||||
const {
|
const {
|
||||||
@ -83,7 +85,11 @@ export default function ChatRow({ chat, onDelete }) {
|
|||||||
</ModalWrapper>
|
</ModalWrapper>
|
||||||
<ModalWrapper isOpen={isResponseOpen}>
|
<ModalWrapper isOpen={isResponseOpen}>
|
||||||
<TextPreview
|
<TextPreview
|
||||||
text={JSON.parse(chat.response)?.text}
|
text={
|
||||||
|
<MarkdownRenderer
|
||||||
|
content={safeJsonParse(chat.response, {})?.text}
|
||||||
|
/>
|
||||||
|
}
|
||||||
closeModal={closeResponseModal}
|
closeModal={closeResponseModal}
|
||||||
/>
|
/>
|
||||||
</ModalWrapper>
|
</ModalWrapper>
|
||||||
@ -118,9 +124,9 @@ const TextPreview = ({ text, closeModal }) => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full p-6">
|
<div className="w-full p-6">
|
||||||
<pre className="w-full h-[200px] py-2 px-4 whitespace-pre-line overflow-auto rounded-lg bg-zinc-900 light:bg-theme-bg-secondary border border-gray-500 text-white text-sm">
|
<div className="w-full h-[60vh] py-2 px-4 whitespace-pre-line overflow-auto rounded-lg bg-zinc-900 light:bg-theme-bg-secondary border border-gray-500 text-white text-sm">
|
||||||
{text}
|
{text}
|
||||||
</pre>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -132,11 +138,7 @@ const ConnectionDetails = ({
|
|||||||
verbose = false,
|
verbose = false,
|
||||||
connection_information,
|
connection_information,
|
||||||
}) => {
|
}) => {
|
||||||
let details = {};
|
const details = safeJsonParse(connection_information, {});
|
||||||
try {
|
|
||||||
details = JSON.parse(connection_information);
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
if (Object.keys(details).length === 0) return null;
|
if (Object.keys(details).length === 0) return null;
|
||||||
|
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
|
|||||||
@ -0,0 +1,87 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import MarkdownIt from "markdown-it";
|
||||||
|
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 (
|
||||||
|
<div className="mb-3">
|
||||||
|
<div
|
||||||
|
onClick={() => setIsExpanded(!isExpanded)}
|
||||||
|
className="cursor-pointer flex items-center gap-x-2 text-theme-text-secondary hover:text-theme-text-primary transition-colors mb-2"
|
||||||
|
>
|
||||||
|
<CaretDown
|
||||||
|
size={14}
|
||||||
|
weight="bold"
|
||||||
|
className={`transition-transform ${isExpanded ? "rotate-180" : ""}`}
|
||||||
|
/>
|
||||||
|
<span className="text-xs font-medium">View thoughts</span>
|
||||||
|
</div>
|
||||||
|
{isExpanded && (
|
||||||
|
<div className="bg-theme-bg-chat-input rounded-md p-3 border-l-2 border-theme-text-secondary/30">
|
||||||
|
<div className="text-xs text-theme-text-secondary font-mono whitespace-pre-wrap">
|
||||||
|
{cleanThought}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function parseContent(content) {
|
||||||
|
const parts = [];
|
||||||
|
let lastIndex = 0;
|
||||||
|
content.replace(/<think>([^]*?)<\/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 (
|
||||||
|
<div className="whitespace-normal">
|
||||||
|
{parts.map((part, index) => {
|
||||||
|
const html = md.render(part.text);
|
||||||
|
if (part.type === "think")
|
||||||
|
return <ThoughtBubble key={index} thought={part.text} />;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(html) }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -55,6 +55,7 @@ export default function EmbedChatsView() {
|
|||||||
const query = useQuery();
|
const query = useQuery();
|
||||||
const [offset, setOffset] = useState(Number(query.get("offset") || 0));
|
const [offset, setOffset] = useState(Number(query.get("offset") || 0));
|
||||||
const [canNext, setCanNext] = useState(false);
|
const [canNext, setCanNext] = useState(false);
|
||||||
|
const [showThinking, setShowThinking] = useState(true);
|
||||||
|
|
||||||
const handleDumpChats = async (exportType) => {
|
const handleDumpChats = async (exportType) => {
|
||||||
const chats = await System.exportChats(exportType, "embed");
|
const chats = await System.exportChats(exportType, "embed");
|
||||||
@ -92,10 +93,15 @@ export default function EmbedChatsView() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchChats() {
|
async function fetchChats() {
|
||||||
const { chats: _chats, hasPages = false } = await Embed.chats(offset);
|
setLoading(true);
|
||||||
|
await Embed.chats(offset)
|
||||||
|
.then(({ chats: _chats, hasPages = false }) => {
|
||||||
setChats(_chats);
|
setChats(_chats);
|
||||||
setCanNext(hasPages);
|
setCanNext(hasPages);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
fetchChats();
|
fetchChats();
|
||||||
}, [offset]);
|
}, [offset]);
|
||||||
@ -211,7 +217,7 @@ export default function EmbedChatsView() {
|
|||||||
: "bg-theme-bg-secondary text-theme-text-primary hover:bg-theme-hover"
|
: "bg-theme-bg-secondary text-theme-text-primary hover:bg-theme-hover"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{t("embed-chats.previous")}
|
{t("common.previous")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleNext}
|
onClick={handleNext}
|
||||||
@ -222,7 +228,7 @@ export default function EmbedChatsView() {
|
|||||||
: "bg-theme-bg-secondary text-theme-text-primary hover:bg-theme-hover"
|
: "bg-theme-bg-secondary text-theme-text-primary hover:bg-theme-hover"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{t("embed-chats.next")}
|
{t("common.next")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user