Compare commits
2 Commits
8e50220242
...
4d0d446686
| Author | SHA1 | Date |
|---|---|---|
|
|
4d0d446686 | |
|
|
d7300dde91 |
File diff suppressed because it is too large
Load Diff
|
|
@ -3,18 +3,23 @@ import {
|
|||
Alert,
|
||||
Box,
|
||||
Card,
|
||||
Chip,
|
||||
CircularProgress,
|
||||
Container,
|
||||
Divider,
|
||||
Grid,
|
||||
IconButton,
|
||||
ListSubheader,
|
||||
MenuItem,
|
||||
Select,
|
||||
TextField,
|
||||
useTheme
|
||||
} from '@mui/material';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { FormattedMessage, useIntl } from 'react-intl';
|
||||
import ErrorIcon from '@mui/icons-material/Error';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
import axiosConfig from '../../../Resources/axiosConfig';
|
||||
import axiosConfig, { axiosConfigWithoutToken } from '../../../Resources/axiosConfig';
|
||||
import { AxiosError, AxiosResponse } from 'axios/index';
|
||||
import routes from '../../../Resources/routes.json';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
|
@ -24,6 +29,15 @@ import Button from '@mui/material/Button';
|
|||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
|
||||
interface TestDiagnoseResult {
|
||||
source: string;
|
||||
alarm: string;
|
||||
explanation?: string;
|
||||
causes?: string[];
|
||||
nextSteps?: string[];
|
||||
message?: string;
|
||||
}
|
||||
|
||||
interface LogProps {
|
||||
errorLoadingS3Data: boolean;
|
||||
id: number;
|
||||
|
|
@ -47,11 +61,19 @@ function Log(props: LogProps) {
|
|||
const navigate = useNavigate();
|
||||
const tokencontext = useContext(TokenContext);
|
||||
const { removeToken } = tokencontext;
|
||||
const intl = useIntl();
|
||||
|
||||
const [diagnoses, setDiagnoses] = useState<{ description: string; lastSeen: string; response: DiagnosticResponse }[]>([]);
|
||||
const [diagnosisLoading, setDiagnosisLoading] = useState(false);
|
||||
const [expandedDiagnoses, setExpandedDiagnoses] = useState<Set<number>>(new Set());
|
||||
|
||||
// demo panel state
|
||||
const [demoPanelOpen, setDemoPanelOpen] = useState(false);
|
||||
const [demoAlarm, setDemoAlarm] = useState<string>('AbnormalGridVoltage');
|
||||
const [demoCustom, setDemoCustom] = useState<string>('');
|
||||
const [demoLoading, setDemoLoading] = useState(false);
|
||||
const [demoResult, setDemoResult] = useState<TestDiagnoseResult | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
axiosConfig
|
||||
.get(`/GetAllErrorsForInstallation?id=${props.id}`)
|
||||
|
|
@ -174,6 +196,63 @@ function Log(props: LogProps) {
|
|||
});
|
||||
};
|
||||
|
||||
const DEMO_ALARMS = {
|
||||
sinexcel: [
|
||||
'AbnormalGridVoltage',
|
||||
'GridVoltagePhaseLoss',
|
||||
'Battery1NotConnected',
|
||||
'Battery1Overvoltage',
|
||||
'LithiumBattery1DischargeForbidden',
|
||||
'InsulationFault',
|
||||
'FanFault',
|
||||
'InverterPowerTubeFault',
|
||||
'Pv1Overvoltage',
|
||||
'IslandProtection',
|
||||
'SystemDerating',
|
||||
'DcBusOvervoltage',
|
||||
],
|
||||
growatt: [
|
||||
'StringFault',
|
||||
'PvShortCircuited',
|
||||
'DcFuseBlown',
|
||||
'DcInputVoltageTooHigh',
|
||||
'NoUtilityGrid',
|
||||
'GridVoltageOutOfRange',
|
||||
'FanFailure',
|
||||
'BatteryDisconnected',
|
||||
'BmsFault',
|
||||
'BatteryCommunicationFailure',
|
||||
'BatteryVoltageTooHigh',
|
||||
'OverTemperatureAlarm',
|
||||
],
|
||||
};
|
||||
|
||||
const runDemo = () => {
|
||||
const alarm = demoAlarm === '__custom__' ? demoCustom.trim() : demoAlarm;
|
||||
if (!alarm) return;
|
||||
setDemoLoading(true);
|
||||
setDemoResult(null);
|
||||
axiosConfigWithoutToken
|
||||
.get(`/TestDiagnoseError?errorDescription=${encodeURIComponent(alarm)}`)
|
||||
.then((res: AxiosResponse<TestDiagnoseResult>) => {
|
||||
setDemoResult(res.data);
|
||||
})
|
||||
.catch(() => {
|
||||
setDemoResult({ source: 'Error', alarm, message: 'Request failed.' });
|
||||
})
|
||||
.finally(() => setDemoLoading(false));
|
||||
};
|
||||
|
||||
const sourceChip = (source: string) => {
|
||||
if (source === 'KnowledgeBase')
|
||||
return <Chip label="Knowledge Base" size="small" sx={{ bgcolor: '#1976d2', color: '#fff', fontWeight: 'bold' }} />;
|
||||
if (source === 'MistralAI')
|
||||
return <Chip label="Mistral AI" size="small" sx={{ bgcolor: '#7b1fa2', color: '#fff', fontWeight: 'bold' }} />;
|
||||
if (source === 'MistralFailed')
|
||||
return <Chip label="Mistral failed" size="small" color="error" />;
|
||||
return <Chip label="Not available" size="small" color="default" />;
|
||||
};
|
||||
|
||||
const warningDescriptionMap: { [key: string]: string } = {
|
||||
// BMS warnings
|
||||
"TaM1": "TaM1: BMS temperature high",
|
||||
|
|
@ -469,6 +548,103 @@ function Log(props: LogProps) {
|
|||
<Container maxWidth="xl">
|
||||
<Grid container>
|
||||
|
||||
{/* ── AI Diagnosis Demo Panel ── */}
|
||||
<Grid item xs={12}>
|
||||
<Box sx={{ marginTop: '20px' }}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
onClick={() => { setDemoPanelOpen(!demoPanelOpen); setDemoResult(null); }}
|
||||
sx={{ textTransform: 'none' }}
|
||||
>
|
||||
{demoPanelOpen
|
||||
? <FormattedMessage id="demo_hide_button" defaultMessage="Hide AI Diagnosis Demo" />
|
||||
: <FormattedMessage id="demo_test_button" defaultMessage="Test AI Diagnosis" />}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{demoPanelOpen && (
|
||||
<Card sx={{ marginTop: '10px', borderLeft: '4px solid #9c27b0', padding: '16px' }}>
|
||||
<Typography variant="subtitle2" fontWeight="bold" sx={{ mb: 1.5 }}>
|
||||
<FormattedMessage id="demo_panel_title" defaultMessage="AI Diagnosis Demo" />
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: '10px', alignItems: 'flex-end' }}>
|
||||
<Select
|
||||
size="small"
|
||||
value={demoAlarm}
|
||||
onChange={e => { setDemoAlarm(e.target.value); setDemoResult(null); }}
|
||||
sx={{ minWidth: 260 }}
|
||||
>
|
||||
<ListSubheader>Sinexcel</ListSubheader>
|
||||
{DEMO_ALARMS.sinexcel.map(a => (
|
||||
<MenuItem key={a} value={a}>{a}</MenuItem>
|
||||
))}
|
||||
<ListSubheader>Growatt</ListSubheader>
|
||||
{DEMO_ALARMS.growatt.map(a => (
|
||||
<MenuItem key={a} value={a}>{a}</MenuItem>
|
||||
))}
|
||||
<ListSubheader><FormattedMessage id="demo_custom_group" defaultMessage="Custom (may use Mistral AI)" /></ListSubheader>
|
||||
<MenuItem value="__custom__"><FormattedMessage id="demo_custom_option" defaultMessage="Type custom alarm below…" /></MenuItem>
|
||||
</Select>
|
||||
|
||||
{demoAlarm === '__custom__' && (
|
||||
<TextField
|
||||
size="small"
|
||||
placeholder={intl.formatMessage({ id: 'demo_custom_placeholder', defaultMessage: 'e.g. UnknownBatteryFault' })}
|
||||
value={demoCustom}
|
||||
onChange={e => { setDemoCustom(e.target.value); setDemoResult(null); }}
|
||||
sx={{ minWidth: 220 }}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
size="small"
|
||||
onClick={runDemo}
|
||||
disabled={demoLoading || (demoAlarm === '__custom__' && !demoCustom.trim())}
|
||||
sx={{ textTransform: 'none', bgcolor: '#9c27b0', '&:hover': { bgcolor: '#7b1fa2' } }}
|
||||
>
|
||||
{demoLoading ? <CircularProgress size={18} sx={{ color: '#fff' }} /> : <FormattedMessage id="demo_diagnose_button" defaultMessage="Diagnose" />}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{demoResult && (
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: '8px', mb: 1 }}>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
<strong>{demoResult.alarm}</strong>
|
||||
</Typography>
|
||||
{sourceChip(demoResult.source)}
|
||||
</Box>
|
||||
|
||||
{demoResult.message ? (
|
||||
<Typography variant="body2" color="text.secondary">{demoResult.message}</Typography>
|
||||
) : (
|
||||
<>
|
||||
<Typography variant="body2" sx={{ mt: 0.5 }}>{demoResult.explanation}</Typography>
|
||||
<Box sx={{ mt: 1 }}>
|
||||
<Typography variant="caption" fontWeight="bold"><FormattedMessage id="ai_likely_causes" defaultMessage="Likely causes:" /></Typography>
|
||||
<ul style={{ margin: '4px 0', paddingLeft: '20px' }}>
|
||||
{(demoResult.causes ?? []).map((c, i) => (
|
||||
<li key={i}><Typography variant="caption">{c}</Typography></li>
|
||||
))}
|
||||
</ul>
|
||||
<Typography variant="caption" fontWeight="bold"><FormattedMessage id="ai_next_steps" defaultMessage="Suggested next steps:" /></Typography>
|
||||
<ol style={{ margin: '4px 0', paddingLeft: '20px' }}>
|
||||
{(demoResult.nextSteps ?? []).map((s, i) => (
|
||||
<li key={i}><Typography variant="caption">{s}</Typography></li>
|
||||
))}
|
||||
</ol>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</Card>
|
||||
)}
|
||||
</Grid>
|
||||
|
||||
{/* AI Diagnosis banner — shown when loading or diagnoses are available */}
|
||||
{diagnosisLoading && (
|
||||
<Grid item xs={12} md={12}>
|
||||
|
|
|
|||
|
|
@ -130,5 +130,17 @@
|
|||
"downloadingBatteryLog": "Das Batterieprotokoll wird heruntergeladen. Es wird im Downloads-Ordner gespeichert. Bitte warten...",
|
||||
"confirmBatteryLogDownload": "Möchten Sie das Batterieprotokoll wirklich herunterladen?",
|
||||
"downloadBatteryLogFailed": "Herunterladen des Batterieprotokolls fehlgeschlagen, bitte versuchen Sie es erneut.",
|
||||
"noReportData": "Keine Berichtsdaten gefunden."
|
||||
"noReportData": "Keine Berichtsdaten gefunden.",
|
||||
"ai_analyzing": "KI analysiert...",
|
||||
"ai_show_details": "Details anzeigen",
|
||||
"ai_show_less": "Weniger anzeigen",
|
||||
"ai_likely_causes": "Wahrscheinliche Ursachen:",
|
||||
"ai_next_steps": "Empfohlene nächste Schritte:",
|
||||
"demo_test_button": "KI-Diagnose testen",
|
||||
"demo_hide_button": "KI-Diagnose Demo ausblenden",
|
||||
"demo_panel_title": "KI-Diagnose Demo",
|
||||
"demo_custom_group": "Benutzerdefiniert (kann Mistral KI verwenden)",
|
||||
"demo_custom_option": "Benutzerdefinierten Alarm eingeben…",
|
||||
"demo_custom_placeholder": "z.B. UnknownBatteryFault",
|
||||
"demo_diagnose_button": "Diagnostizieren"
|
||||
}
|
||||
|
|
@ -112,5 +112,17 @@
|
|||
"downloadingBatteryLog": "The battery log is getting downloaded. It will be saved in the Downloads folder. Please wait...",
|
||||
"confirmBatteryLogDownload": "Do you really want to download battery log?",
|
||||
"downloadBatteryLogFailed": "Download battery log failed, please try again.",
|
||||
"noReportData": "No report data found."
|
||||
"noReportData": "No report data found.",
|
||||
"ai_analyzing": "AI is analyzing...",
|
||||
"ai_show_details": "Show details",
|
||||
"ai_show_less": "Show less",
|
||||
"ai_likely_causes": "Likely causes:",
|
||||
"ai_next_steps": "Suggested next steps:",
|
||||
"demo_test_button": "Test AI Diagnosis",
|
||||
"demo_hide_button": "Hide AI Diagnosis Demo",
|
||||
"demo_panel_title": "AI Diagnosis Demo",
|
||||
"demo_custom_group": "Custom (may use Mistral AI)",
|
||||
"demo_custom_option": "Type custom alarm below…",
|
||||
"demo_custom_placeholder": "e.g. UnknownBatteryFault",
|
||||
"demo_diagnose_button": "Diagnose"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,5 +124,17 @@
|
|||
"downloadingBatteryLog": "Le journal de la batterie est en cours de téléchargement. Il sera enregistré dans le dossier Téléchargements. Veuillez patienter...",
|
||||
"confirmBatteryLogDownload": "Voulez-vous vraiment télécharger le journal de la batterie?",
|
||||
"downloadBatteryLogFailed": "Échec du téléchargement du journal de la batterie, veuillez réessayer.",
|
||||
"noReportData": "Aucune donnée de rapport trouvée."
|
||||
"noReportData": "Aucune donnée de rapport trouvée.",
|
||||
"ai_analyzing": "L'IA analyse...",
|
||||
"ai_show_details": "Afficher les détails",
|
||||
"ai_show_less": "Afficher moins",
|
||||
"ai_likely_causes": "Causes probables :",
|
||||
"ai_next_steps": "Prochaines étapes suggérées :",
|
||||
"demo_test_button": "Tester le diagnostic IA",
|
||||
"demo_hide_button": "Masquer la démo de diagnostic IA",
|
||||
"demo_panel_title": "Démo de diagnostic IA",
|
||||
"demo_custom_group": "Personnalisé (peut utiliser Mistral IA)",
|
||||
"demo_custom_option": "Saisir une alarme personnalisée…",
|
||||
"demo_custom_placeholder": "ex. UnknownBatteryFault",
|
||||
"demo_diagnose_button": "Diagnostiquer"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,5 +135,17 @@
|
|||
"downloadingBatteryLog": "Il registro della batteria è in fase di download. Verrà salvato nella cartella Download. Attendere prego...",
|
||||
"confirmBatteryLogDownload": "Vuoi davvero scaricare il registro della batteria?",
|
||||
"downloadBatteryLogFailed": "Download del registro della batteria fallito, riprovare.",
|
||||
"noReportData": "Nessun dato del rapporto trovato."
|
||||
"noReportData": "Nessun dato del rapporto trovato.",
|
||||
"ai_analyzing": "L'IA sta analizzando...",
|
||||
"ai_show_details": "Mostra dettagli",
|
||||
"ai_show_less": "Mostra meno",
|
||||
"ai_likely_causes": "Cause probabili:",
|
||||
"ai_next_steps": "Passi successivi suggeriti:",
|
||||
"demo_test_button": "Testa diagnosi IA",
|
||||
"demo_hide_button": "Nascondi demo diagnosi IA",
|
||||
"demo_panel_title": "Demo diagnosi IA",
|
||||
"demo_custom_group": "Personalizzato (potrebbe usare Mistral IA)",
|
||||
"demo_custom_option": "Inserisci allarme personalizzato…",
|
||||
"demo_custom_placeholder": "es. UnknownBatteryFault",
|
||||
"demo_diagnose_button": "Diagnostica"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue