summaryrefslogtreecommitdiff
path: root/frontend/src
diff options
context:
space:
mode:
authorYurenHao0426 <blackhao0426@gmail.com>2026-02-13 19:41:03 +0000
committerYurenHao0426 <blackhao0426@gmail.com>2026-02-13 19:41:03 +0000
commit711452fc3beae15153711cbed5af0f59eb5398f2 (patch)
tree25a3dfab188368bbbe9161c45806482e4f4862a4 /frontend/src
parent942749ef10851bf74d068851e6added89bea5900 (diff)
Add rendered/plain text toggle for response views
Add an Eye/EyeOff toggle button to switch between rendered markdown and raw plain text in all three response locations: main response panel, expanded response modal, and Quick Chat messages. Useful for copying raw text without formatting artifacts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'frontend/src')
-rw-r--r--frontend/src/components/Sidebar.tsx53
1 files changed, 47 insertions, 6 deletions
diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx
index 079b4d5..b73c513 100644
--- a/frontend/src/components/Sidebar.tsx
+++ b/frontend/src/components/Sidebar.tsx
@@ -17,7 +17,7 @@ const preprocessLaTeX = (content: string): string => {
.replace(/\\\(([\s\S]*?)\\\)/g, (_, math) => `$${math}$`);
};
-import { Play, Settings, Info, ChevronLeft, ChevronRight, Maximize2, Edit3, X, Check, FileText, MessageCircle, Send, GripVertical, GitMerge, Trash2, AlertCircle, Loader2, Navigation, Upload, Search, Link, Layers } from 'lucide-react';
+import { Play, Settings, Info, ChevronLeft, ChevronRight, Maximize2, Edit3, X, Check, FileText, MessageCircle, Send, GripVertical, GitMerge, Trash2, AlertCircle, Loader2, Navigation, Upload, Search, Link, Layers, Eye, EyeOff } from 'lucide-react';
interface SidebarProps {
isOpen: boolean;
@@ -55,6 +55,7 @@ const Sidebar: React.FC<SidebarProps> = ({ isOpen, onToggle, onInteract }) => {
const [isModalOpen, setIsModalOpen] = useState(false);
const [isEditing, setIsEditing] = useState(false);
const [editedResponse, setEditedResponse] = useState('');
+ const [rawTextMode, setRawTextMode] = useState(false);
// Summary states
const [showSummaryModal, setShowSummaryModal] = useState(false);
@@ -1855,6 +1856,15 @@ const Sidebar: React.FC<SidebarProps> = ({ isOpen, onToggle, onInteract }) => {
{selectedNode.data.response && (
<>
<button
+ onClick={() => setRawTextMode(!rawTextMode)}
+ className={`p-1 rounded ${rawTextMode
+ ? (isDark ? 'bg-blue-900/50 text-blue-400' : 'bg-blue-100 text-blue-600')
+ : (isDark ? 'text-gray-400 hover:bg-gray-700' : 'text-gray-500 hover:bg-gray-200')}`}
+ title={rawTextMode ? 'Rendered view' : 'Plain text'}
+ >
+ {rawTextMode ? <Eye size={14} /> : <EyeOff size={14} />}
+ </button>
+ <button
onClick={() => setShowSummaryModal(true)}
disabled={isSummarizing}
className="p-1 hover:bg-gray-200 rounded text-gray-500 hover:text-gray-700 disabled:opacity-50"
@@ -1909,10 +1919,16 @@ const Sidebar: React.FC<SidebarProps> = ({ isOpen, onToggle, onInteract }) => {
</button>
</div>
</div>
+ ) : rawTextMode ? (
+ <pre className={`p-3 rounded-md border min-h-[150px] text-sm whitespace-pre-wrap break-words overflow-auto ${
+ isDark
+ ? 'bg-gray-900 border-gray-700 text-gray-200'
+ : 'bg-gray-50 border-gray-200 text-gray-900'
+ }`}>{selectedNode.data.response || (streamingNodeId === selectedNode.id ? streamBuffer : '')}</pre>
) : (
<div className={`p-3 rounded-md border min-h-[150px] text-sm prose prose-sm max-w-none ${
- isDark
- ? 'bg-gray-900 border-gray-700 prose-invert text-gray-200'
+ isDark
+ ? 'bg-gray-900 border-gray-700 prose-invert text-gray-200'
: 'bg-gray-50 border-gray-200 text-gray-900'
}`}>
<ReactMarkdown remarkPlugins={[remarkGfm, remarkMath]} rehypePlugins={[rehypeKatex]}>{preprocessLaTeX(selectedNode.data.response || (streamingNodeId === selectedNode.id ? streamBuffer : ''))}</ReactMarkdown>
@@ -2186,18 +2202,30 @@ const Sidebar: React.FC<SidebarProps> = ({ isOpen, onToggle, onInteract }) => {
{/* Modal Header */}
<div className={`flex items-center justify-between p-4 border-b ${isDark ? 'border-gray-700' : 'border-gray-200'}`}>
<h3 className="font-semibold text-lg">{selectedNode.data.label} - Response</h3>
- <div className="flex gap-2">
+ <div className="flex gap-2 items-center">
+ {!isEditing && (
+ <button
+ onClick={() => setRawTextMode(!rawTextMode)}
+ className={`px-3 py-1 text-sm rounded flex items-center gap-1 ${rawTextMode
+ ? (isDark ? 'bg-blue-900/50 text-blue-400' : 'bg-blue-100 text-blue-600')
+ : (isDark ? 'text-gray-400 hover:bg-gray-700' : 'text-gray-600 hover:bg-gray-100')}`}
+ title={rawTextMode ? 'Rendered view' : 'Plain text'}
+ >
+ {rawTextMode ? <Eye size={14} /> : <EyeOff size={14} />}
+ {rawTextMode ? 'Rendered' : 'Plain text'}
+ </button>
+ )}
{!isEditing && (
<button
onClick={() => setIsEditing(true)}
- className="px-3 py-1 text-sm text-gray-600 hover:bg-gray-100 rounded flex items-center gap-1"
+ className={`px-3 py-1 text-sm rounded flex items-center gap-1 ${isDark ? 'text-gray-400 hover:bg-gray-700' : 'text-gray-600 hover:bg-gray-100'}`}
>
<Edit3 size={14} /> Edit
</button>
)}
<button
onClick={() => { setIsModalOpen(false); setIsEditing(false); }}
- className="p-1 hover:bg-gray-200 rounded text-gray-500"
+ className={`p-1 rounded ${isDark ? 'text-gray-400 hover:bg-gray-700' : 'text-gray-500 hover:bg-gray-200'}`}
>
<X size={18} />
</button>
@@ -2212,6 +2240,8 @@ const Sidebar: React.FC<SidebarProps> = ({ isOpen, onToggle, onInteract }) => {
onChange={(e) => setEditedResponse(e.target.value)}
className="w-full h-full min-h-[400px] border border-gray-300 rounded-md p-3 text-sm font-mono focus:ring-2 focus:ring-blue-500 resize-y"
/>
+ ) : rawTextMode ? (
+ <pre className={`text-sm whitespace-pre-wrap break-words ${isDark ? 'text-gray-200' : 'text-gray-900'}`}>{selectedNode.data.response || ''}</pre>
) : (
<div className={`prose prose-sm max-w-none ${isDark ? 'prose-invert' : ''}`}>
<ReactMarkdown remarkPlugins={[remarkGfm, remarkMath]} rehypePlugins={[rehypeKatex]}>{preprocessLaTeX(selectedNode.data.response || '')}</ReactMarkdown>
@@ -2527,6 +2557,15 @@ const Sidebar: React.FC<SidebarProps> = ({ isOpen, onToggle, onInteract }) => {
<option value="o3" disabled={!canUsePremiumModels}>o3 {!canUsePremiumModels && '🔒'}</option>
</optgroup>
</select>
+ <button
+ onClick={() => setRawTextMode(!rawTextMode)}
+ className={`p-1 rounded ${rawTextMode
+ ? (isDark ? 'bg-blue-900/50 text-blue-400' : 'bg-blue-100 text-blue-600')
+ : (isDark ? 'text-gray-400 hover:bg-gray-700' : 'text-gray-500 hover:bg-gray-200')}`}
+ title={rawTextMode ? 'Rendered view' : 'Plain text'}
+ >
+ {rawTextMode ? <Eye size={16} /> : <EyeOff size={16} />}
+ </button>
<button onClick={closeQuickChat} className={`p-1 rounded ${isDark ? 'hover:bg-gray-700' : 'hover:bg-gray-200'}`}>
<X size={20} className={isDark ? 'text-gray-400' : 'text-gray-500'} />
</button>
@@ -2608,6 +2647,8 @@ const Sidebar: React.FC<SidebarProps> = ({ isOpen, onToggle, onInteract }) => {
{msg.role === 'user' ? (
<p className="whitespace-pre-wrap">{msg.content}</p>
+ ) : rawTextMode ? (
+ <pre className="text-sm whitespace-pre-wrap break-words">{msg.content}</pre>
) : (
<div className={`prose prose-sm max-w-none ${isDark ? 'prose-invert' : ''}`}>
<ReactMarkdown remarkPlugins={[remarkGfm, remarkMath]} rehypePlugins={[rehypeKatex]}>{preprocessLaTeX(msg.content)}</ReactMarkdown>