fix: Rules of Hooks — Terminal state提升到App顶层
- 移除JSX内IIFE(违反Rules of Hooks) - termCmd/termOut/termRunning→App顶层useState - 切换标签不再触发hook数量变化
This commit is contained in:
@@ -28,6 +28,23 @@ export default function App() {
|
|||||||
// Connection state
|
// Connection state
|
||||||
const [connStatus, setConnStatus] = useState<'disconnected' | 'connecting' | 'connected' | 'error'>('disconnected');
|
const [connStatus, setConnStatus] = useState<'disconnected' | 'connecting' | 'connected' | 'error'>('disconnected');
|
||||||
|
|
||||||
|
// Terminal state
|
||||||
|
const [termCmd, setTermCmd] = useState('');
|
||||||
|
const [termOut, setTermOut] = useState('Execute remote commands via Gateway MCP.\nType a shell command and press Execute.\n');
|
||||||
|
const [termRunning, setTermRunning] = useState(false);
|
||||||
|
const runTermCmd = async () => {
|
||||||
|
if (!termCmd.trim() || termRunning) return;
|
||||||
|
const c = termCmd.trim(); setTermCmd(''); setTermRunning(true);
|
||||||
|
setTermOut(o => o + `$ ${c}\n`);
|
||||||
|
try {
|
||||||
|
const r = await fetch(`${gatewayUrl}/tools/exec`, { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({command:c}), signal:AbortSignal.timeout(30000) });
|
||||||
|
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
||||||
|
const d = await r.json() as any;
|
||||||
|
setTermOut(o => o + (d.stdout||d.stderr||JSON.stringify(d)) + '\n');
|
||||||
|
} catch(e:any) { setTermOut(o => o + `Error: ${e.message}\n`); }
|
||||||
|
finally { setTermRunning(false); }
|
||||||
|
};
|
||||||
|
|
||||||
/** Send chat message to Gateway */
|
/** Send chat message to Gateway */
|
||||||
const sendMessage = async () => {
|
const sendMessage = async () => {
|
||||||
const text = input.trim();
|
const text = input.trim();
|
||||||
@@ -130,36 +147,17 @@ export default function App() {
|
|||||||
|
|
||||||
{/* ── Terminal Tab ── */}
|
{/* ── Terminal Tab ── */}
|
||||||
{tab === 'terminal' && (
|
{tab === 'terminal' && (
|
||||||
(() => {
|
|
||||||
const [cmd, setCmd] = useState('');
|
|
||||||
const [out, setOut] = useState('Execute remote commands via Gateway MCP.\nType a shell command and press Execute.\n');
|
|
||||||
const [running, setRunning] = useState(false);
|
|
||||||
const runCmd = async () => {
|
|
||||||
if (!cmd.trim() || running) return;
|
|
||||||
const c = cmd.trim(); setCmd(''); setRunning(true);
|
|
||||||
setOut(o => o + `$ ${c}\n`);
|
|
||||||
try {
|
|
||||||
const r = await fetch(`${gatewayUrl}/tools/exec`, { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({command:c}), signal:AbortSignal.timeout(30000) });
|
|
||||||
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
|
||||||
const d = await r.json() as any;
|
|
||||||
setOut(o => o + (d.stdout||d.stderr||JSON.stringify(d)) + '\n');
|
|
||||||
} catch(e:any) { setOut(o => o + `Error: ${e.message}\n`); }
|
|
||||||
finally { setRunning(false); }
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<View style={styles.chatContainer}>
|
<View style={styles.chatContainer}>
|
||||||
<ScrollView style={styles.termOutput}>
|
<ScrollView style={styles.termOutput}>
|
||||||
<Text style={styles.termText} selectable>{out}</Text>
|
<Text style={styles.termText} selectable>{termOut}</Text>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
<View style={styles.inputBar}>
|
<View style={styles.inputBar}>
|
||||||
<TextInput style={styles.chatInput} value={cmd} onChangeText={setCmd} placeholder="ls -la /home" placeholderTextColor="#475569" autoCapitalize="none" autoCorrect={false} onSubmitEditing={runCmd} returnKeyType="send" />
|
<TextInput style={styles.chatInput} value={termCmd} onChangeText={setTermCmd} placeholder="ls -la /home" placeholderTextColor="#475569" autoCapitalize="none" autoCorrect={false} onSubmitEditing={runTermCmd} returnKeyType="send" />
|
||||||
<TouchableOpacity style={[styles.sendBtn, running && styles.sendBtnDisabled]} onPress={runCmd} disabled={running}>
|
<TouchableOpacity style={[styles.sendBtn, termRunning && styles.sendBtnDisabled]} onPress={runTermCmd} disabled={termRunning}>
|
||||||
<Text style={styles.sendBtnText}>{running ? '...' : 'Run'}</Text>
|
<Text style={styles.sendBtnText}>{termRunning ? '...' : 'Run'}</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
|
||||||
})()
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* ── Settings Tab ── */}
|
{/* ── Settings Tab ── */}
|
||||||
|
|||||||
Reference in New Issue
Block a user