|
|
|
|
@@ -204,6 +204,10 @@ export default function Shell({ api }) {
|
|
|
|
|
const tabsRef = useRef({})
|
|
|
|
|
const nextIdRef = useRef(1)
|
|
|
|
|
const settingsRef = useRef({ fontSize: 12, fontFamily: "'JetBrains Mono', 'Fira Code', monospace", theme: 'default' })
|
|
|
|
|
const activeTabRef = useRef(null)
|
|
|
|
|
const pendingCommandsRef = useRef({})
|
|
|
|
|
|
|
|
|
|
useEffect(() => { activeTabRef.current = activeTab }, [activeTab])
|
|
|
|
|
|
|
|
|
|
const savedTabs = (() => {
|
|
|
|
|
try {
|
|
|
|
|
@@ -408,11 +412,21 @@ export default function Shell({ api }) {
|
|
|
|
|
|
|
|
|
|
const bufferSaveInterval = setInterval(() => { if (!disposed) saveBuffer() }, 5000)
|
|
|
|
|
|
|
|
|
|
console.log(`[Shell] initTerminal tab=${tabId} type=${tab.type} container=${!!container}`)
|
|
|
|
|
console.log(`[Shell] initTerminal tab=${tabId} type=${tab.type} name="${tab.name}" shell="${tab.shell || '(default)'}"`)
|
|
|
|
|
tabsRef.current[tabId] = { term, fitAddon, ws, resizeObserver, onResize, bufferSaveInterval, saveBuffer, disposed: () => disposed }
|
|
|
|
|
const origDispose = () => { disposed = true }
|
|
|
|
|
tabsRef.current[tabId]._markDisposed = origDispose
|
|
|
|
|
tabsRef.current[tabId]._markDisposed = () => { disposed = true }
|
|
|
|
|
console.log(`[Shell] initTerminal tab=${tabId} done, tabsRef keys:`, Object.keys(tabsRef.current))
|
|
|
|
|
|
|
|
|
|
const pending = pendingCommandsRef.current[tabId]
|
|
|
|
|
if (pending && pending.length > 0) {
|
|
|
|
|
console.log(`[Shell] Flushing ${pending.length} pending commands for tab ${tabId}`)
|
|
|
|
|
for (const cmd of pending) {
|
|
|
|
|
if (ws.readyState === WebSocket.OPEN) {
|
|
|
|
|
ws.send(JSON.stringify({ type: 'input', data: cmd + '\r' }))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
delete pendingCommandsRef.current[tabId]
|
|
|
|
|
}
|
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
@@ -600,24 +614,28 @@ export default function Shell({ api }) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const sendToTerminal = useCallback((code, tabId) => {
|
|
|
|
|
const targetId = tabId || activeTab
|
|
|
|
|
const targetId = tabId || activeTabRef.current
|
|
|
|
|
const entry = tabsRef.current[targetId]
|
|
|
|
|
if (!entry) {
|
|
|
|
|
console.warn(`[Shell] sendToTerminal: tab ${targetId} not in tabsRef. Available:`, Object.keys(tabsRef.current), 'activeTab:', activeTab, 'requested tabId:', tabId)
|
|
|
|
|
console.warn(`[Shell] sendToTerminal: tab ${targetId} not ready. Queueing. tabsRef:`, Object.keys(tabsRef.current), 'activeTab:', activeTabRef.current, 'requested:', tabId)
|
|
|
|
|
if (!pendingCommandsRef.current[targetId]) pendingCommandsRef.current[targetId] = []
|
|
|
|
|
pendingCommandsRef.current[targetId].push(code)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if (!entry.ws || entry.ws.readyState !== WebSocket.OPEN) {
|
|
|
|
|
console.warn(`[Shell] sendToTerminal: WebSocket not ready for tab ${targetId}, state:`, entry.ws?.readyState)
|
|
|
|
|
console.warn(`[Shell] sendToTerminal: WS not open for tab ${targetId} (state=${entry.ws?.readyState}). Queueing.`)
|
|
|
|
|
if (!pendingCommandsRef.current[targetId]) pendingCommandsRef.current[targetId] = []
|
|
|
|
|
pendingCommandsRef.current[targetId].push(code)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
console.log(`[Shell] sendToTerminal: sending code to tab ${targetId} (${code.length} chars)`)
|
|
|
|
|
console.log(`[Shell] sendToTerminal: tab ${targetId} ← ${code.length} chars`)
|
|
|
|
|
entry.ws.send(JSON.stringify({ type: 'input', data: code + '\r' }))
|
|
|
|
|
}, [activeTab])
|
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
|
|
const focusAiTerminal = useCallback(() => {
|
|
|
|
|
const entry = tabsRef.current[activeTab]
|
|
|
|
|
const entry = tabsRef.current[activeTabRef.current]
|
|
|
|
|
if (entry) entry.term.focus()
|
|
|
|
|
}, [activeTab])
|
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
|
|
const _sendAiMessage = useCallback(async (text, fromEvent = false) => {
|
|
|
|
|
if (!text || !text.trim() || aiLoadingRef.current || aiAtLimit) return
|
|
|
|
|
@@ -649,7 +667,8 @@ export default function Shell({ api }) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const currentTab = activeTab
|
|
|
|
|
const currentTab = activeTabRef.current
|
|
|
|
|
console.log(`[Shell] _sendAiMessage: activeTab=${currentTab}, fromEvent=${fromEvent}, text="${trimmed.slice(0, 50)}"`)
|
|
|
|
|
setAiMessages(prev => [...prev, { role: 'user', content: trimmed, _tabId: currentTab }])
|
|
|
|
|
setAiLoading(true)
|
|
|
|
|
|
|
|
|
|
|