add alarm AI diagnosis demo panel
This commit is contained in:
parent
d7300dde91
commit
4d0d446686
|
|
@ -3,18 +3,23 @@ import {
|
||||||
Alert,
|
Alert,
|
||||||
Box,
|
Box,
|
||||||
Card,
|
Card,
|
||||||
|
Chip,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
Container,
|
Container,
|
||||||
Divider,
|
Divider,
|
||||||
Grid,
|
Grid,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
ListSubheader,
|
||||||
|
MenuItem,
|
||||||
|
Select,
|
||||||
|
TextField,
|
||||||
useTheme
|
useTheme
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import Typography from '@mui/material/Typography';
|
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 ErrorIcon from '@mui/icons-material/Error';
|
||||||
import WarningIcon from '@mui/icons-material/Warning';
|
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 { AxiosError, AxiosResponse } from 'axios/index';
|
||||||
import routes from '../../../Resources/routes.json';
|
import routes from '../../../Resources/routes.json';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
@ -24,6 +29,15 @@ import Button from '@mui/material/Button';
|
||||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||||
import Checkbox from '@mui/material/Checkbox';
|
import Checkbox from '@mui/material/Checkbox';
|
||||||
|
|
||||||
|
interface TestDiagnoseResult {
|
||||||
|
source: string;
|
||||||
|
alarm: string;
|
||||||
|
explanation?: string;
|
||||||
|
causes?: string[];
|
||||||
|
nextSteps?: string[];
|
||||||
|
message?: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface LogProps {
|
interface LogProps {
|
||||||
errorLoadingS3Data: boolean;
|
errorLoadingS3Data: boolean;
|
||||||
id: number;
|
id: number;
|
||||||
|
|
@ -47,11 +61,19 @@ function Log(props: LogProps) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const tokencontext = useContext(TokenContext);
|
const tokencontext = useContext(TokenContext);
|
||||||
const { removeToken } = tokencontext;
|
const { removeToken } = tokencontext;
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
const [diagnoses, setDiagnoses] = useState<{ description: string; lastSeen: string; response: DiagnosticResponse }[]>([]);
|
const [diagnoses, setDiagnoses] = useState<{ description: string; lastSeen: string; response: DiagnosticResponse }[]>([]);
|
||||||
const [diagnosisLoading, setDiagnosisLoading] = useState(false);
|
const [diagnosisLoading, setDiagnosisLoading] = useState(false);
|
||||||
const [expandedDiagnoses, setExpandedDiagnoses] = useState<Set<number>>(new Set());
|
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(() => {
|
useEffect(() => {
|
||||||
axiosConfig
|
axiosConfig
|
||||||
.get(`/GetAllErrorsForInstallation?id=${props.id}`)
|
.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 } = {
|
const warningDescriptionMap: { [key: string]: string } = {
|
||||||
// BMS warnings
|
// BMS warnings
|
||||||
"TaM1": "TaM1: BMS temperature high",
|
"TaM1": "TaM1: BMS temperature high",
|
||||||
|
|
@ -469,6 +548,103 @@ function Log(props: LogProps) {
|
||||||
<Container maxWidth="xl">
|
<Container maxWidth="xl">
|
||||||
<Grid container>
|
<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 */}
|
{/* AI Diagnosis banner — shown when loading or diagnoses are available */}
|
||||||
{diagnosisLoading && (
|
{diagnosisLoading && (
|
||||||
<Grid item xs={12} md={12}>
|
<Grid item xs={12} md={12}>
|
||||||
|
|
|
||||||
|
|
@ -130,5 +130,17 @@
|
||||||
"downloadingBatteryLog": "Das Batterieprotokoll wird heruntergeladen. Es wird im Downloads-Ordner gespeichert. Bitte warten...",
|
"downloadingBatteryLog": "Das Batterieprotokoll wird heruntergeladen. Es wird im Downloads-Ordner gespeichert. Bitte warten...",
|
||||||
"confirmBatteryLogDownload": "Möchten Sie das Batterieprotokoll wirklich herunterladen?",
|
"confirmBatteryLogDownload": "Möchten Sie das Batterieprotokoll wirklich herunterladen?",
|
||||||
"downloadBatteryLogFailed": "Herunterladen des Batterieprotokolls fehlgeschlagen, bitte versuchen Sie es erneut.",
|
"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...",
|
"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?",
|
"confirmBatteryLogDownload": "Do you really want to download battery log?",
|
||||||
"downloadBatteryLogFailed": "Download battery log failed, please try again.",
|
"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...",
|
"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?",
|
"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.",
|
"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...",
|
"downloadingBatteryLog": "Il registro della batteria è in fase di download. Verrà salvato nella cartella Download. Attendere prego...",
|
||||||
"confirmBatteryLogDownload": "Vuoi davvero scaricare il registro della batteria?",
|
"confirmBatteryLogDownload": "Vuoi davvero scaricare il registro della batteria?",
|
||||||
"downloadBatteryLogFailed": "Download del registro della batteria fallito, riprovare.",
|
"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