mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
fix(copiolot-ui): fix code markdown rendering in copilot & table (#2048)
This commit is contained in:
@@ -4,7 +4,7 @@ import React, { useEffect, useMemo, useState } from 'react'
|
||||
import { Check, Copy } from 'lucide-react'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import { Tooltip } from '@/components/emcn'
|
||||
import { Code, Tooltip } from '@/components/emcn'
|
||||
|
||||
/**
|
||||
* Recursively extracts text content from React elements
|
||||
@@ -28,56 +28,30 @@ const getTextContent = (element: React.ReactNode): string => {
|
||||
return ''
|
||||
}
|
||||
|
||||
// Fix for code block text rendering issues
|
||||
// Global layout fixes for markdown content inside the copilot panel
|
||||
if (typeof document !== 'undefined') {
|
||||
const styleId = 'copilot-markdown-fix'
|
||||
if (!document.getElementById(styleId)) {
|
||||
const style = document.createElement('style')
|
||||
style.id = styleId
|
||||
style.textContent = `
|
||||
.copilot-markdown-wrapper pre {
|
||||
color: #F5F5F5 !important;
|
||||
font-weight: 400 !important;
|
||||
text-shadow: none !important;
|
||||
filter: none !important;
|
||||
opacity: 1 !important;
|
||||
-webkit-font-smoothing: antialiased !important;
|
||||
-moz-osx-font-smoothing: grayscale !important;
|
||||
text-rendering: optimizeLegibility !important;
|
||||
/* Prevent any markdown content from expanding beyond the panel */
|
||||
.copilot-markdown-wrapper,
|
||||
.copilot-markdown-wrapper * {
|
||||
max-width: 100% !important;
|
||||
overflow: auto !important;
|
||||
}
|
||||
|
||||
.dark .copilot-markdown-wrapper pre {
|
||||
color: #F0F0F0 !important;
|
||||
}
|
||||
|
||||
.copilot-markdown-wrapper pre code,
|
||||
.copilot-markdown-wrapper pre code *,
|
||||
.copilot-markdown-wrapper pre span,
|
||||
.copilot-markdown-wrapper pre div {
|
||||
color: inherit !important;
|
||||
opacity: 1 !important;
|
||||
font-weight: 400 !important;
|
||||
text-shadow: none !important;
|
||||
filter: none !important;
|
||||
-webkit-font-smoothing: antialiased !important;
|
||||
-moz-osx-font-smoothing: grayscale !important;
|
||||
text-rendering: optimizeLegibility !important;
|
||||
}
|
||||
|
||||
/* Prevent any markdown content from expanding beyond the panel */
|
||||
.copilot-markdown-wrapper, .copilot-markdown-wrapper * {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
.copilot-markdown-wrapper p, .copilot-markdown-wrapper li {
|
||||
.copilot-markdown-wrapper p,
|
||||
.copilot-markdown-wrapper li {
|
||||
overflow-wrap: anywhere !important;
|
||||
word-break: break-word !important;
|
||||
}
|
||||
|
||||
.copilot-markdown-wrapper a {
|
||||
overflow-wrap: anywhere !important;
|
||||
word-break: break-all !important;
|
||||
}
|
||||
|
||||
.copilot-markdown-wrapper code:not(pre code) {
|
||||
white-space: normal !important;
|
||||
overflow-wrap: anywhere !important;
|
||||
@@ -219,7 +193,7 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend
|
||||
</li>
|
||||
),
|
||||
|
||||
// Code blocks
|
||||
// Code blocks - render using shared Code.Viewer for consistent styling
|
||||
pre: ({ children }: React.HTMLAttributes<HTMLPreElement>) => {
|
||||
let codeContent: React.ReactNode = children
|
||||
let language = 'code'
|
||||
@@ -272,13 +246,24 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend
|
||||
}
|
||||
}
|
||||
|
||||
// Map markdown language tag to Code.Viewer supported languages
|
||||
const normalizedLanguage = (language || '').toLowerCase()
|
||||
const viewerLanguage: 'javascript' | 'json' | 'python' =
|
||||
normalizedLanguage === 'json'
|
||||
? 'json'
|
||||
: normalizedLanguage === 'python' || normalizedLanguage === 'py'
|
||||
? 'python'
|
||||
: 'javascript'
|
||||
|
||||
return (
|
||||
<div className='my-6 w-0 min-w-full rounded-md bg-gray-900 text-sm dark:bg-black'>
|
||||
<div className='flex items-center justify-between border-gray-700 border-b px-4 py-1.5 dark:border-gray-800'>
|
||||
<span className='font-season text-gray-400 text-xs'>{language}</span>
|
||||
<div className='my-6 w-0 min-w-full overflow-hidden rounded-md border border-[var(--border-strong)] bg-[#1F1F1F] text-sm'>
|
||||
<div className='flex items-center justify-between border-[var(--border-strong)] border-b px-4 py-1.5'>
|
||||
<span className='font-season text-[#A3A3A3] text-xs'>
|
||||
{language === 'code' ? viewerLanguage : language}
|
||||
</span>
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
className='text-muted-foreground transition-colors hover:text-gray-300'
|
||||
className='text-[#A3A3A3] transition-colors hover:text-gray-300'
|
||||
title='Copy'
|
||||
>
|
||||
{showCopySuccess ? (
|
||||
@@ -288,11 +273,12 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<div className='overflow-x-auto'>
|
||||
<pre className='whitespace-pre p-4 font-mono text-[#F5F5F5] text-sm leading-relaxed dark:text-[#F0F0F0]'>
|
||||
{actualCodeText}
|
||||
</pre>
|
||||
</div>
|
||||
<Code.Viewer
|
||||
code={actualCodeText}
|
||||
showGutter
|
||||
language={viewerLanguage}
|
||||
className='m-0 rounded-none border-0 bg-transparent'
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
@@ -307,7 +293,7 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend
|
||||
if (inline) {
|
||||
return (
|
||||
<code
|
||||
className='whitespace-normal break-all rounded bg-gray-300 px-1 py-0.5 font-mono text-[#707070] text-[0.9em] dark:bg-[var(--surface-11)] dark:text-[#E8E8E8]'
|
||||
className='whitespace-normal break-all rounded border border-[var(--border-strong)] bg-[#1F1F1F] px-1 py-0.5 font-mono text-[#eeeeee] text-[0.9em]'
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -463,30 +463,40 @@ export function InlineToolCall({
|
||||
const url = params.url || ''
|
||||
const method = (params.method || '').toUpperCase()
|
||||
return (
|
||||
<div className='w-full overflow-hidden rounded border border-muted bg-card'>
|
||||
<div className='grid grid-cols-2 gap-0 border-muted/60 border-b bg-muted/40 py-1.5'>
|
||||
<div className='self-start px-2 font-medium font-season text-[#858585] text-[10px] uppercase tracking-wide dark:text-[#E0E0E0]'>
|
||||
Method
|
||||
</div>
|
||||
<div className='self-start px-2 font-medium font-season text-[#858585] text-[10px] uppercase tracking-wide dark:text-[#E0E0E0]'>
|
||||
Endpoint
|
||||
</div>
|
||||
</div>
|
||||
<div className='grid grid-cols-[auto_1fr] gap-2 py-1.5'>
|
||||
<div className='self-start px-2'>
|
||||
<span className='inline-flex rounded bg-muted px-1.5 py-0.5 font-[470] font-mono text-[#707070] text-xs dark:text-[#E8E8E8]'>
|
||||
{method || 'GET'}
|
||||
</span>
|
||||
</div>
|
||||
<div className='min-w-0 self-start px-2'>
|
||||
<span
|
||||
className='block overflow-x-auto whitespace-nowrap font-[470] font-mono text-[#707070] text-xs dark:text-[#E8E8E8]'
|
||||
title={url}
|
||||
>
|
||||
{url || 'URL not provided'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className='w-full overflow-hidden rounded-[4px] border border-[var(--border-strong)] bg-[#1F1F1F]'>
|
||||
<table className='w-full table-fixed bg-transparent'>
|
||||
<thead className='bg-transparent'>
|
||||
<tr className='border-[var(--border-strong)] border-b bg-transparent'>
|
||||
<th className='w-[26%] border-[var(--border-strong)] border-r bg-transparent px-[10px] py-[5px] text-left font-medium text-[14px] text-[var(--text-tertiary)]'>
|
||||
Method
|
||||
</th>
|
||||
<th className='w-[74%] bg-transparent px-[10px] py-[5px] text-left font-medium text-[14px] text-[var(--text-tertiary)]'>
|
||||
Endpoint
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className='bg-transparent'>
|
||||
<tr className='group relative border-[var(--border-strong)] border-t bg-transparent'>
|
||||
<td className='relative w-[26%] border-[var(--border-strong)] border-r bg-transparent p-0'>
|
||||
<div className='px-[10px] py-[8px]'>
|
||||
<span className='font-mono text-muted-foreground text-xs'>
|
||||
{method || 'GET'}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className='relative w-[74%] bg-transparent p-0'>
|
||||
<div className='min-w-0 px-[10px] py-[8px]'>
|
||||
<span
|
||||
className='block break-all font-mono text-muted-foreground text-xs'
|
||||
title={url}
|
||||
>
|
||||
{url || 'URL not provided'}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -499,7 +509,7 @@ export function InlineToolCall({
|
||||
const normalizedEntries: Array<[string, string]> = []
|
||||
Object.entries(variables).forEach(([key, value]) => {
|
||||
if (typeof value === 'object' && value !== null && 'name' in value && 'value' in value) {
|
||||
// Handle {name: "key", value: "val"} format
|
||||
// Handle { name: "KEY", value: "VAL" } format
|
||||
normalizedEntries.push([String((value as any).name), String((value as any).value)])
|
||||
} else {
|
||||
// Handle direct key-value format
|
||||
@@ -508,35 +518,48 @@ export function InlineToolCall({
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='w-full overflow-hidden rounded border border-muted bg-card'>
|
||||
<div className='grid grid-cols-[160px_1fr] gap-0 border-muted/60 border-b bg-muted/40 py-1.5'>
|
||||
<div className='self-start px-2 font-medium font-season text-[#858585] text-[11px] uppercase tracking-wide dark:text-[#E0E0E0]'>
|
||||
Name
|
||||
</div>
|
||||
<div className='self-start px-2 font-medium font-season text-[#858585] text-[11px] uppercase tracking-wide dark:text-[#E0E0E0]'>
|
||||
Value
|
||||
</div>
|
||||
</div>
|
||||
{normalizedEntries.length === 0 ? (
|
||||
<div className='px-2 py-1.5 font-[470] font-season text-[#707070] text-xs dark:text-[#E8E8E8]'>
|
||||
No variables provided
|
||||
</div>
|
||||
) : (
|
||||
<div className='divide-y divide-muted/60'>
|
||||
{normalizedEntries.map(([name, value]) => (
|
||||
<div key={name} className='grid grid-cols-[160px_1fr] gap-0 py-1.5'>
|
||||
<div className='self-start px-2 font-medium font-season text-amber-800 text-xs dark:text-amber-200'>
|
||||
{name}
|
||||
</div>
|
||||
<div className='min-w-0 self-start overflow-x-auto px-2'>
|
||||
<span className='whitespace-nowrap font-[470] font-mono text-amber-700 text-xs dark:text-amber-300'>
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className='w-full overflow-hidden rounded-[4px] border border-[var(--border-strong)] bg-[#1F1F1F]'>
|
||||
<table className='w-full table-fixed bg-transparent'>
|
||||
<thead className='bg-transparent'>
|
||||
<tr className='border-[var(--border-strong)] border-b bg-transparent'>
|
||||
<th className='w-[36%] border-[var(--border-strong)] border-r bg-transparent px-[10px] py-[5px] text-left font-medium text-[14px] text-[var(--text-tertiary)]'>
|
||||
Name
|
||||
</th>
|
||||
<th className='w-[64%] bg-transparent px-[10px] py-[5px] text-left font-medium text-[14px] text-[var(--text-tertiary)]'>
|
||||
Value
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className='bg-transparent'>
|
||||
{normalizedEntries.length === 0 ? (
|
||||
<tr className='border-[var(--border-strong)] border-t bg-transparent'>
|
||||
<td colSpan={2} className='px-[10px] py-[8px] text-muted-foreground text-xs'>
|
||||
No variables provided
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
normalizedEntries.map(([name, value]) => (
|
||||
<tr
|
||||
key={name}
|
||||
className='group relative border-[var(--border-strong)] border-t bg-transparent'
|
||||
>
|
||||
<td className='relative w-[36%] border-[var(--border-strong)] border-r bg-transparent p-0'>
|
||||
<div className='px-[10px] py-[8px]'>
|
||||
<span className='truncate font-medium text-foreground text-xs'>{name}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className='relative w-[64%] bg-transparent p-0'>
|
||||
<div className='min-w-0 px-[10px] py-[8px]'>
|
||||
<span className='block overflow-x-auto whitespace-nowrap font-mono text-muted-foreground text-xs'>
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user