@@ -47,7 +43,7 @@ export default function Dashboard({ api, onRescan }) {
{notifications.map(n => (
- {n.time.toLocaleTimeString(layout.locale, { hour: '2-digit', minute: '2-digit', second: '2-digit' })}
+ {n.time.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit' })}
{n.text}
diff --git a/web/src/components/Shell.jsx b/web/src/components/Shell.jsx
index 795837c..d4af5bd 100644
--- a/web/src/components/Shell.jsx
+++ b/web/src/components/Shell.jsx
@@ -8,37 +8,74 @@ import { useI18n } from '../i18n'
const MAX_TABS = 7
-const XTERM_THEME = {
- background: '#0A0A0C',
- foreground: '#EAE0E2',
- cursor: '#FF0033',
- cursorAccent: '#0A0A0C',
- selectionBackground: '#FF003344',
- selectionForeground: '#ffffff',
- black: '#0A0A0C',
- red: '#FF0033',
- green: '#00E676',
- yellow: '#FFD740',
- blue: '#448AFF',
- magenta: '#FF1A5E',
- cyan: '#00BCD4',
- white: '#EAE0E2',
- brightBlack: '#5A4F52',
- brightRed: '#FF5252',
- brightGreen: '#69F0AE',
- brightYellow: '#FFFF00',
- brightBlue: '#82B1FF',
- brightMagenta: '#FF80AB',
- brightCyan: '#84FFFF',
- brightWhite: '#FFFFFF',
+const THEMES = {
+ default: {
+ background: '#0A0A0C', foreground: '#EAE0E2', cursor: '#FF0033',
+ cursorAccent: '#0A0A0C', selectionBackground: '#FF003344', selectionForeground: '#ffffff',
+ black: '#0A0A0C', red: '#FF0033', green: '#00E676', yellow: '#FFD740',
+ blue: '#448AFF', magenta: '#FF1A5E', cyan: '#00BCD4', white: '#EAE0E2',
+ brightBlack: '#5A4F52', brightRed: '#FF5252', brightGreen: '#69F0AE',
+ brightYellow: '#FFFF00', brightBlue: '#82B1FF', brightMagenta: '#FF80AB',
+ brightCyan: '#84FFFF', brightWhite: '#FFFFFF',
+ },
+ monokai: {
+ background: '#272822', foreground: '#F8F8F2', cursor: '#F8F8F0',
+ cursorAccent: '#272822', selectionBackground: '#F8F8F244', selectionForeground: '#ffffff',
+ black: '#272822', red: '#F92672', green: '#A6E22E', yellow: '#E6DB74',
+ blue: '#66D9EF', magenta: '#AE81FF', cyan: '#A1EFE4', white: '#F8F8F2',
+ brightBlack: '#75715E', brightRed: '#F92672', brightGreen: '#A6E22E',
+ brightYellow: '#E6DB74', brightBlue: '#66D9EF', brightMagenta: '#AE81FF',
+ brightCyan: '#A1EFE4', brightWhite: '#F8F8F2',
+ },
+ gruvbox: {
+ background: '#282828', foreground: '#EBDBB2', cursor: '#FB4934',
+ cursorAccent: '#282828', selectionBackground: '#EBDBB244', selectionForeground: '#ffffff',
+ black: '#282828', red: '#CC241D', green: '#98971A', yellow: '#D79921',
+ blue: '#458588', magenta: '#B16286', cyan: '#689D6A', white: '#EBDBB2',
+ brightBlack: '#928374', brightRed: '#FB4934', brightGreen: '#B8BB26',
+ brightYellow: '#FABC2A', brightBlue: '#83A598', brightMagenta: '#D3869B',
+ brightCyan: '#8EC07C', brightWhite: '#EBDBB2',
+ },
+ nord: {
+ background: '#2E3440', foreground: '#D8DEE9', cursor: '#D8DEE9',
+ cursorAccent: '#2E3440', selectionBackground: '#D8DEE944', selectionForeground: '#ffffff',
+ black: '#2E3440', red: '#BF616A', green: '#A3BE8C', yellow: '#EBCB8B',
+ blue: '#81A1C1', magenta: '#B48EAD', cyan: '#88C0D0', white: '#D8DEE9',
+ brightBlack: '#4C566A', brightRed: '#BF616A', brightGreen: '#A3BE8C',
+ brightYellow: '#EBCB8B', brightBlue: '#81A1C1', brightMagenta: '#B48EAD',
+ brightCyan: '#8FBCBB', brightWhite: '#ECEFF4',
+ },
+ 'solarized-dark': {
+ background: '#002B36', foreground: '#839496', cursor: '#D33682',
+ cursorAccent: '#002B36', selectionBackground: '#83949644', selectionForeground: '#ffffff',
+ black: '#002B36', red: '#DC322F', green: '#859900', yellow: '#B58900',
+ blue: '#268BD2', magenta: '#D33682', cyan: '#2AA198', white: '#FDF6E3',
+ brightBlack: '#073642', brightRed: '#CB4B16', brightGreen: '#586E75',
+ brightYellow: '#657B83', brightBlue: '#6C71C4', brightMagenta: '#6C71C4',
+ brightCyan: '#93A1A1', brightWhite: '#FDF6E3',
+ },
+ dracula: {
+ background: '#282A36', foreground: '#F8F8F2', cursor: '#F8F8F2',
+ cursorAccent: '#282A36', selectionBackground: '#F8F8F244', selectionForeground: '#ffffff',
+ black: '#282A36', red: '#FF5555', green: '#50FA7B', yellow: '#F1FA8C',
+ blue: '#BD93F9', magenta: '#FF79C6', cyan: '#8BE9FD', white: '#F8F8F2',
+ brightBlack: '#6272A4', brightRed: '#FF6E6E', brightGreen: '#69FF94',
+ brightYellow: '#FFFFA5', brightBlue: '#D6ACFF', brightMagenta: '#FF92DF',
+ brightCyan: '#A4FFFF', brightWhite: '#FFFFFF',
+ },
}
-function createTerminal(container) {
+function getTheme(themeName) {
+ return THEMES[themeName] || THEMES.default
+}
+
+function createTerminal(container, settings = {}) {
+ const theme = getTheme(settings.theme || 'default')
const term = new XTerm({
cursorBlink: true,
- fontSize: 14,
- fontFamily: "'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'SF Mono', 'Menlo', monospace",
- theme: XTERM_THEME,
+ fontSize: settings.fontSize || 14,
+ fontFamily: settings.fontFamily || "'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'SF Mono', 'Menlo', monospace",
+ theme,
allowTransparency: false,
scrollback: 5000,
})
@@ -116,27 +153,30 @@ export default function Shell({ api }) {
const [showSshModal, setShowSshModal] = useState(false)
const [editingTab, setEditingTab] = useState(null)
const [editName, setEditName] = useState('')
+ const [terminalSettings, setTerminalSettings] = useState({
+ fontSize: 14,
+ fontFamily: "'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'SF Mono', 'Menlo', monospace",
+ theme: 'default',
+ })
const [sshForm, setSshForm] = useState({
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 || [])
setSystemTerminals(d.system || [])
}).catch(() => {})
+ api.getConfig().then(d => {
+ if (d.terminal) {
+ setTerminalSettings({
+ fontSize: d.terminal.font_size || 14,
+ fontFamily: d.terminal.font_family || "'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'SF Mono', 'Menlo', monospace",
+ theme: d.terminal.theme || 'default',
+ })
+ }
+ }).catch(() => {})
}, [])
const initTerminal = useCallback((tabId, tab) => {
@@ -145,7 +185,11 @@ export default function Shell({ api }) {
const container = document.getElementById(`terminal-${tabId}`)
if (!container) return
- const { term, fitAddon } = createTerminal(container)
+ const { term, fitAddon } = createTerminal(container, {
+ fontSize: terminalSettings.fontSize,
+ fontFamily: terminalSettings.fontFamily,
+ theme: terminalSettings.theme,
+ })
let initPayload
if (tab.type === 'ssh') {
@@ -307,21 +351,6 @@ 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 (
@@ -436,27 +465,6 @@ 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/components/Studio.jsx b/web/src/components/Studio.jsx
index 5fbbdf3..9350666 100644
--- a/web/src/components/Studio.jsx
+++ b/web/src/components/Studio.jsx
@@ -178,9 +178,10 @@ export default function Studio({ api }) {
try {
let accumulated = ''
- await api.sendChat(text, true).then(full => {
- accumulated = full
- }).catch(() => {})
+ await api.sendChat(text, true, (partial) => {
+ accumulated = partial
+ setStreaming(partial)
+ })
const finalContent = accumulated || t('studio.noResponse')
setMessages(prev => [...prev, {
diff --git a/web/src/i18n/en.js b/web/src/i18n/en.js
index c88b1ab..4d4d563 100644
--- a/web/src/i18n/en.js
+++ b/web/src/i18n/en.js
@@ -81,10 +81,6 @@ const en = {
shell: {
terminal: 'Terminal',
- hideAi: 'Hide AI',
- aiAssistant: 'AI Assistant',
- aiWelcome: 'I know your system inside out. Ask me anything.',
- askAi: 'Ask AI...',
send: 'Send',
noResponse: 'No response',
error: 'Error',
@@ -117,6 +113,7 @@ const en = {
panels: {
profile: 'Profile',
providers: 'AI Providers',
+ terminal: 'Terminal',
updates: 'Updates',
locale: 'Language & Keyboard',
skills: 'Skills',
@@ -174,6 +171,11 @@ const en = {
enterToken: 'Enter your API token for {provider}',
tokenPlaceholder: 'sk-...',
setupDescription: 'Configure your AI provider token to use the assistant.',
+ terminalTheme: 'Terminal Theme',
+ fontSize: 'Font Size',
+ fontFamily: 'Font Family',
+ preview: 'Preview',
+ saving: 'Saving...',
},
}
diff --git a/web/src/i18n/fr.js b/web/src/i18n/fr.js
index dfb7052..1465c65 100644
--- a/web/src/i18n/fr.js
+++ b/web/src/i18n/fr.js
@@ -81,10 +81,6 @@ const fr = {
shell: {
terminal: 'Terminal',
- hideAi: 'Masquer IA',
- aiAssistant: 'Assistant IA',
- aiWelcome: 'Je connais votre syst\u00e8me sur le bout des doigts. Demandez-moi n\u2019importe quoi.',
- askAi: 'Demander \u00e0 l\u2019IA...',
send: 'Envoyer',
noResponse: 'Pas de r\u00e9ponse',
error: 'Erreur',
@@ -117,6 +113,7 @@ const fr = {
panels: {
profile: 'Profil',
providers: 'Fournisseurs IA',
+ terminal: 'Terminal',
updates: 'Mises \u00e0 jour',
locale: 'Langue & Clavier',
skills: 'Comp\u00e9tences',
@@ -174,6 +171,11 @@ const fr = {
tokenPlaceholder: 'sk-...',
setupDescription: 'Configurez le token de votre fournisseur IA pour utiliser l\'assistant.',
cancel: 'Annuler',
+ terminalTheme: 'Th\u00e8me du terminal',
+ fontSize: 'Taille de police',
+ fontFamily: 'Police',
+ preview: 'Aper\u00e7u',
+ saving: 'Enregistrement...',
},
}
diff --git a/web/src/styles/global.css b/web/src/styles/global.css
index c402688..728f01e 100644
--- a/web/src/styles/global.css
+++ b/web/src/styles/global.css
@@ -380,7 +380,6 @@ input::placeholder { color: var(--text-disabled); }
}
.shell-xterm-instance .xterm { height: 100%; padding: 4px; }
-.shell-ai-col { width: 340px; border-left: 1px solid var(--border); background: var(--bg-surface); display: flex; flex-direction: column; flex-shrink: 0; }
.connection-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; flex-shrink: 0; }
.connection-dot.on { background: var(--success); box-shadow: 0 0 6px var(--success); }
.connection-dot.off { background: var(--error); }
@@ -510,14 +509,6 @@ input::placeholder { color: var(--text-disabled); }
.agent-name { font-weight: 600; color: var(--text-primary); font-size: 13px; }
.agent-status { font-size: 11px; color: var(--text-tertiary); margin-top: 2px; }
-.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; }
-
.empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 40px 20px; color: var(--text-disabled); font-size: 13px; text-align: center; gap: 8px; }
.dashboard-layout { display: flex; flex-direction: column; height: 100%; }