diff --git a/web/src/components/Shell.jsx b/web/src/components/Shell.jsx
index 6897709..637b7d4 100644
--- a/web/src/components/Shell.jsx
+++ b/web/src/components/Shell.jsx
@@ -1,73 +1,69 @@
import { useState, useRef, useEffect, useCallback } from 'react'
-import { Terminal as XTerm } from '@xterm/xterm'
-import { FitAddon } from '@xterm/addon-fit'
-import { WebLinksAddon } from '@xterm/addon-web-links'
-import { Plus, X, Monitor, Globe, ChevronDown, Pencil, Trash2, Search, Copy, Send, Eye } from 'lucide-react'
-import '@xterm/xterm/css/xterm.css'
-import { useI18n } from '../i18n'
-
-const MAX_TABS = 7
-const SHELL_MAX_TOKENS = 100000
-const TABS_STORAGE_KEY = 'muyue_shell_tabs'
-const TERMINAL_BUFFER_KEY = 'muyue_terminal_buffers'
-
-function renderContent(text) {
- const parts = []
- const codeBlockRegex = /(```[\s\S]*?```)/g
- let match
- let lastIndex = 0
- while ((match = codeBlockRegex.exec(text)) !== null) {
- if (match.index > lastIndex) {
- parts.push({ type: 'text', content: text.slice(lastIndex, match.index) })
- }
- const full = match[1]
- const firstNewline = full.indexOf('\n')
- const lang = firstNewline > -1 ? full.slice(3, firstNewline).trim() : ''
- const code = firstNewline > -1 ? full.slice(firstNewline + 1, -3) : full.slice(3, -3)
- parts.push({ type: 'code', lang, content: code })
- lastIndex = match.index + full.length
- }
- if (lastIndex < text.length) {
- const remaining = text.slice(lastIndex)
- const openBlock = remaining.match(/```(\w*)\n?([\s\S]*)$/)
- if (openBlock) {
- if (openBlock.index > 0) {
- parts.push({ type: 'text', content: remaining.slice(0, openBlock.index) })
- }
- parts.push({ type: 'code', lang: openBlock[1] || '', content: openBlock[2] || '' })
- } else {
- parts.push({ type: 'text', content: remaining })
- }
- }
- return parts
+impo
+// === Style thème système pour xterm ===
+function getCSSVariable(varName) {
+ if (typeof document === 'undefined') return null;
+ return getComputedStyle(document.documentElement).getPropertyValue(varName).trim() || null;
}
-function formatText(text) {
- let html = text
- .replace(/&/g, '&').replace(//g, '>')
+function parseHexColor(hex) {
+ if (!hex || hex.startsWith('var(')) return null;
+ hex = hex.replace('#', '');
+ if (hex.length === 3) hex = hex.split('').map(c => c + c).join('');
+ if (hex.length !== 6) return null;
+ const r = parseInt(hex.slice(0, 2), 16);
+ const g = parseInt(hex.slice(2, 4), 16);
+ const b = parseInt(hex.slice(4, 6), 16);
+ return { r, g, b };
+}
- html = html
- .replace(/\*\*(.+?)\*\*/g, '$1')
- .replace(/`([^`]+)`/g, '$1')
- .replace(/^### (.+)$/gm, '
$1
')
- .replace(/^## (.+)$/gm, '$1
')
- .replace(/^# (.+)$/gm, '$1
')
- .replace(/^\s*[-*] (.+)$/gm, '• $1
')
- .replace(/^\s*(\d+)[.)] (.+)$/gm, '$1 $2
')
- .replace(/\n/g, '
')
+function toRgbString(hex) {
+ const c = parseHexColor(hex);
+ if (!c) return '#000000';
+ return `#${c.r.toString(16).padStart(2, '0')}${c.g.toString(16).padStart(2, '0')}${c.b.toString(16).padStart(2, '0')}`;
+}
- html = html
- .replace(/
\s*
/g, '
')
- .replace(/
\s*( {
@@ -291,7 +290,7 @@ export default function Shell({ api }) {
const [terminalSettings, setTerminalSettings] = useState({
fontSize: 12,
fontFamily: "'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'SF Mono', 'Menlo', monospace",
- theme: 'default',
+ theme: 'system',
})
useEffect(() => { settingsRef.current = terminalSettings }, [terminalSettings])
@@ -356,7 +355,7 @@ export default function Shell({ api }) {
setTerminalSettings({
fontSize: d.terminal.font_size || 12,
fontFamily: d.terminal.font_family || "'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'SF Mono', 'Menlo', monospace",
- theme: d.terminal.theme || 'default',
+ theme: d.terminal.theme || 'system',
})
}
}).catch(() => {})
diff --git a/web/src/styles/global.css b/web/src/styles/global.css
index 63d34f6..1443ab8 100644
--- a/web/src/styles/global.css
+++ b/web/src/styles/global.css
@@ -1058,3 +1058,76 @@ input::placeholder { color: var(--text-disabled); }
word-break: break-word;
background: var(--bg);
}
+
+/* === XTerm Custom Styling === */
+/* Styles for xterm.js integrated with Muyue theme */
+.shell-xterm-instance .xterm {
+ padding: 4px 8px;
+}
+
+.shell-xterm-instance .xterm-viewport {
+ background-color: var(--bg-base) !important;
+}
+
+.shell-xterm-instance .xterm-screen {
+ background-color: var(--bg-base);
+}
+
+/* Scrollbar styling for xterm */
+.shell-xterm-instance .xterm-viewport::-webkit-scrollbar {
+ width: 8px;
+}
+
+.shell-xterm-instance .xterm-viewport::-webkit-scrollbar-track {
+ background: var(--bg-surface);
+}
+
+.shell-xterm-instance .xterm-viewport::-webkit-scrollbar-thumb {
+ background: var(--accent-dim);
+ border-radius: 4px;
+}
+
+.shell-xterm-instance .xterm-viewport::-webkit-scrollbar-thumb:hover {
+ background: var(--accent-dark);
+}
+
+/* Selection styling */
+.shell-xterm-instance .xterm-selection {
+ background: var(--accent-dim) !important;
+}
+
+/* Focus ring styling */
+.shell-xterm-instance .xterm:focus .xterm-helper-text-container {
+ box-shadow: none;
+}
+
+/* Ensure consistent font rendering */
+.shell-xterm-instance .xterm .xterm-char-measure-element {
+ font-family: var(--font-mono) !important;
+}
+
+/* Bell animation styling */
+.shell-xterm-instance .xterm-bell {
+ animation: xterm-bell-flash 0.3s ease-out;
+}
+
+@keyframes xterm-bell-flash {
+ 0% { opacity: 1; }
+ 50% { opacity: 0.5; }
+ 100% { opacity: 0; }
+}
+
+/* Cursor styling */
+.shell-xterm-instance .xterm-cursor {
+ outline: none !important;
+}
+
+/* Link styling for web links addon */
+.shell-xterm-instance .xterm-link {
+ color: var(--accent-light) !important;
+ text-decoration: underline;
+}
+
+.shell-xterm-instance .xterm-link:hover {
+ color: var(--accent-muted) !important;
+}