feat: add multilingual chat support with markdown rendering
Some checks failed
CI/CD Pipeline / Run Tests (push) Has been cancelled
CI/CD Pipeline / Security Scanning (push) Has been cancelled
CI/CD Pipeline / Build and Push Docker Images (api) (push) Has been cancelled
CI/CD Pipeline / Build and Push Docker Images (chat) (push) Has been cancelled
CI/CD Pipeline / Build and Push Docker Images (frontend) (push) Has been cancelled
CI/CD Pipeline / Build and Push Docker Images (worker) (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / Lint Code (push) Has started running
CI/CD Pipeline / Generate Documentation (push) Has started running

- Fix Socket.IO proxy configuration in nginx for chat connectivity
- Add Socket.IO path routing (/socket.io/) with WebSocket upgrade support
- Fix frontend healthcheck to use curl instead of wget
- Add react-markdown and remark-gfm for proper markdown rendering
- Implement language selector in chat interface (8 languages supported)
- Add language parameter to chat agent and LLM prompts
- Support English, Italian, Spanish, French, German, Portuguese, Chinese, Japanese

This resolves the chat connection issues and enables users to receive
AI responses in their preferred language with properly formatted markdown.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-20 19:14:38 +02:00
parent 8c2fa6af47
commit 6f5deb0879
6 changed files with 329 additions and 47 deletions

View File

@@ -3,20 +3,25 @@ import {
AppBar, Toolbar, Typography, Container, Box, Paper,
TextField, Button, List, ListItem, ListItemText,
CircularProgress, Chip, Grid, Card, CardContent,
Tabs, Tab, Divider, IconButton
Tabs, Tab, Divider, IconButton, Select, MenuItem,
FormControl, InputLabel
} from '@mui/material';
import {
Send as SendIcon,
Search as SearchIcon,
Description as DocIcon,
Support as SupportIcon,
CloudUpload as UploadIcon
CloudUpload as UploadIcon,
Language as LanguageIcon
} from '@mui/icons-material';
import axios from 'axios';
import io from 'socket.io-client';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000';
const CHAT_URL = import.meta.env.VITE_CHAT_URL || 'http://localhost:8001';
// Use relative URLs to work with nginx proxy in production
const API_URL = import.meta.env.VITE_API_URL || (typeof window !== 'undefined' ? window.location.origin + '/api' : 'http://localhost:8000');
const CHAT_URL = import.meta.env.VITE_CHAT_URL || (typeof window !== 'undefined' ? window.location.origin : 'http://localhost:8001');
function App() {
const [activeTab, setActiveTab] = useState(0);
@@ -56,6 +61,7 @@ function ChatInterface() {
const [input, setInput] = useState('');
const [loading, setLoading] = useState(false);
const [socket, setSocket] = useState(null);
const [language, setLanguage] = useState('en');
const messagesEndRef = useRef(null);
useEffect(() => {
@@ -90,8 +96,8 @@ function ChatInterface() {
setMessages(prev => [...prev, userMessage]);
setLoading(true);
socket.emit('chat', { message: input, history: messages });
socket.emit('chat', { message: input, history: messages, language: language });
setInput('');
};
@@ -99,11 +105,32 @@ function ChatInterface() {
<Grid container spacing={3}>
<Grid item xs={12} md={8}>
<Paper sx={{ height: '70vh', display: 'flex', flexDirection: 'column' }}>
<Box sx={{ p: 2, bgcolor: 'primary.main', color: 'white' }}>
<Typography variant="h6">Technical Support Chat</Typography>
<Typography variant="caption">
AI-powered assistant with access to datacenter documentation
</Typography>
<Box sx={{ p: 2, bgcolor: 'primary.main', color: 'white', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Box>
<Typography variant="h6">Technical Support Chat</Typography>
<Typography variant="caption">
AI-powered assistant with access to datacenter documentation
</Typography>
</Box>
<FormControl size="small" sx={{ minWidth: 120, bgcolor: 'white', borderRadius: 1 }}>
<InputLabel id="language-select-label">Language</InputLabel>
<Select
labelId="language-select-label"
value={language}
label="Language"
onChange={(e) => setLanguage(e.target.value)}
startAdornment={<LanguageIcon sx={{ mr: 0.5, color: 'action.active' }} />}
>
<MenuItem value="en">🇬🇧 English</MenuItem>
<MenuItem value="it">🇮🇹 Italiano</MenuItem>
<MenuItem value="es">🇪🇸 Español</MenuItem>
<MenuItem value="fr">🇫🇷 Français</MenuItem>
<MenuItem value="de">🇩🇪 Deutsch</MenuItem>
<MenuItem value="pt">🇵🇹 Português</MenuItem>
<MenuItem value="zh">🇨🇳 中文</MenuItem>
<MenuItem value="ja">🇯🇵 日本語</MenuItem>
</Select>
</FormControl>
</Box>
<Box sx={{ flexGrow: 1, overflow: 'auto', p: 2 }}>
@@ -117,7 +144,38 @@ function ChatInterface() {
maxWidth: '70%',
bgcolor: msg.role === 'user' ? 'primary.light' : 'grey.100'
}}>
<Typography variant="body1">{msg.content}</Typography>
{msg.role === 'user' ? (
<Typography variant="body1">{msg.content}</Typography>
) : (
<Box sx={{
'& h1, & h2, & h3': { mt: 2, mb: 1 },
'& h1': { fontSize: '1.5rem', fontWeight: 600 },
'& h2': { fontSize: '1.3rem', fontWeight: 600 },
'& h3': { fontSize: '1.1rem', fontWeight: 600 },
'& p': { mb: 1 },
'& ul, & ol': { pl: 2, mb: 1 },
'& li': { mb: 0.5 },
'& code': {
bgcolor: 'rgba(0,0,0,0.05)',
p: 0.5,
borderRadius: 1,
fontFamily: 'monospace',
fontSize: '0.9em'
},
'& pre': {
bgcolor: 'rgba(0,0,0,0.05)',
p: 2,
borderRadius: 1,
overflow: 'auto',
'& code': { bgcolor: 'transparent', p: 0 }
},
'& hr': { my: 2, border: 'none', borderTop: '1px solid rgba(0,0,0,0.12)' }
}}>
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{msg.content}
</ReactMarkdown>
</Box>
)}
{msg.related_docs && (
<Box sx={{ mt: 1 }}>
{msg.related_docs.map((doc, i) => (