fix: AI terminal init, Shift+Tab nav, code block rendering, command filtering
- Fix AI terminal not initializing (wait for shell col visibility, remove offsetHeight guard) - Add Shift+Tab to cycle between shell terminals - Handle unclosed code blocks in renderContent (Shell + Studio) - Filter irrelevant commands from history (short/non-alpha backend + expanded frontend exclude list) 💘 Generated with Crush Assisted-by: GLM-5.1 via Crush <crush@charm.land>
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -553,10 +554,11 @@ func (s *Server) handleRecentCommands(w http.ResponseWriter, r *http.Request) {
|
|||||||
shell = "zsh"
|
shell = "zsh"
|
||||||
}
|
}
|
||||||
lines := strings.Split(string(data), "\n")
|
lines := strings.Split(string(data), "\n")
|
||||||
start := len(lines) - 25
|
start := len(lines) - 50
|
||||||
if start < 0 {
|
if start < 0 {
|
||||||
start = 0
|
start = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := len(lines) - 1; i >= start; i-- {
|
for i := len(lines) - 1; i >= start; i-- {
|
||||||
line := strings.TrimSpace(lines[i])
|
line := strings.TrimSpace(lines[i])
|
||||||
if line == "" || strings.HasPrefix(line, "#") {
|
if line == "" || strings.HasPrefix(line, "#") {
|
||||||
@@ -573,6 +575,15 @@ func (s *Server) handleRecentCommands(w http.ResponseWriter, r *http.Request) {
|
|||||||
if line == "" {
|
if line == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
base := strings.Fields(line)[0]
|
||||||
|
if len(base) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !regexp.MustCompile(`^[a-zA-Z@./]`).MatchString(base) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
entries = append(entries, cmdEntry{Cmd: line, Shell: shell})
|
entries = append(entries, cmdEntry{Cmd: line, Shell: shell})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,13 +93,14 @@ export default function Dashboard({ api, refreshRef }) {
|
|||||||
const minimax = (quota || []).find(p => p.name === 'minimax')
|
const minimax = (quota || []).find(p => p.name === 'minimax')
|
||||||
const zai = (quota || []).find(p => p.name === 'zai')
|
const zai = (quota || []).find(p => p.name === 'zai')
|
||||||
|
|
||||||
const EXCLUDE_CMDS = ['ls', 'cd', 'pwd', 'clear', 'exit', 'history']
|
const EXCLUDE_CMDS = ['ls', 'cd', 'pwd', 'clear', 'exit', 'history', 'cat', 'echo', 'grep', 'export', 'alias', 'unalias', 'set', 'unset', 'source', '.', 'fg', 'bg', 'jobs', 'wait', 'true', 'false', 'yes', 'sleep', 'date', 'whoami', 'id', 'uname', 'hostname', 'uptime', 'df', 'free', 'top', 'htop', 'nano', 'vi', 'vim', 'less', 'more', 'tail', 'head', 'man', 'info', 'which', 'whereis', 'type', 'command', 'hash', 'builtin', 'help']
|
||||||
|
|
||||||
const topCmds = (() => {
|
const topCmds = (() => {
|
||||||
const counts = {}
|
const counts = {}
|
||||||
for (const c of recentCmds) {
|
for (const c of recentCmds) {
|
||||||
const base = c.cmd.split(/\s+/)[0]
|
const base = c.cmd.split(/\s+/)[0]
|
||||||
if (EXCLUDE_CMDS.includes(base) || !base) continue
|
if (!base || base.length < 2 || EXCLUDE_CMDS.includes(base)) continue
|
||||||
|
if (!/^[a-zA-Z@.\/]/.test(base)) continue
|
||||||
counts[base] = (counts[base] || 0) + 1
|
counts[base] = (counts[base] || 0) + 1
|
||||||
}
|
}
|
||||||
return Object.entries(counts)
|
return Object.entries(counts)
|
||||||
|
|||||||
@@ -28,7 +28,16 @@ function renderContent(text) {
|
|||||||
lastIndex = match.index + full.length
|
lastIndex = match.index + full.length
|
||||||
}
|
}
|
||||||
if (lastIndex < text.length) {
|
if (lastIndex < text.length) {
|
||||||
parts.push({ type: 'text', content: text.slice(lastIndex) })
|
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
|
return parts
|
||||||
}
|
}
|
||||||
@@ -308,7 +317,7 @@ export default function Shell({ api }) {
|
|||||||
if (tabsRef.current[tabId]) return
|
if (tabsRef.current[tabId]) return
|
||||||
|
|
||||||
const container = document.getElementById(`terminal-${tabId}`)
|
const container = document.getElementById(`terminal-${tabId}`)
|
||||||
if (!container || container.offsetHeight === 0) return
|
if (!container) return
|
||||||
|
|
||||||
const s = settingsRef.current
|
const s = settingsRef.current
|
||||||
const { term, fitAddon } = createTerminal(container, {
|
const { term, fitAddon } = createTerminal(container, {
|
||||||
@@ -368,7 +377,12 @@ export default function Shell({ api }) {
|
|||||||
if (!tab) return
|
if (!tab) return
|
||||||
|
|
||||||
const tryInit = (attempt) => {
|
const tryInit = (attempt) => {
|
||||||
if (attempt > 10) return
|
if (attempt > 20) return
|
||||||
|
const shellCol = document.querySelector('.shell-terminal-col')
|
||||||
|
if (!shellCol || shellCol.offsetParent === null) {
|
||||||
|
setTimeout(() => tryInit(attempt + 1), 150)
|
||||||
|
return
|
||||||
|
}
|
||||||
const container = document.getElementById(`terminal-${tab.id}`)
|
const container = document.getElementById(`terminal-${tab.id}`)
|
||||||
if (!container || container.offsetHeight === 0) {
|
if (!container || container.offsetHeight === 0) {
|
||||||
setTimeout(() => tryInit(attempt + 1), 100)
|
setTimeout(() => tryInit(attempt + 1), 100)
|
||||||
@@ -404,7 +418,17 @@ export default function Shell({ api }) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onKey = (e) => {
|
const onKey = (e) => {
|
||||||
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return
|
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return
|
||||||
if (!e.altKey) return
|
if (!e.altKey && !(e.key === 'Tab' && e.shiftKey)) return
|
||||||
|
|
||||||
|
if (e.key === 'Tab' && e.shiftKey) {
|
||||||
|
const shellTab = document.querySelector('.shell-layout')
|
||||||
|
if (!shellTab || shellTab.closest('.tab-hidden')) return
|
||||||
|
e.preventDefault()
|
||||||
|
const idx = tabs.findIndex(t => t.id === activeTab)
|
||||||
|
const next = (idx + 1) % tabs.length
|
||||||
|
setActiveTab(tabs[next].id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const num = parseInt(e.key)
|
const num = parseInt(e.key)
|
||||||
if (num >= 1 && num <= tabs.length) {
|
if (num >= 1 && num <= tabs.length) {
|
||||||
|
|||||||
@@ -47,7 +47,16 @@ function renderContent(text) {
|
|||||||
lastIndex = match.index + full.length
|
lastIndex = match.index + full.length
|
||||||
}
|
}
|
||||||
if (lastIndex < text.length) {
|
if (lastIndex < text.length) {
|
||||||
parts.push({ type: 'text', content: text.slice(lastIndex) })
|
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
|
return parts
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user