@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.
- package/lib/cjs/api/types/index.js +1 -0
- package/lib/cjs/api/types/model.js +2 -0
- package/lib/cjs/components/conversation/conversation.js +46 -17
- package/lib/cjs/components/conversation/message.js +306 -103
- package/lib/cjs/components/conversation/prompt.js +85 -20
- package/lib/cjs/components/conversation/use-conversation.js +161 -10
- package/lib/cjs/components/image-preview.js +7 -5
- package/lib/esm/api/types/index.js +1 -0
- package/lib/esm/api/types/model.js +1 -0
- package/lib/esm/components/conversation/conversation.js +48 -19
- package/lib/esm/components/conversation/message.js +309 -106
- package/lib/esm/components/conversation/prompt.js +86 -21
- package/lib/esm/components/conversation/use-conversation.js +162 -11
- package/lib/esm/components/image-preview.js +7 -5
- package/lib/types/api/types/index.d.ts +1 -0
- package/lib/types/api/types/model.d.ts +11 -0
- package/lib/types/components/conversation/conversation.d.ts +3 -1
- package/lib/types/components/conversation/index.d.ts +1 -0
- package/lib/types/components/conversation/message.d.ts +6 -2
- package/lib/types/components/conversation/prompt.d.ts +3 -1
- package/lib/types/components/conversation/use-conversation.d.ts +5 -2
- package/package.json +1 -1
|
@@ -5,15 +5,142 @@ const immer_1 = require("immer");
|
|
|
5
5
|
const nanoid_1 = require("nanoid");
|
|
6
6
|
const react_1 = require("react");
|
|
7
7
|
const nextId = () => (0, nanoid_1.nanoid)(16);
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
const STORAGE_KEY = 'aigne-hub-conversation-history';
|
|
9
|
+
const SESSION_STORAGE_KEY = 'aigne-hub-conversation-session';
|
|
10
|
+
const MAX_CACHED_MESSAGES = 5; // Limit cached messages to reduce storage usage
|
|
11
|
+
// Load messages from localStorage/sessionStorage
|
|
12
|
+
const loadMessages = async () => {
|
|
13
|
+
try {
|
|
14
|
+
// Try localStorage first, then sessionStorage as fallback
|
|
15
|
+
let cached = localStorage.getItem(STORAGE_KEY);
|
|
16
|
+
if (!cached) {
|
|
17
|
+
cached = sessionStorage.getItem(SESSION_STORAGE_KEY);
|
|
18
|
+
}
|
|
19
|
+
if (cached) {
|
|
20
|
+
const parsed = JSON.parse(cached);
|
|
21
|
+
// Validate and filter out invalid messages
|
|
22
|
+
if (Array.isArray(parsed) && parsed.length > 0) {
|
|
23
|
+
const validMessages = parsed.filter((msg) => msg.id && (msg.prompt || msg.response));
|
|
24
|
+
return validMessages;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
console.warn('Failed to load conversation history:', error);
|
|
30
|
+
}
|
|
31
|
+
// Return empty array if no history, let the component handle initial message
|
|
32
|
+
return [];
|
|
33
|
+
};
|
|
34
|
+
// Save messages to localStorage
|
|
35
|
+
const saveMessages = async (messages) => {
|
|
36
|
+
try {
|
|
37
|
+
// Only save completed messages (no loading state)
|
|
38
|
+
const toSave = messages
|
|
39
|
+
.filter((msg) => !msg.loading)
|
|
40
|
+
.slice(-MAX_CACHED_MESSAGES) // Keep only last N messages
|
|
41
|
+
.map((msg) => {
|
|
42
|
+
var _a;
|
|
43
|
+
// Replace image data URLs with placeholders to save storage space
|
|
44
|
+
const response = msg.response && typeof msg.response === 'object' && 'images' in msg.response
|
|
45
|
+
? {
|
|
46
|
+
...msg.response,
|
|
47
|
+
images: ((_a = msg.response.images) === null || _a === void 0 ? void 0 : _a.map((img) => ({
|
|
48
|
+
...img,
|
|
49
|
+
url: img.url && img.url.startsWith('data:') ? '[IMAGE_PLACEHOLDER]' : img.url,
|
|
50
|
+
}))) || [],
|
|
51
|
+
}
|
|
52
|
+
: msg.response;
|
|
53
|
+
return {
|
|
54
|
+
id: msg.id,
|
|
55
|
+
prompt: msg.prompt,
|
|
56
|
+
response,
|
|
57
|
+
timestamp: msg.timestamp,
|
|
58
|
+
// Don't save meta and error state to reduce size
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
// Try to save to localStorage, fallback to sessionStorage if quota exceeded
|
|
62
|
+
try {
|
|
63
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(toSave));
|
|
64
|
+
// Clear sessionStorage if localStorage succeeded
|
|
65
|
+
sessionStorage.removeItem(SESSION_STORAGE_KEY);
|
|
66
|
+
}
|
|
67
|
+
catch (storageError) {
|
|
68
|
+
if (storageError instanceof Error && storageError.name === 'QuotaExceededError') {
|
|
69
|
+
// Fallback to sessionStorage
|
|
70
|
+
try {
|
|
71
|
+
sessionStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(toSave));
|
|
72
|
+
}
|
|
73
|
+
catch (sessionError) {
|
|
74
|
+
console.warn('Failed to save to sessionStorage:', sessionError);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
console.warn('Failed to save to localStorage:', storageError);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
console.warn('Failed to save conversation history:', error);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
function useConversation({ scrollToBottom, textCompletions, imageGenerations, enableCache = true, }) {
|
|
87
|
+
const [messages, setMessages] = (0, react_1.useState)([]);
|
|
88
|
+
const [isInitialLoad, setIsInitialLoad] = (0, react_1.useState)(true);
|
|
89
|
+
const [isLoadingHistory, setIsLoadingHistory] = (0, react_1.useState)(enableCache);
|
|
90
|
+
// Load initial messages from cache
|
|
91
|
+
(0, react_1.useEffect)(() => {
|
|
92
|
+
if (enableCache && isLoadingHistory) {
|
|
93
|
+
loadMessages().then((loadedMessages) => {
|
|
94
|
+
// If no messages loaded, show welcome message
|
|
95
|
+
if (loadedMessages.length === 0) {
|
|
96
|
+
setMessages([
|
|
97
|
+
{ id: nextId(), response: 'Hi, I am AIGNE Hub! How can I assist you today?', timestamp: Date.now() },
|
|
98
|
+
]);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
setMessages(loadedMessages);
|
|
102
|
+
}
|
|
103
|
+
setIsLoadingHistory(false);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
else if (!enableCache) {
|
|
107
|
+
setMessages([
|
|
108
|
+
{ id: nextId(), response: 'Hi, I am AIGNE Hub! How can I assist you today?', timestamp: Date.now() },
|
|
109
|
+
]);
|
|
110
|
+
setIsLoadingHistory(false);
|
|
111
|
+
}
|
|
112
|
+
}, [enableCache, isLoadingHistory]);
|
|
113
|
+
// Scroll to bottom on initial load
|
|
114
|
+
(0, react_1.useEffect)(() => {
|
|
115
|
+
if (isInitialLoad && messages.length > 0 && !isLoadingHistory) {
|
|
116
|
+
// Wait for DOM to render
|
|
117
|
+
setTimeout(() => {
|
|
118
|
+
scrollToBottom === null || scrollToBottom === void 0 ? void 0 : scrollToBottom({ force: true });
|
|
119
|
+
setIsInitialLoad(false);
|
|
120
|
+
}, 100);
|
|
121
|
+
}
|
|
122
|
+
}, [isInitialLoad, messages.length, scrollToBottom, isLoadingHistory]);
|
|
123
|
+
// Save messages to localStorage whenever they change (with debounce)
|
|
124
|
+
(0, react_1.useEffect)(() => {
|
|
125
|
+
if (enableCache && messages.length > 0) {
|
|
126
|
+
const timeoutId = setTimeout(() => {
|
|
127
|
+
saveMessages(messages).catch((error) => {
|
|
128
|
+
console.error('❌ Failed to save messages:', error);
|
|
129
|
+
});
|
|
130
|
+
}, 1000); // Delay save by 1 second to avoid saving during streaming
|
|
131
|
+
return () => clearTimeout(timeoutId);
|
|
132
|
+
}
|
|
133
|
+
return () => { };
|
|
134
|
+
}, [messages, enableCache]);
|
|
12
135
|
const add = (0, react_1.useCallback)(async (prompt, meta) => {
|
|
13
136
|
const id = nextId();
|
|
14
|
-
|
|
15
|
-
|
|
137
|
+
const timestamp = Date.now();
|
|
138
|
+
setMessages((v) => v.concat({ id, prompt, loading: true, meta, timestamp }));
|
|
139
|
+
setTimeout(() => {
|
|
140
|
+
scrollToBottom === null || scrollToBottom === void 0 ? void 0 : scrollToBottom({ force: true });
|
|
141
|
+
}, 100);
|
|
16
142
|
try {
|
|
143
|
+
// Handle image generation command
|
|
17
144
|
if (imageGenerations && typeof prompt === 'string') {
|
|
18
145
|
const m = prompt.match(/^\/image(\s+(?<size>256|512|1024))?(\s+(?<n>[1-9]|10))?\s+(?<prompt>[\s\S]+)/);
|
|
19
146
|
if (m === null || m === void 0 ? void 0 : m.groups) {
|
|
@@ -49,7 +176,7 @@ function useConversation({ scrollToBottom, textCompletions, imageGenerations, })
|
|
|
49
176
|
response = value.text;
|
|
50
177
|
}
|
|
51
178
|
else if (isImages(value)) {
|
|
52
|
-
response = value.images;
|
|
179
|
+
response = { images: value.images };
|
|
53
180
|
}
|
|
54
181
|
else {
|
|
55
182
|
delta = decoder.decode(value);
|
|
@@ -76,8 +203,13 @@ function useConversation({ scrollToBottom, textCompletions, imageGenerations, })
|
|
|
76
203
|
catch (error) {
|
|
77
204
|
setMessages((v) => (0, immer_1.produce)(v, (draft) => {
|
|
78
205
|
const item = draft.find((i) => i.id === id);
|
|
79
|
-
if (item)
|
|
80
|
-
|
|
206
|
+
if (item) {
|
|
207
|
+
// Format error for CreditErrorAlert component
|
|
208
|
+
item.error = {
|
|
209
|
+
message: error instanceof Error ? error.message : String(error),
|
|
210
|
+
type: 'unknown',
|
|
211
|
+
};
|
|
212
|
+
}
|
|
81
213
|
}));
|
|
82
214
|
return null;
|
|
83
215
|
}
|
|
@@ -96,5 +228,24 @@ function useConversation({ scrollToBottom, textCompletions, imageGenerations, })
|
|
|
96
228
|
i.loading = false;
|
|
97
229
|
}));
|
|
98
230
|
}, []);
|
|
99
|
-
|
|
231
|
+
const clearHistory = (0, react_1.useCallback)(async () => {
|
|
232
|
+
const initialMessage = {
|
|
233
|
+
id: nextId(),
|
|
234
|
+
response: 'Hi, I am AIGNE Hub! How can I assist you today?',
|
|
235
|
+
timestamp: Date.now(),
|
|
236
|
+
};
|
|
237
|
+
setMessages([initialMessage]);
|
|
238
|
+
if (enableCache) {
|
|
239
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
240
|
+
sessionStorage.removeItem(SESSION_STORAGE_KEY);
|
|
241
|
+
}
|
|
242
|
+
}, [enableCache]);
|
|
243
|
+
return {
|
|
244
|
+
messages,
|
|
245
|
+
add,
|
|
246
|
+
cancel,
|
|
247
|
+
setMessages,
|
|
248
|
+
clearHistory,
|
|
249
|
+
isLoadingHistory,
|
|
250
|
+
};
|
|
100
251
|
}
|
|
@@ -92,11 +92,13 @@ function ImagePreview({ dataSource = [], itemWidth = undefined, itemHeight = und
|
|
|
92
92
|
opacity: 1,
|
|
93
93
|
},
|
|
94
94
|
},
|
|
95
|
-
}, children: (0, jsx_runtime_1.jsxs)(material_1.Box, { sx: { position: 'relative' }, children: [(0, jsx_runtime_1.jsx)(react_photo_view_1.PhotoView, { ...item, children: (0, jsx_runtime_1.jsx)(loading_image_1.default, { ...item, src:
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
95
|
+
}, children: (0, jsx_runtime_1.jsxs)(material_1.Box, { sx: { position: 'relative' }, children: [(0, jsx_runtime_1.jsx)(react_photo_view_1.PhotoView, { ...item, children: (0, jsx_runtime_1.jsx)(loading_image_1.default, { ...item, src: item.src.startsWith('data:')
|
|
96
|
+
? item.src
|
|
97
|
+
: (0, ufo_1.withQuery)(item.src, {
|
|
98
|
+
imageFilter: 'resize',
|
|
99
|
+
f: 'webp',
|
|
100
|
+
w: typeof itemWidth === 'number' ? Math.min(itemWidth * 2, 1200) : undefined,
|
|
101
|
+
}), style: {
|
|
100
102
|
transition,
|
|
101
103
|
borderRadius,
|
|
102
104
|
objectFit: 'cover',
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Avatar, Box, CircularProgress } from '@mui/material';
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Avatar, Box, CircularProgress, Fade } from '@mui/material';
|
|
3
3
|
import isNil from 'lodash/isNil';
|
|
4
4
|
import { useCallback, useEffect, useImperativeHandle, useRef } from 'react';
|
|
5
5
|
import CreditErrorAlert from '../credit/alert';
|
|
6
6
|
import ImagePreview from '../image-preview';
|
|
7
7
|
import Message from './message';
|
|
8
8
|
import Prompt from './prompt';
|
|
9
|
-
export default function Conversation({ ref, messages, onSubmit, customActions = () => [], renderAvatar = undefined, maxWidth = 1000, scrollContainer = undefined, promptProps = {}, ...props }) {
|
|
9
|
+
export default function Conversation({ ref, messages, onSubmit, customActions = () => [], renderAvatar = undefined, maxWidth = 1000, scrollContainer = undefined, promptProps = {}, chatLayout = 'left-right', ...props }) {
|
|
10
10
|
const scroller = useRef(scrollContainer !== null && scrollContainer !== void 0 ? scrollContainer : null);
|
|
11
11
|
const { element, scrollToBottom } = useAutoScrollToBottom({ scroller });
|
|
12
12
|
useImperativeHandle(ref, () => ({
|
|
@@ -18,28 +18,57 @@ export default function Conversation({ ref, messages, onSubmit, customActions =
|
|
|
18
18
|
flexDirection: 'column',
|
|
19
19
|
overflow: 'auto',
|
|
20
20
|
...props.sx,
|
|
21
|
-
}, children: _jsxs(Box, { sx: { mt:
|
|
21
|
+
}, children: _jsxs(Box, { sx: { mt: 3, mx: 2, flexGrow: 1, display: 'flex', flexDirection: 'column' }, className: "conversation-container", children: [_jsxs(Box, { sx: { flexGrow: 1, width: '100%', mx: 'auto', maxWidth }, children: [messages.map((msg) => {
|
|
22
22
|
var _a, _b;
|
|
23
23
|
const actions = customActions === null || customActions === void 0 ? void 0 : customActions(msg);
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
24
|
+
const isLeftRight = chatLayout === 'left-right';
|
|
25
|
+
return (_jsx(Fade, { in: true, timeout: 300, children: _jsxs(Box, { id: `conversation-${msg.id}`, children: [!isNil(msg.prompt) && (_jsx(Message, { avatar: (_a = renderAvatar === null || renderAvatar === void 0 ? void 0 : renderAvatar(msg, false)) !== null && _a !== void 0 ? _a : _jsx(Avatar, { sx: { bgcolor: 'secondary.main' }, children: "\uD83E\uDDD1" }), message: msg.prompt, actions: actions === null || actions === void 0 ? void 0 : actions[0], timestamp: msg.timestamp, isUser: isLeftRight, chatLayout: chatLayout })), (!isNil(msg.response) || !isNil(msg.loading) || !isNil(msg.error)) && (_jsxs(Message, { id: `response-${msg.id}`, loading: msg.loading && !!msg.response, message: typeof msg.response === 'string' ? msg.response : undefined, avatar: (_b = renderAvatar === null || renderAvatar === void 0 ? void 0 : renderAvatar(msg, true)) !== null && _b !== void 0 ? _b : _jsx(Avatar, { sx: { bgcolor: 'primary.main' }, children: "\uD83E\uDD16\uFE0F" }), actions: actions === null || actions === void 0 ? void 0 : actions[1], timestamp: msg.timestamp, isUser: false, chatLayout: chatLayout, children: [msg.response &&
|
|
26
|
+
typeof msg.response === 'object' &&
|
|
27
|
+
'images' in msg.response &&
|
|
28
|
+
Array.isArray(msg.response.images) &&
|
|
29
|
+
msg.response.images.length > 0 && (_jsxs(_Fragment, { children: [msg.response.images.some((img) => img.url && img.url.startsWith('data:')) && (_jsx(ImagePreview, { itemWidth: 200, borderRadius: 12, dataSource: msg.response.images
|
|
30
|
+
.filter((img) => img.url && img.url.startsWith('data:'))
|
|
31
|
+
.map(({ url }) => ({
|
|
32
|
+
src: url,
|
|
33
|
+
onLoad: () => scrollToBottom(),
|
|
34
|
+
})) })), msg.response.images.some((img) => !img.url || img.url === '[IMAGE_PLACEHOLDER]') && (_jsxs(Box, { sx: {
|
|
35
|
+
margin: '8px 0',
|
|
36
|
+
minHeight: '200px',
|
|
37
|
+
background: '#f5f5f5',
|
|
38
|
+
borderRadius: '8px',
|
|
39
|
+
padding: '16px',
|
|
40
|
+
display: 'flex',
|
|
41
|
+
alignItems: 'center',
|
|
42
|
+
justifyContent: 'center',
|
|
43
|
+
flexDirection: 'column',
|
|
44
|
+
gap: '12px',
|
|
45
|
+
border: '2px dashed #ddd',
|
|
46
|
+
}, children: [_jsx(Box, { sx: { fontSize: '48px', opacity: 0.4 }, children: "\uD83D\uDDBC\uFE0F" }), _jsx(Box, { sx: {
|
|
47
|
+
fontSize: '14px',
|
|
48
|
+
color: '#666',
|
|
49
|
+
textAlign: 'center',
|
|
50
|
+
fontWeight: 500,
|
|
51
|
+
minWidth: 200,
|
|
52
|
+
}, children: msg.response.images.filter((img) => !img.url || img.url === '[IMAGE_PLACEHOLDER]')
|
|
53
|
+
.length === 1
|
|
54
|
+
? 'Image (Not Cached)'
|
|
55
|
+
: `${msg.response.images.filter((img) => !img.url || img.url === '[IMAGE_PLACEHOLDER]').length} Images (Not Cached)` })] }))] })), msg.error ? (
|
|
56
|
+
// @ts-ignore
|
|
57
|
+
_jsx(CreditErrorAlert, { error: msg.error })) : (msg.loading &&
|
|
58
|
+
!msg.response && (_jsxs(Box, { sx: {
|
|
59
|
+
minHeight: 32,
|
|
60
|
+
display: 'flex',
|
|
61
|
+
alignItems: 'center',
|
|
62
|
+
gap: 1.5,
|
|
63
|
+
color: 'text.secondary',
|
|
64
|
+
fontSize: '14px',
|
|
65
|
+
}, children: [_jsx(CircularProgress, { size: 18, thickness: 4 }), _jsx("span", { children: "AI is thinking..." })] })))] }))] }) }, msg.id));
|
|
37
66
|
}), element] }), _jsxs(Box, { sx: { mx: 'auto', width: '100%', maxWidth, position: 'sticky', bottom: 0 }, children: [_jsx(Box, { sx: {
|
|
38
|
-
height:
|
|
67
|
+
height: 24,
|
|
39
68
|
pointerEvents: 'none',
|
|
40
69
|
background: (theme) => `linear-gradient(transparent, ${theme.palette.background.paper})`,
|
|
41
70
|
} }), _jsx(Box, { sx: {
|
|
42
|
-
pb:
|
|
71
|
+
pb: 4,
|
|
43
72
|
bgcolor: 'background.paper',
|
|
44
73
|
}, children: _jsx(Prompt, { onSubmit: onSubmit, ...promptProps }) })] })] }) }));
|
|
45
74
|
}
|