Files
opsxshell/src/components/ai/AgentMarkdown.tsx
T
Logan.Li b0d0e3a5f3 feat(i18n): add multi-language support (ja/ko/vi/ru/zh-TW) with system auto-detection
- Extend AppLanguage to 7 locales: zh-CN, zh-TW, en, ja, ko, vi, ru
- Auto-detect system language via navigator.languages; fallback to English
- Add BCP47 locale mapping for date formatting and sorting
- Create 50 translation JSON files (5 languages × 10 namespaces, 1137 keys each)
- Update language selector with native language names
- Extend locale key parity test to cover all 7 languages (70 assertions)
- Add VNC/RDP desktop, serial terminal, AI agent, port forwarding, and SFTP features
- Add credential encryption, host metrics, session backup crypto, and zmodem bridge
- Update Rust backend: desktop services, host metrics, SSH/SFTP enhancements
2026-08-12 00:07:48 +08:00

104 lines
3.9 KiB
TypeScript

import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
interface AgentMarkdownProps {
content: string
className?: string
}
/** 简单检测是否含 Markdown 结构(标题/表格/引用/列表/粗体) */
export function looksLikeMarkdown(text: string): boolean {
const t = text.trim()
if (!t) return false
return (
/^#{1,3}\s/m.test(t) ||
/^\|.+\|/m.test(t) ||
/^>\s/m.test(t) ||
/^[-*]\s/m.test(t) ||
/^\d+\.\s/m.test(t) ||
/\*\*[^*]+\*\*/.test(t) ||
/```/.test(t)
)
}
/** Agent 结论富文本:对齐 aiops AgentMarkdown,使用本产品色板 */
export function AgentMarkdown({ content, className = '' }: AgentMarkdownProps) {
return (
<div className={`agent-md min-w-0 ${className}`.trim()}>
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={{
h1: ({ children }) => (
<h1 className="mb-2 mt-3 text-base font-bold text-content-primary first:mt-0">{children}</h1>
),
h2: ({ children }) => (
<h2 className="mb-1.5 mt-3 text-sm font-semibold text-content-primary first:mt-0">{children}</h2>
),
h3: ({ children }) => (
<h3 className="mb-1 mt-2 text-sm font-medium text-content-primary first:mt-0">{children}</h3>
),
p: ({ children }) => (
<p className="my-1.5 text-xs leading-relaxed text-content-secondary">{children}</p>
),
ul: ({ children }) => (
<ul className="my-1.5 list-disc space-y-0.5 pl-5 text-xs text-content-secondary">{children}</ul>
),
ol: ({ children }) => (
<ol className="my-1.5 list-decimal space-y-0.5 pl-5 text-xs text-content-secondary">{children}</ol>
),
li: ({ children }) => <li className="leading-relaxed">{children}</li>,
strong: ({ children }) => (
<strong className="font-semibold text-content-primary">{children}</strong>
),
blockquote: ({ children }) => (
<blockquote className="my-2 border-l-2 border-accent-blue/50 pl-3 text-xs italic text-txt-muted">
{children}
</blockquote>
),
table: ({ children }) => (
<div className="border-dark-700/60 my-2 overflow-x-auto rounded-md border">
<table className="w-full border-collapse text-[11px]">{children}</table>
</div>
),
thead: ({ children }) => <thead className="bg-dark-900/80">{children}</thead>,
th: ({ children }) => (
<th className="border-dark-700/60 whitespace-nowrap border-b px-2.5 py-1.5 text-left font-medium text-content-primary">
{children}
</th>
),
td: ({ children }) => (
<td className="border-dark-700/40 border-b px-2.5 py-1.5 text-content-secondary">{children}</td>
),
tr: ({ children }) => <tr className="hover:bg-dark-800/40">{children}</tr>,
code: ({ className: codeClass, children }) => {
const isBlock = Boolean(codeClass?.includes('language-'))
if (isBlock) {
return (
<pre className="bg-dark-950/60 my-2 overflow-x-auto rounded px-2 py-1.5 text-[11px] text-content-secondary">
<code>{children}</code>
</pre>
)
}
return (
<code className="bg-dark-800 rounded px-1 py-0.5 text-[11px] text-content-primary">{children}</code>
)
},
hr: () => <hr className="border-dark-700/60 my-3" />,
a: ({ href, children }) => (
<a
href={href}
target="_blank"
rel="noreferrer"
className="text-accent-blue underline-offset-2 hover:underline"
>
{children}
</a>
),
}}
>
{content}
</ReactMarkdown>
</div>
)
}