diff --git a/web/src/components/Shell.jsx b/web/src/components/Shell.jsx
index 87b9868..964dcab 100644
--- a/web/src/components/Shell.jsx
+++ b/web/src/components/Shell.jsx
@@ -163,6 +163,17 @@ export default function Shell({ api }) {
name: '', host: '', port: 22, user: '', key_path: '',
})
+ const [aiMessages, setAiMessages] = useState([
+ { role: 'ai', content: t('shell.aiWelcome') }
+ ])
+ const [aiInput, setAiInput] = useState('')
+ const [aiLoading, setAiLoading] = useState(false)
+ const aiMessagesRef = useRef(null)
+
+ useEffect(() => {
+ aiMessagesRef.current?.scrollTo(0, aiMessagesRef.current.scrollHeight)
+ }, [aiMessages])
+
useEffect(() => {
api.getTerminalSessions().then(d => {
setSshConnections(d.ssh || [])
@@ -361,6 +372,21 @@ export default function Shell({ api }) {
}
}
+ const handleAiSend = async () => {
+ if (!aiInput.trim() || aiLoading) return
+ const text = aiInput.trim()
+ setAiMessages(prev => [...prev, { role: 'user', content: text }])
+ setAiInput('')
+ setAiLoading(true)
+ try {
+ const res = await api.runCommand(`echo "AI: ${text}"`, '')
+ setAiMessages(prev => [...prev, { role: 'ai', content: res.output || t('shell.noResponse') }])
+ } catch (err) {
+ setAiMessages(prev => [...prev, { role: 'ai', content: `${t('shell.error')}: ${err.message}` }])
+ }
+ setAiLoading(false)
+ }
+
return (
@@ -475,6 +501,27 @@ export default function Shell({ api }) {
+
+
{t('shell.aiAssistant')}
+
+ {aiMessages.map((msg, i) => (
+
+ {msg.content}
+
+ ))}
+ {aiLoading &&
}
+
+
+ setAiInput(e.target.value)}
+ onKeyDown={e => e.key === 'Enter' && handleAiSend()}
+ placeholder={t('shell.askAi')}
+ />
+
+
+
+
{showSshModal && (
setShowSshModal(false)}>
e.stopPropagation()}>
diff --git a/web/src/i18n/en.js b/web/src/i18n/en.js
index 4d4d563..40a3ecd 100644
--- a/web/src/i18n/en.js
+++ b/web/src/i18n/en.js
@@ -107,6 +107,9 @@ const en = {
systemTerminals: 'System terminals',
switchTerminal: 'Switch terminal',
localShell: 'Local Shell',
+ aiAssistant: 'AI Assistant',
+ aiWelcome: 'Hello! I can help you with terminal commands. Ask me anything!',
+ askAi: 'Ask AI assistant...',
},
config: {
diff --git a/web/src/i18n/fr.js b/web/src/i18n/fr.js
index 1465c65..cd32c92 100644
--- a/web/src/i18n/fr.js
+++ b/web/src/i18n/fr.js
@@ -107,6 +107,9 @@ const fr = {
systemTerminals: 'Terminaux syst\u00e8me',
switchTerminal: 'Changer de terminal',
localShell: 'Shell local',
+ aiAssistant: 'Assistant IA',
+ aiWelcome: 'Bonjour ! Je peux vous aider avec les commandes du terminal. Demandez-moi n\'importe quoi !',
+ askAi: 'Interroger l\'assistant IA...',
},
config: {
diff --git a/web/src/styles/global.css b/web/src/styles/global.css
index ea7fd40..edc234b 100644
--- a/web/src/styles/global.css
+++ b/web/src/styles/global.css
@@ -385,6 +385,15 @@ input::placeholder { color: var(--text-disabled); }
.connection-dot.on { background: var(--success); box-shadow: 0 0 6px var(--success); }
.connection-dot.off { background: var(--error); }
+.shell-ai-col { width: 320px; border-left: 1px solid var(--border); background: var(--bg-surface); display: flex; flex-direction: column; flex-shrink: 0; }
+.ai-panel-header { padding: 12px 16px; border-bottom: 1px solid var(--border); font-weight: 700; font-size: 13px; color: var(--accent); }
+.ai-panel-messages { flex: 1; overflow-y: auto; padding: 12px; display: flex; flex-direction: column; gap: 8px; }
+.ai-message { padding: 8px 12px; border-radius: var(--radius); font-size: 13px; line-height: 1.5; word-break: break-word; }
+.ai-message.ai { background: var(--bg-card); border-left: 3px solid var(--accent); }
+.ai-message.user { background: var(--accent-bg); border-left: 3px solid var(--accent-muted); }
+.ai-panel-input { display: flex; gap: 6px; padding: 10px 12px; border-top: 1px solid var(--border); }
+.ai-panel-input input { flex: 1; font-size: 13px; padding: 6px 10px; }
+
.shell-modal-overlay {
position: fixed; inset: 0; background: rgba(0,0,0,0.6);
display: flex; align-items: center; justify-content: center; z-index: 1000;
@@ -570,18 +579,60 @@ input::placeholder { color: var(--text-disabled); }
.feed-loading { display: flex; align-items: center; justify-content: center; padding: 60px 0; }
.feed-item { display: flex; gap: 10px; padding: 8px 12px; border-radius: var(--radius); animation: fadeIn 0.15s ease-out; }
.feed-item:hover { background: var(--bg-card); }
-.feed-item.user { background: var(--bg-card); border-left: 3px solid var(--accent-muted); }
-.feed-item.assistant { }
+.feed-item.user { background: var(--bg-card); border-left: 3px solid #FFD740; }
+.feed-item.assistant { border-left: 3px solid transparent; }
+.feed-item.assistant:hover { border-left-color: var(--accent-dark); }
.feed-item.system { align-items: center; gap: 8px; padding: 6px 12px; }
-.feed-avatar { width: 24px; height: 24px; border-radius: 50%; background: var(--accent-bg); color: var(--accent); display: flex; align-items: center; justify-content: center; flex-shrink: 0; margin-top: 2px; }
+.feed-avatar { width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0; margin-top: 2px; font-size: 14px; }
+.feed-avatar.user-rank { background: rgba(255, 215, 64, 0.15); }
+.feed-avatar.ai-rank { background: var(--accent-bg); }
+.feed-rank-icon { display: flex; align-items: center; justify-content: center; }
.feed-body { flex: 1; min-width: 0; }
.feed-header { display: flex; align-items: center; gap: 8px; margin-bottom: 2px; }
+.feed-rank-badge {
+ font-size: 9px; font-weight: 800; font-family: var(--font-mono);
+ padding: 1px 6px; border-radius: 3px; border: 1px solid;
+ letter-spacing: 0.5px; text-transform: uppercase;
+ background: rgba(255, 215, 64, 0.08);
+}
.feed-role { font-size: 11px; font-weight: 700; color: var(--accent); text-transform: uppercase; letter-spacing: 0.5px; }
.feed-time { font-size: 10px; color: var(--text-disabled); font-family: var(--font-mono); }
.feed-content { font-size: 14px; line-height: 1.7; color: var(--text-primary); word-break: break-word; }
.feed-system-badge { width: 6px; height: 6px; border-radius: 50%; background: var(--accent-dim); flex-shrink: 0; }
.feed-system-text { font-size: 12px; color: var(--text-tertiary); font-style: italic; flex: 1; }
+.feed-thinking-block {
+ background: var(--bg-surface); border: 1px solid var(--border); border-left: 2px solid var(--accent-dim);
+ border-radius: var(--radius); margin: 6px 0 8px; overflow: hidden;
+ transition: all 0.3s ease;
+}
+.feed-thinking-block.active {
+ border-left-color: var(--warning);
+}
+.feed-thinking-block.done {
+ border-left-color: var(--text-disabled);
+ opacity: 0.7;
+}
+.feed-thinking-block.done .feed-thinking-content {
+ max-height: 80px;
+ overflow-y: auto;
+}
+.feed-thinking-header {
+ display: flex; align-items: center; gap: 6px;
+ padding: 6px 10px; font-size: 10px; font-weight: 700;
+ color: var(--text-tertiary); text-transform: uppercase; letter-spacing: 0.5px;
+ background: var(--bg-card); border-bottom: 1px solid var(--border);
+}
+.feed-thinking-header svg { color: var(--warning); }
+.feed-thinking-dots { display: inline-flex; gap: 2px; margin-left: 4px; }
+.feed-thinking-dots span { width: 4px; height: 4px; border-radius: 50%; background: var(--warning); animation: bounce 1.2s ease-in-out infinite; }
+.feed-thinking-dots span:nth-child(2) { animation-delay: 0.15s; }
+.feed-thinking-dots span:nth-child(3) { animation-delay: 0.3s; }
+.feed-thinking-content {
+ padding: 8px 10px; font-size: 12px; color: var(--text-tertiary);
+ font-style: italic; line-height: 1.5; max-height: 120px; overflow-y: auto;
+}
+
.studio-code-block {
background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius);
overflow: hidden; margin: 8px 0;