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, '

$1

') + .replace(/^## (.+)$/gm, '

$1

') + .replace(/^# (.+)$/gm, '

$1

') + .replace(/^\s*[-*] (.+)$/gm, '
• $1
') + .replace(/^\s*(\d+)[.)] (.+)$/gm, '
$1 $2
') + .replace(/\n/g, '
') + + html = html + .replace(/\s*/g, '
') + .replace(/\s*(