File size: 4,870 Bytes
f247952
ded9881
 
 
 
f247952
 
 
 
 
 
 
 
ded9881
 
f247952
 
 
ded9881
 
 
f247952
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ded9881
 
 
 
 
 
 
 
f247952
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ded9881
f247952
 
ded9881
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f247952
ded9881
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f247952
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import { useEffect, useMemo, useState } from 'react';
import { Alert, AlertTitle, Box, Button, Link, Typography } from '@mui/material';
import { useAgentStore } from '@/store/agentStore';
import { apiFetch } from '@/utils/api';
import { inferenceCreditCta, isInferenceCreditError } from '@/utils/inferenceBilling';

interface ChatErrorBannerProps {
  error: string;
  sessionId: string;
  model?: string | null;
  onDismiss: () => void;
}

const DISCUSSIONS_URL = 'https://huggingface.co/spaces/smolagents/ml-intern/discussions';

export default function ChatErrorBanner({ error, sessionId, model, onDismiss }: ChatErrorBannerProps) {
  const [copied, setCopied] = useState(false);
  const [reportedAt, setReportedAt] = useState(() => new Date().toISOString());
  const userPlan = useAgentStore((s) => s.user?.plan);
  const isCreditError = isInferenceCreditError(error);
  const creditCta = isCreditError ? inferenceCreditCta(userPlan) : null;

  useEffect(() => {
    setReportedAt(new Date().toISOString());
    setCopied(false);
  }, [error]);

  const details = useMemo(
    () => [
      'ML Intern message failure',
      `time: ${reportedAt}`,
      `session: ${sessionId}`,
      `model: ${model || 'unknown'}`,
      `error: ${error}`,
    ].join('\n'),
    [error, model, reportedAt, sessionId],
  );

  const copyDetails = async () => {
    try {
      await navigator.clipboard.writeText(details);
      setCopied(true);
    } catch {
      setCopied(false);
    }
  };

  const trackProClick = () => {
    if (userPlan === 'pro') return;
    void apiFetch(`/api/pro-click/${sessionId}`, {
      method: 'POST',
      body: JSON.stringify({ source: 'inference_credit_error', target: 'hf_pro' }),
    }).catch(() => {});
  };

  return (
    <Box sx={{ maxWidth: 880, mx: 'auto', width: '100%', px: { xs: 0, sm: 1, md: 2 }, mb: 1 }}>
      <Alert
        severity="error"
        variant="outlined"
        onClose={onDismiss}
        sx={{
          bgcolor: 'rgba(224, 90, 79, 0.08)',
          borderColor: 'rgba(224, 90, 79, 0.4)',
          color: 'var(--text)',
          '& .MuiAlert-icon': { color: 'var(--accent-red)' },
        }}
        action={
          <Box sx={{ display: 'flex', gap: 0.5 }}>
            <Button color="inherit" size="small" onClick={copyDetails} sx={{ textTransform: 'none' }}>
              {copied ? 'Copied' : 'Copy details'}
            </Button>
            <Button color="inherit" size="small" onClick={onDismiss} sx={{ textTransform: 'none' }}>
              Dismiss
            </Button>
          </Box>
        }
      >
        <AlertTitle sx={{ fontWeight: 700, fontSize: '0.86rem' }}>
          {creditCta?.title ?? 'Message failed'}
        </AlertTitle>
        <Typography variant="body2" sx={{ fontSize: '0.8rem', lineHeight: 1.5 }}>
          {creditCta ? (
            creditCta.message
          ) : (
            <>
              The backend could not process the last message. Retry after a moment. If it keeps
              happening,{' '}
              <Link
                href={DISCUSSIONS_URL}
                target="_blank"
                rel="noopener noreferrer"
                color="inherit"
                underline="always"
              >
                open a discussion
              </Link>{' '}
              with the copied details.
            </>
          )}
        </Typography>
        {creditCta && (
          <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.75, mt: 1 }}>
            <Button
              component="a"
              href={creditCta.primaryHref}
              target="_blank"
              rel="noopener noreferrer"
              color="inherit"
              size="small"
              variant="outlined"
              onClick={trackProClick}
              sx={{ textTransform: 'none' }}
            >
              {creditCta.primaryLabel}
            </Button>
            {creditCta.secondaryHref && creditCta.secondaryLabel && (
              <Button
                component="a"
                href={creditCta.secondaryHref}
                target="_blank"
                rel="noopener noreferrer"
                color="inherit"
                size="small"
                sx={{ textTransform: 'none' }}
              >
                {creditCta.secondaryLabel}
              </Button>
            )}
          </Box>
        )}
        <Typography
          variant="caption"
          component="pre"
          sx={{
            display: 'block',
            mt: 1,
            mb: 0,
            p: 1,
            maxHeight: 96,
            overflow: 'auto',
            bgcolor: 'var(--code-bg)',
            borderRadius: '6px',
            whiteSpace: 'pre-wrap',
            wordBreak: 'break-word',
            fontSize: '0.72rem',
          }}
        >
          {error}
        </Typography>
      </Alert>
    </Box>
  );
}