Files
chaos-api/web/src/components/playground/CodeViewer.js
T
Apple\Apple a84f402a2b 🐛 fix: correct JSON syntax highlighting for API responses in debug panel
- Change response content language from 'javascript' to 'json' for proper highlighting
- Improve automatic JSON detection to handle both objects and arrays
- Add intelligent content type detection based on string patterns
- Include development environment debug logging for troubleshooting
- Ensure all API responses display with correct JSON syntax coloring

This fix resolves the issue where API response data was not properly
syntax highlighted, ensuring consistent JSON formatting across all
debug panel tabs (preview, request, and response).
2025-05-31 02:50:36 +08:00

217 lines
5.9 KiB
JavaScript

import React, { useState } from 'react';
import { Button, Tooltip, Toast } from '@douyinfe/semi-ui';
import { Copy } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { copy } from '../../helpers/utils';
// VS Code 深色主题样式
const codeThemeStyles = {
container: {
backgroundColor: '#1e1e1e',
color: '#d4d4d4',
fontFamily: 'Consolas, "Courier New", Monaco, "SF Mono", monospace',
fontSize: '13px',
lineHeight: '1.4',
borderRadius: '8px',
border: '1px solid #3c3c3c',
position: 'relative',
overflow: 'hidden',
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)',
},
content: {
height: '100%',
overflowY: 'auto',
overflowX: 'auto',
padding: '16px',
margin: 0,
whiteSpace: 'pre',
wordBreak: 'normal',
background: '#1e1e1e',
},
copyButton: {
position: 'absolute',
top: '12px',
right: '12px',
zIndex: 10,
backgroundColor: 'rgba(45, 45, 45, 0.9)',
border: '1px solid rgba(255, 255, 255, 0.1)',
color: '#d4d4d4',
borderRadius: '6px',
transition: 'all 0.2s ease',
},
copyButtonHover: {
backgroundColor: 'rgba(60, 60, 60, 0.95)',
borderColor: 'rgba(255, 255, 255, 0.2)',
transform: 'scale(1.05)',
},
noContent: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '100%',
color: '#666',
fontSize: '14px',
fontStyle: 'italic',
backgroundColor: 'var(--semi-color-fill-0)',
borderRadius: '8px',
}
};
// 自定义 JSON 高亮器(使用 VS Code 深色主题配色)
const highlightJson = (str) => {
return str.replace(
/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?)/g,
(match) => {
let color = '#b5cea8'; // 数字颜色 (绿色)
if (/^"/.test(match)) {
if (/:$/.test(match)) {
color = '#9cdcfe'; // 键名颜色 (蓝色)
} else {
color = '#ce9178'; // 字符串值颜色 (橙色)
}
} else if (/true|false/.test(match)) {
color = '#569cd6'; // 布尔值颜色 (蓝色)
} else if (/null/.test(match)) {
color = '#569cd6'; // null 值颜色 (蓝色)
}
return `<span style="color: ${color}">${match}</span>`;
}
);
};
const CodeViewer = ({ content, title, language = 'json' }) => {
const { t } = useTranslation();
const [copied, setCopied] = useState(false);
const [isHoveringCopy, setIsHoveringCopy] = useState(false);
const handleCopy = async () => {
try {
let textToCopy = content;
// 如果是对象,转换为格式化的 JSON 字符串
if (typeof content === 'object' && content !== null) {
textToCopy = JSON.stringify(content, null, 2);
}
const success = await copy(textToCopy);
if (success) {
setCopied(true);
Toast.success(t('已复制到剪贴板'));
setTimeout(() => setCopied(false), 2000);
} else {
Toast.error(t('复制失败'));
}
} catch (err) {
Toast.error(t('复制失败'));
console.error('Copy failed:', err);
}
};
// 格式化内容
const getFormattedContent = () => {
if (!content) return '';
if (typeof content === 'object') {
try {
return JSON.stringify(content, null, 2);
} catch (e) {
return String(content);
}
} else if (typeof content === 'string') {
// 尝试解析并重新格式化 JSON
try {
const parsed = JSON.parse(content);
return JSON.stringify(parsed, null, 2);
} catch (e) {
return content;
}
}
return String(content);
};
// 获取高亮的 HTML
const getHighlightedContent = () => {
const formattedContent = getFormattedContent();
// 尝试检测是否为 JSON 格式
const isJsonLike = () => {
if (language === 'json') return true;
// 自动检测:如果内容看起来像 JSON,就用 JSON 高亮
const trimmed = formattedContent.trim();
const looksLikeJson = (trimmed.startsWith('{') && trimmed.endsWith('}')) ||
(trimmed.startsWith('[') && trimmed.endsWith(']'));
// 调试日志
if (process.env.NODE_ENV === 'development') {
console.log('CodeViewer Debug:', {
language,
contentType: typeof content,
trimmedStart: trimmed.substring(0, 10),
looksLikeJson,
willHighlight: looksLikeJson
});
}
return looksLikeJson;
};
if (isJsonLike()) {
return highlightJson(formattedContent);
}
// 对于非 JSON 内容,使用简单的文本高亮
return formattedContent;
};
if (!content) {
return (
<div style={codeThemeStyles.noContent}>
<span>
{title === 'preview' ? t('正在构造请求体预览...') :
title === 'request' ? t('暂无请求数据') :
t('暂无响应数据')}
</span>
</div>
);
}
return (
<div style={codeThemeStyles.container} className="h-full">
{/* 复制按钮 */}
<div
style={{
...codeThemeStyles.copyButton,
...(isHoveringCopy ? codeThemeStyles.copyButtonHover : {})
}}
onMouseEnter={() => setIsHoveringCopy(true)}
onMouseLeave={() => setIsHoveringCopy(false)}
>
<Tooltip content={copied ? t('已复制') : t('复制代码')}>
<Button
icon={<Copy size={14} />}
onClick={handleCopy}
size="small"
theme="borderless"
style={{
backgroundColor: 'transparent',
border: 'none',
color: copied ? '#4ade80' : '#d4d4d4',
padding: '6px',
}}
/>
</Tooltip>
</div>
{/* 代码内容 */}
<div
style={codeThemeStyles.content}
className="model-settings-scroll"
dangerouslySetInnerHTML={{ __html: getHighlightedContent() }}
/>
</div>
);
};
export default CodeViewer;