diff --git a/web/src/components/Shell.jsx b/web/src/components/Shell.jsx
index 8318745..9e66382 100644
--- a/web/src/components/Shell.jsx
+++ b/web/src/components/Shell.jsx
@@ -1,6 +1,73 @@
-import { useState, useRef, useEffect, useCallback } from 'react'
+import { useState, useRef, useEffect, useCallback, useMemo } 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, Bot } from 'lucide-react'
+import '@xterm/xterm/css/xterm.css'
import { useI18n } from '../i18n'
-import { Monitor } from 'lucide-react'
+
+const AI_TAB_ID = 0
+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
+}
+
+function formatText(text) {
+ let html = text
+ .replace(/&/g, '&').replace(//g, '>')
+
+ html = html
+ .replace(/\*\*(.+?)\*\*/g, '$1')
+ .replace(/`([^`]+)`/g, '$1')
+ .replace(/^### (.+)$/gm, '