/
sweetebin
/
hackaton_26
Обзор
Документация
Войти
/
sweetebin
/
hackaton_26
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
web-form-project/src/components/SourceDialog.jsx
116 строк
4 KB
sweetebin
Трассировка до первоисточника в UI + CLI-инструментарий решений для агента
28 июл 2026, 17:22
28 июл 2026, 17:22
b4de763
Код
Авторство
О чём код?
import { useEffect, useState } from 'react'; import { Alert, Box, Chip, Dialog, DialogContent, DialogTitle, Divider, IconButton, LinearProgress, Stack, Tooltip, Typography, } from '@mui/material'; import CloseIcon from '@mui/icons-material/Close'; import { getSource } from '../api/client'; const KIND_LABELS = { interview: '🎙 интервью', ticket: '🎫 обращение', backlog: '📋 задача бэклога', context: '📚 контекст продукта', }; // Конец цепочки трассировки: задача → сигнал → первичный документ. // Открывается по клику на ref источника где угодно в интерфейсе. export default function SourceDialog({ projectId, sourceId, onClose }) { const [doc, setDoc] = useState(null); const [error, setError] = useState(null); useEffect(() => { if (sourceId == null) return undefined; let cancelled = false; setDoc(null); setError(null); getSource(projectId, sourceId) .then((d) => !cancelled && setDoc(d)) .catch((e) => !cancelled && setError(e.message)); return () => { cancelled = true; }; }, [projectId, sourceId]); return ( <Dialog open={sourceId != null} onClose={onClose} maxWidth="md" fullWidth> <DialogTitle> <Stack direction="row" alignItems="center" spacing={1}> {doc && <Chip size="small" variant="outlined" label={KIND_LABELS[doc.kind] || doc.kind} />} <Typography variant="subtitle1" fontWeight={600} sx={{ flexGrow: 1 }}> {doc ? `${doc.ref}${doc.title && doc.title !== doc.ref ? ` — ${doc.title}` : ''}` : 'Первичный документ'} </Typography> <IconButton onClick={onClose} aria-label="Закрыть"> <CloseIcon /> </IconButton> </Stack> </DialogTitle> <DialogContent dividers> {error && <Alert severity="error">{error}</Alert>} {!doc && !error && <LinearProgress />} {doc && ( <> <Typography variant="body2" component="div" sx={{ whiteSpace: 'pre-wrap', maxHeight: '45vh', overflowY: 'auto', bgcolor: 'action.hover', borderRadius: 1, p: 2, }} > {doc.body || '— документ без текста —'} </Typography> {doc.signals.length > 0 && ( <> <Divider sx={{ my: 2 }} /> <Typography variant="subtitle2" sx={{ mb: 1 }}> Извлечённые сигналы ({doc.signals.filter((s) => s.type !== 'none').length}) </Typography> {doc.signals.map((s) => s.type === 'none' ? ( <Chip key={s.id} size="small" variant="outlined" label="N/A — сигналов нет" /> ) : ( <Box key={s.id} sx={{ mb: 1.5 }}> <Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 0.3 }}> <Tooltip title="Эмоциональная окраска 0–3"> <Chip size="small" color={['default', 'info', 'warning', 'error'][s.emotion] || 'default'} label={`эмоция ${s.emotion}`} /> </Tooltip> {s.needName && ( <Chip size="small" variant="outlined" label={`потребность: ${s.needName}`} /> )} </Stack> <Typography variant="body2">{s.jtbd}</Typography> {s.quote && ( <Typography variant="body2" color="text.secondary"> «{s.quote}» </Typography> )} </Box> ) )} </> )} </> )} </DialogContent> </Dialog> ); }