@blocklet/aigne-hub 0.4.43 → 0.4.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,114 +1,317 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cx } from '@emotion/css';
3
- import styled from '@emotion/styled';
4
- import { CopyAll } from '@mui/icons-material';
5
- import { Box, Button, Tooltip } from '@mui/material';
6
- import { useMemo, useState } from 'react';
3
+ import { CheckCircleOutline, CopyAll } from '@mui/icons-material';
4
+ import { Box, Button, Tooltip, useTheme } from '@mui/material';
5
+ import { useEffect, useMemo, useState } from 'react';
7
6
  import ReactMarkdown from 'react-markdown';
8
- export default function Message({ avatar = undefined, message = undefined, children = undefined, loading = false, actions = undefined, ...props }) {
7
+ export default function Message({ avatar = undefined, message = undefined, children = undefined, loading = false, actions = undefined, timestamp = undefined, isUser = false, chatLayout = 'traditional', ...props }) {
8
+ const theme = useTheme();
9
9
  const text = useMemo(() => (typeof message === 'string' ? message : message === null || message === void 0 ? void 0 : message.map((i) => `${i.role}: ${i.content}`).join('\n\n')), [message]);
10
- return (_jsxs(Root, { ...props, display: "flex", children: [_jsx(Box, { className: "avatar", sx: {
11
- mr: 1,
12
- }, children: avatar }), _jsxs(Box, { className: cx('content'), children: [_jsx(Box, { component: ReactMarkdown, className: cx('message', loading && 'cursor'), children: text }), children, _jsxs(Box, { className: "actions", children: [actions, text && _jsx(CopyButton, { message: text }, "copy")] })] })] }));
10
+ // Create theme-based styles
11
+ const getMessageStyles = () => {
12
+ const baseStyles = {
13
+ '> .message-content-wrapper': {
14
+ '> .content': {
15
+ '> .message': {
16
+ lineHeight: 1.6,
17
+ fontSize: '15px',
18
+ '> *:first-of-type': {
19
+ marginTop: 0,
20
+ },
21
+ '> *:last-child': {
22
+ marginBottom: 0,
23
+ },
24
+ pre: {
25
+ lineHeight: 1.5,
26
+ backgroundColor: theme.palette.grey[50],
27
+ overflow: 'auto',
28
+ padding: 2,
29
+ borderRadius: 1,
30
+ border: `1px solid ${theme.palette.divider}`,
31
+ margin: '12px 0',
32
+ boxShadow: theme.shadows[1],
33
+ position: 'relative',
34
+ '&::before': {
35
+ content: 'attr(data-language)',
36
+ position: 'absolute',
37
+ top: 8,
38
+ right: 8,
39
+ fontSize: '11px',
40
+ color: theme.palette.text.disabled,
41
+ textTransform: 'uppercase',
42
+ fontWeight: 600,
43
+ letterSpacing: '0.5px',
44
+ },
45
+ },
46
+ code: {
47
+ backgroundColor: theme.palette.action.hover,
48
+ padding: '2px 6px',
49
+ borderRadius: 0.5,
50
+ fontSize: '0.9em',
51
+ fontFamily: '"Monaco", "Menlo", "Ubuntu Mono", "Consolas", "source-code-pro", monospace',
52
+ },
53
+ 'pre code': {
54
+ backgroundColor: 'transparent',
55
+ padding: 0,
56
+ display: 'block',
57
+ border: 'none !important',
58
+ },
59
+ 'ul, ol': {
60
+ paddingLeft: '24px',
61
+ },
62
+ li: {
63
+ margin: '4px 0',
64
+ },
65
+ blockquote: {
66
+ borderLeft: `3px solid ${theme.palette.divider}`,
67
+ paddingLeft: 2,
68
+ margin: '12px 0',
69
+ color: theme.palette.text.secondary,
70
+ },
71
+ table: {
72
+ borderCollapse: 'collapse',
73
+ width: '100%',
74
+ margin: '12px 0',
75
+ },
76
+ 'th, td': {
77
+ border: `1px solid ${theme.palette.divider}`,
78
+ padding: '8px 12px',
79
+ textAlign: 'left',
80
+ },
81
+ th: {
82
+ backgroundColor: theme.palette.action.hover,
83
+ fontWeight: 600,
84
+ },
85
+ '&.cursor': {
86
+ '> *:last-child': {
87
+ '&:after': {
88
+ content: '""',
89
+ display: 'inline-block',
90
+ verticalAlign: 'middle',
91
+ height: '1em',
92
+ marginTop: '-0.15em',
93
+ marginLeft: '0.15em',
94
+ borderRight: `0.15em solid ${theme.palette.primary.main}`,
95
+ animation: 'blink-caret 0.75s step-end infinite',
96
+ '@keyframes blink-caret': {
97
+ 'from, to': {
98
+ borderColor: 'transparent',
99
+ },
100
+ '50%': {
101
+ borderColor: theme.palette.primary.main,
102
+ },
103
+ },
104
+ },
105
+ },
106
+ },
107
+ },
108
+ },
109
+ },
110
+ };
111
+ // User message styles (right-aligned with blue background)
112
+ if (chatLayout === 'left-right' && isUser) {
113
+ return {
114
+ ...baseStyles,
115
+ '> .message-content-wrapper > .content': {
116
+ background: theme.palette.primary.light,
117
+ color: theme.palette.primary.contrastText,
118
+ border: 'none',
119
+ boxShadow: theme.shadows[2],
120
+ position: 'relative',
121
+ overflow: 'visible',
122
+ '.message': {
123
+ color: theme.palette.primary.contrastText,
124
+ code: {
125
+ backgroundColor: 'rgba(255, 255, 255, 0.25)',
126
+ color: theme.palette.primary.contrastText,
127
+ border: '1px solid rgba(255, 255, 255, 0.15)',
128
+ },
129
+ pre: {
130
+ backgroundColor: 'rgba(0, 0, 0, 0.25)',
131
+ borderColor: 'rgba(255, 255, 255, 0.15)',
132
+ boxShadow: 'inset 0 1px 3px rgba(0, 0, 0, 0.2)',
133
+ code: {
134
+ color: 'rgba(255, 255, 255, 0.95)',
135
+ border: 'none !important',
136
+ backgroundColor: 'transparent !important',
137
+ padding: '0 !important',
138
+ },
139
+ },
140
+ a: {
141
+ color: theme.palette.primary.light,
142
+ textDecoration: 'underline',
143
+ textDecorationColor: 'rgba(255, 255, 255, 0.4)',
144
+ '&:hover': {
145
+ color: '#bbdefb',
146
+ },
147
+ },
148
+ strong: {
149
+ fontWeight: 600,
150
+ color: 'rgba(255, 255, 255, 0.98)',
151
+ },
152
+ },
153
+ },
154
+ '&:hover > .message-content-wrapper > .content': {
155
+ background: theme.palette.primary.main,
156
+ },
157
+ };
158
+ }
159
+ // AI message styles (left-aligned with light background)
160
+ return {
161
+ ...baseStyles,
162
+ '> .message-content-wrapper > .content': {
163
+ background: theme.palette.background.paper,
164
+ border: `1px solid ${theme.palette.divider}`,
165
+ boxShadow: theme.shadows[1],
166
+ '.message': {
167
+ color: theme.palette.text.primary,
168
+ code: {
169
+ background: theme.palette.grey[100],
170
+ border: `1px solid ${theme.palette.divider}`,
171
+ },
172
+ pre: {
173
+ background: theme.palette.grey[50],
174
+ border: `1px solid ${theme.palette.divider}`,
175
+ code: {
176
+ background: 'transparent !important',
177
+ border: 'none !important',
178
+ padding: '0 !important',
179
+ },
180
+ },
181
+ a: {
182
+ color: theme.palette.primary.main,
183
+ textDecoration: 'none',
184
+ borderBottom: `1px solid ${theme.palette.primary.light}`,
185
+ transition: 'all 0.2s ease',
186
+ '&:hover': {
187
+ color: theme.palette.primary.dark,
188
+ borderBottomColor: theme.palette.primary.dark,
189
+ },
190
+ },
191
+ strong: {
192
+ fontWeight: 600,
193
+ color: theme.palette.text.primary,
194
+ },
195
+ },
196
+ },
197
+ '&:hover > .message-content-wrapper > .content': {
198
+ background: theme.palette.grey[50],
199
+ borderColor: theme.palette.action.focus,
200
+ },
201
+ };
202
+ };
203
+ // Force re-render every minute to update relative time
204
+ const [now, setNow] = useState(Date.now());
205
+ useEffect(() => {
206
+ if (!timestamp)
207
+ return undefined;
208
+ const interval = setInterval(() => {
209
+ setNow(Date.now());
210
+ }, 60000); // Update every minute
211
+ return () => clearInterval(interval);
212
+ }, [timestamp]);
213
+ const formattedTime = useMemo(() => {
214
+ if (!timestamp)
215
+ return '';
216
+ const date = new Date(timestamp);
217
+ const diffMs = now - date.getTime();
218
+ const diffMins = Math.floor(diffMs / 60000);
219
+ const diffHours = Math.floor(diffMs / 3600000);
220
+ const diffDays = Math.floor(diffMs / 86400000);
221
+ if (diffMins < 1)
222
+ return 'Just now';
223
+ if (diffMins < 60)
224
+ return `${diffMins}m ago`;
225
+ if (diffHours < 24)
226
+ return `${diffHours}h ago`;
227
+ if (diffDays < 7)
228
+ return `${diffDays}d ago`;
229
+ return date.toLocaleDateString();
230
+ }, [timestamp, now]);
231
+ const isLeftRight = chatLayout === 'left-right';
232
+ return (_jsxs(Box, { ...props, display: "flex", className: cx(isLeftRight && isUser && 'user-message', isLeftRight && !isUser && 'ai-message'), sx: {
233
+ mb: 2.5,
234
+ '&:hover .message-meta': {
235
+ opacity: 1,
236
+ },
237
+ ...(isLeftRight && isUser
238
+ ? {
239
+ justifyContent: 'flex-end',
240
+ '.avatar': { order: 2, mr: 0, ml: 1 },
241
+ '.content': { alignItems: 'flex-end' },
242
+ }
243
+ : {}),
244
+ ...getMessageStyles(),
245
+ ...props.sx,
246
+ }, children: [_jsx(Box, { className: "avatar", sx: {
247
+ pt: 0.625,
248
+ flexShrink: 0,
249
+ mr: isLeftRight && !isUser ? 1 : isLeftRight && isUser ? 0 : 1,
250
+ ml: isLeftRight && isUser ? 1 : 0,
251
+ '& .MuiAvatar-root': {
252
+ width: 38,
253
+ height: 38,
254
+ boxShadow: '0 2px 8px rgba(0, 0, 0, 0.12)',
255
+ },
256
+ }, children: avatar }), _jsxs(Box, { className: "message-content-wrapper", sx: {
257
+ display: 'flex',
258
+ flexDirection: 'column',
259
+ maxWidth: '80%',
260
+ minWidth: 'auto',
261
+ position: 'relative',
262
+ }, children: [_jsxs(Box, { className: cx('content'), sx: {
263
+ minHeight: 40,
264
+ overflow: 'hidden',
265
+ wordBreak: 'break-word',
266
+ padding: 1.75,
267
+ borderRadius: 2,
268
+ position: 'relative',
269
+ backgroundColor: 'transparent',
270
+ border: 'none',
271
+ transition: 'all 0.25s cubic-bezier(0.4, 0, 0.2, 1)',
272
+ display: 'flex',
273
+ flexDirection: 'column',
274
+ }, children: [text && (_jsx(Box, { component: ReactMarkdown, className: cx('message', loading && 'cursor'), children: text })), children] }), _jsxs(Box, { className: "message-meta", sx: {
275
+ display: 'flex',
276
+ alignItems: 'center',
277
+ gap: 1,
278
+ mt: 0.5,
279
+ opacity: 0,
280
+ transition: 'opacity 0.2s ease',
281
+ justifyContent: isLeftRight && isUser ? 'flex-end' : 'flex-start',
282
+ }, children: [timestamp && (_jsx(Box, { className: "timestamp", sx: {
283
+ fontSize: '11px',
284
+ color: 'text.secondary',
285
+ }, children: formattedTime })), _jsxs(Box, { className: "actions", sx: {
286
+ display: 'flex',
287
+ gap: 0.5,
288
+ '& button': {
289
+ minWidth: 0,
290
+ p: 0.5,
291
+ height: 24,
292
+ width: 24,
293
+ color: 'text.secondary',
294
+ borderRadius: 0.5,
295
+ transition: 'all 0.15s ease',
296
+ },
297
+ }, children: [actions, text && _jsx(CopyButton, { message: text }, "copy")] })] })] })] }));
13
298
  }
14
299
  function CopyButton({ message }) {
15
300
  const [copied, setCopied] = useState(false);
16
- return (_jsx(Tooltip, { title: copied === 'copied' ? 'Copied!' : 'Copy', placement: "top", open: Boolean(copied), children: _jsx(Button, { size: "small", className: cx('copy', copied && 'active'), onMouseEnter: () => setCopied(true), onMouseLeave: () => setCopied(false), onClick: () => {
17
- navigator.clipboard.writeText(message);
18
- setCopied('copied');
19
- setTimeout(() => setCopied(false), 1500);
20
- }, children: _jsx(CopyAll, { fontSize: "small" }) }) }));
301
+ const [showTooltip, setShowTooltip] = useState(false);
302
+ return (_jsx(Tooltip, { title: copied === 'copied' ? 'Copied!' : 'Copy', placement: "top", open: showTooltip || Boolean(copied), children: _jsx(Button, { size: "small", className: cx('copy', copied && 'active'), onMouseEnter: () => setShowTooltip(true), onMouseLeave: () => setShowTooltip(false), onClick: () => {
303
+ navigator.clipboard
304
+ .writeText(message)
305
+ .then(() => {
306
+ setCopied('copied');
307
+ })
308
+ .catch((err) => {
309
+ console.error('Failed to copy message', err);
310
+ });
311
+ setShowTooltip(false);
312
+ setTimeout(() => setCopied(false), 2000);
313
+ }, sx: {
314
+ color: copied === 'copied' ? 'success.main' : 'inherit',
315
+ transition: 'color 0.2s ease',
316
+ }, children: copied === 'copied' ? _jsx(CheckCircleOutline, { fontSize: "small" }) : _jsx(CopyAll, { fontSize: "small" }) }) }));
21
317
  }
22
- const Root = styled(Box) `
23
- > .avatar {
24
- padding-top: 5px;
25
-
26
- > .MuiAvatar-root {
27
- width: 30px;
28
- height: 30px;
29
- }
30
- }
31
-
32
- > .content {
33
- min-height: 40px;
34
- flex: 1;
35
- overflow: hidden;
36
- word-break: break-word;
37
- padding: 8px;
38
- border-radius: 4px;
39
- position: relative;
40
-
41
- > .message {
42
- > *:first-of-type {
43
- margin-top: 0;
44
- }
45
- > *:last-child {
46
- margin-bottom: 0;
47
- }
48
-
49
- pre {
50
- line-height: 1.2;
51
- background-color: #f6f8fa;
52
- overflow: auto;
53
- padding: 16px;
54
- border-radius: 3px;
55
- }
56
-
57
- &.cursor {
58
- > *:last-child {
59
- &:after {
60
- content: '';
61
- display: inline-block;
62
- vertical-align: middle;
63
- height: 1em;
64
- margin-top: -0.15em;
65
- margin-left: 0.15em;
66
- border-right: 0.15em solid orange;
67
- animation: blink-caret 0.75s step-end infinite;
68
-
69
- @keyframes blink-caret {
70
- from,
71
- to {
72
- border-color: transparent;
73
- }
74
- 50% {
75
- border-color: orange;
76
- }
77
- }
78
- }
79
- }
80
- }
81
- }
82
-
83
- > .actions {
84
- position: absolute;
85
- right: 2px;
86
- top: 2px;
87
- border-radius: 4px;
88
- opacity: 0;
89
-
90
- &.active {
91
- display: flex;
92
- }
93
-
94
- button {
95
- min-width: 0;
96
- padding: 0;
97
- height: 24px;
98
- width: 22px;
99
- color: rgba(0, 0, 0, 0.4);
100
- }
101
- }
102
- }
103
-
104
- &:hover {
105
- > .content {
106
- background-color: rgba(0, 0, 0, 0.05);
107
-
108
- > .actions {
109
- opacity: 1;
110
- background-color: rgba(240, 240, 240, 0.9);
111
- }
112
- }
113
- }
114
- `;
@@ -1,10 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Send } from '@mui/icons-material';
3
- import { Box, IconButton, Input, InputAdornment } from '@mui/material';
3
+ import { Box, IconButton, Input } from '@mui/material';
4
4
  import { useHistoryTravel } from 'ahooks';
5
5
  import { useState } from 'react';
6
- export default function Prompt({ startAdornment = undefined, endAdornment = undefined, onSubmit, slotProps = {}, sx = {}, ...props }) {
6
+ export default function Prompt({ startAdornment = undefined, endAdornment = undefined, topAdornment = undefined, onSubmit, slotProps = {}, sx = {}, placeholder = 'Type your message... (Shift+Enter for new line)', ...props }) {
7
7
  const [prompt, setPrompt] = useState('');
8
+ const [isFocused, setIsFocused] = useState(false);
8
9
  const { value: historyPrompt, setValue: setHistoryPrompt, forwardLength, back, go, forward } = useHistoryTravel('');
9
10
  const submit = () => {
10
11
  if (!prompt.trim()) {
@@ -18,23 +19,87 @@ export default function Prompt({ startAdornment = undefined, endAdornment = unde
18
19
  setPrompt('');
19
20
  }, 50);
20
21
  };
21
- return (_jsxs(Box, { ...props, sx: { display: 'flex', gap: 1, alignItems: 'center', ...sx }, component: "form", onSubmit: (e) => e.preventDefault(), children: [startAdornment, _jsx(Input, { fullWidth: true, disableUnderline: true, value: prompt, multiline: true, maxRows: 10, sx: { py: 0.8, px: 1, boxShadow: 2, borderRadius: 1 }, onChange: (e) => setPrompt(e.target.value), onKeyDown: (e) => {
22
- if (e.keyCode === 229) {
23
- return;
24
- }
25
- if (!e.shiftKey && e.key === 'Enter') {
26
- e.preventDefault();
27
- submit();
28
- }
29
- else if (e.key === 'ArrowUp') {
30
- e.preventDefault();
31
- back();
32
- setPrompt(historyPrompt || '');
33
- }
34
- else if (e.key === 'ArrowDown') {
35
- e.preventDefault();
36
- forward();
37
- setPrompt(historyPrompt || '');
38
- }
39
- }, endAdornment: _jsx(InputAdornment, { position: "end", children: _jsx(IconButton, { onClick: submit, size: "small", type: "submit", children: _jsx(Send, { fontSize: "small" }) }) }), ...slotProps }), endAdornment] }));
22
+ const charCount = prompt.length;
23
+ const showCharCount = isFocused && charCount > 0;
24
+ return (_jsxs(Box, { ...props, sx: { display: 'flex', flexDirection: 'column', gap: 1.5, ...sx }, component: "form", onSubmit: (e) => e.preventDefault(), children: [topAdornment && (_jsx(Box, { sx: {
25
+ display: 'flex',
26
+ alignItems: 'center',
27
+ justifyContent: 'space-between',
28
+ gap: 2,
29
+ px: 1,
30
+ }, children: topAdornment })), _jsxs(Box, { sx: {
31
+ position: 'relative',
32
+ flex: 1,
33
+ display: 'flex',
34
+ alignItems: 'stretch',
35
+ gap: 1.5,
36
+ p: 1.5,
37
+ boxShadow: '0 2px 12px rgba(0, 0, 0, 0.08)',
38
+ borderRadius: 2,
39
+ border: '1px solid',
40
+ borderColor: 'divider',
41
+ transition: 'all 0.2s ease',
42
+ bgcolor: 'background.paper',
43
+ '&:hover': {
44
+ boxShadow: '0 4px 16px rgba(0, 0, 0, 0.12)',
45
+ borderColor: 'primary.main',
46
+ },
47
+ '&:focus-within': {
48
+ boxShadow: '0 4px 20px rgba(25, 118, 210, 0.2)',
49
+ borderColor: 'primary.main',
50
+ },
51
+ }, children: [startAdornment, _jsx(Input, { fullWidth: true, disableUnderline: true, value: prompt, multiline: true, maxRows: 10, placeholder: placeholder, sx: {
52
+ py: 0,
53
+ px: 0,
54
+ fontSize: '15px',
55
+ border: 'none',
56
+ boxShadow: 'none',
57
+ '&:hover': {
58
+ boxShadow: 'none',
59
+ },
60
+ '&.Mui-focused': {
61
+ boxShadow: 'none',
62
+ },
63
+ }, onChange: (e) => setPrompt(e.target.value), onFocus: () => setIsFocused(true), onBlur: () => setIsFocused(false), onKeyDown: (e) => {
64
+ if (e.keyCode === 229) {
65
+ return;
66
+ }
67
+ if (!e.shiftKey && e.key === 'Enter') {
68
+ e.preventDefault();
69
+ submit();
70
+ }
71
+ else if (e.key === 'ArrowUp') {
72
+ e.preventDefault();
73
+ back();
74
+ setPrompt(historyPrompt || '');
75
+ }
76
+ else if (e.key === 'ArrowDown') {
77
+ e.preventDefault();
78
+ forward();
79
+ setPrompt(historyPrompt || '');
80
+ }
81
+ }, ...slotProps }), _jsx(IconButton, { onClick: submit, size: "medium", type: "submit", disabled: !prompt.trim(), sx: {
82
+ bgcolor: prompt.trim() ? 'primary.main' : 'action.disabledBackground',
83
+ color: prompt.trim() ? 'primary.contrastText' : 'action.disabled',
84
+ transition: 'all 0.2s ease',
85
+ width: 44,
86
+ height: 44,
87
+ alignSelf: 'flex-end',
88
+ flexShrink: 0,
89
+ '&:hover': {
90
+ bgcolor: prompt.trim() ? 'primary.dark' : 'action.disabledBackground',
91
+ transform: prompt.trim() ? 'scale(1.05)' : 'none',
92
+ },
93
+ '&.Mui-disabled': {
94
+ bgcolor: 'action.disabledBackground',
95
+ color: 'action.disabled',
96
+ },
97
+ }, children: _jsx(Send, { fontSize: "small" }) }), showCharCount && (_jsxs(Box, { sx: {
98
+ position: 'absolute',
99
+ bottom: -24,
100
+ right: 8,
101
+ fontSize: '11px',
102
+ color: 'text.secondary',
103
+ opacity: 0.7,
104
+ }, children: [charCount, " characters"] }))] }), endAdornment] }));
40
105
  }