@truedat/ai 8.5.4 → 8.5.6
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/package.json +3 -3
- package/src/api.js +2 -0
- package/src/components/AiRoutes.js +3 -0
- package/src/components/assistant/Assistant.js +186 -78
- package/src/components/assistant/AssistantChat.js +91 -30
- package/src/components/assistant/AssistantConversations.js +78 -0
- package/src/components/assistant/AssistantPage.js +139 -0
- package/src/components/assistant/AssistantPageChat.js +447 -0
- package/src/components/assistant/__tests__/Assistant.spec.js +15 -0
- package/src/components/assistant/__tests__/AssistantChat.spec.js +18 -0
- package/src/components/assistant/__tests__/AssistantConversations.spec.js +111 -0
- package/src/components/assistant/__tests__/__snapshots__/Assistant.spec.js.snap +5 -5
- package/src/components/assistant/__tests__/__snapshots__/AssistantChat.spec.js.snap +18 -8
- package/src/components/assistant/__tests__/__snapshots__/AssistantConversations.spec.js.snap +73 -0
- package/src/components/assistant/hooks/useAgentConversation.js +9 -3
- package/src/components/assistant/hooks/useAssistantSocket.js +36 -3
- package/src/components/index.js +2 -1
- package/src/hooks/__tests__/useAgentConversations.spec.js +83 -0
- package/src/hooks/useAgentConversations.js +15 -0
- package/src/styles/assistant.less +281 -121
- package/src/components/assistant/AssistantBubble.js +0 -56
- package/src/components/assistant/__tests__/AssistantBubble.spec.js +0 -14
- package/src/components/assistant/__tests__/__snapshots__/AssistantBubble.spec.js.snap +0 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/ai",
|
|
3
|
-
"version": "8.5.
|
|
3
|
+
"version": "8.5.6",
|
|
4
4
|
"description": "Truedat Web Artificial Intelligence package",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"module": "src/index.js",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"@testing-library/jest-dom": "^6.6.3",
|
|
51
51
|
"@testing-library/react": "^16.3.0",
|
|
52
52
|
"@testing-library/user-event": "^14.6.1",
|
|
53
|
-
"@truedat/test": "8.5.
|
|
53
|
+
"@truedat/test": "8.5.6",
|
|
54
54
|
"identity-obj-proxy": "^3.0.0",
|
|
55
55
|
"jest": "^29.7.0",
|
|
56
56
|
"redux-saga-test-plan": "^4.0.6"
|
|
@@ -80,5 +80,5 @@
|
|
|
80
80
|
"semantic-ui-react": "^3.0.0-beta.2",
|
|
81
81
|
"swr": "^2.3.3"
|
|
82
82
|
},
|
|
83
|
-
"gitHead": "
|
|
83
|
+
"gitHead": "41e5e6138f5622558bae4151e720c040c4581162"
|
|
84
84
|
}
|
package/src/api.js
CHANGED
|
@@ -16,12 +16,14 @@ const API_SUGGESTIONS_REQUEST = "/api/suggestions/request";
|
|
|
16
16
|
const API_TRANSLATIONS_AVAILABILITY_CHECK =
|
|
17
17
|
"/api/translations/availability_check";
|
|
18
18
|
const API_TRANSLATIONS_REQUEST = "/api/translations/request";
|
|
19
|
+
const API_AGENT_LAYER_CONVERSATIONS = "/api/agent_layer/conversations";
|
|
19
20
|
const API_AGENT_LAYER_CONVERSATION = "/api/agent_layer/conversation";
|
|
20
21
|
const API_AGENT_LAYER_RUN = "/api/agent_layer/run";
|
|
21
22
|
const API_SOCKET_ENDPOINT = "/api/socket";
|
|
22
23
|
|
|
23
24
|
export {
|
|
24
25
|
API_ACTION,
|
|
26
|
+
API_AGENT_LAYER_CONVERSATIONS,
|
|
25
27
|
API_ACTIONS,
|
|
26
28
|
API_ACTIONS_SEARCH,
|
|
27
29
|
API_ACTION_SET_ACTIVE,
|
|
@@ -10,6 +10,7 @@ import Actions from "./actions/Actions";
|
|
|
10
10
|
import Action from "./actions/Action";
|
|
11
11
|
import ActionEdit from "./actions/ActionEdit";
|
|
12
12
|
import ActionNew from "./actions/ActionNew";
|
|
13
|
+
import AssistantPage from "./assistant/AssistantPage";
|
|
13
14
|
|
|
14
15
|
export default function AiRoutes() {
|
|
15
16
|
return (
|
|
@@ -52,6 +53,8 @@ export default function AiRoutes() {
|
|
|
52
53
|
<Route path={":id"} element={<Action />} />
|
|
53
54
|
</Route>
|
|
54
55
|
|
|
56
|
+
<Route path={"ai-assistant"} element={<AssistantPage />} />
|
|
57
|
+
|
|
55
58
|
<Route path="*" element={null} />
|
|
56
59
|
</Routes>
|
|
57
60
|
);
|
|
@@ -1,123 +1,231 @@
|
|
|
1
|
-
import { useRef, useState, useCallback, useEffect } from "react";
|
|
1
|
+
import { useRef, useState, useCallback, useEffect, useMemo } from "react";
|
|
2
2
|
import { useWebContext } from "@truedat/core/webContext";
|
|
3
|
+
import { Icon } from "semantic-ui-react";
|
|
4
|
+
import { useIntl } from "react-intl";
|
|
5
|
+
import { useNavigate } from "react-router";
|
|
6
|
+
import truedatLogo from "assets/truedat-logo-home-no-text.png";
|
|
3
7
|
|
|
4
8
|
import useAssistantSocket from "./hooks/useAssistantSocket";
|
|
5
9
|
import useAgentConversation from "./hooks/useAgentConversation";
|
|
6
|
-
import
|
|
10
|
+
import useAgentConversations from "../../hooks/useAgentConversations";
|
|
7
11
|
import AssistantChat from "./AssistantChat";
|
|
8
12
|
|
|
9
|
-
const FADE_DURATION_MS = 150;
|
|
10
13
|
const EMPTY_CONVERSATION = [];
|
|
14
|
+
const PANEL_ANIM_MS = 200;
|
|
15
|
+
const AI_ASSISTANT_PATH = "/ai-assistant";
|
|
11
16
|
|
|
12
17
|
const Assistant = () => {
|
|
13
|
-
const {
|
|
18
|
+
const {
|
|
19
|
+
disable_td_ai: disableTdAi = false,
|
|
20
|
+
assistant: {
|
|
21
|
+
name: assistantName = null,
|
|
22
|
+
welcome_message: welcomeMessage = null,
|
|
23
|
+
exact = false,
|
|
24
|
+
} = {},
|
|
25
|
+
} = useWebContext();
|
|
26
|
+
const { formatMessage } = useIntl();
|
|
27
|
+
const navigate = useNavigate();
|
|
14
28
|
|
|
15
29
|
const [isOpen, setIsOpen] = useState(false);
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
30
|
+
const [isClosing, setIsClosing] = useState(false);
|
|
31
|
+
const { conversationId, startNewConversation, selectConversation } =
|
|
32
|
+
useAgentConversation(isOpen);
|
|
33
|
+
const { conversations, refresh: refreshConversations } = useAgentConversations();
|
|
20
34
|
const [conversation, setConversation] = useState(EMPTY_CONVERSATION);
|
|
21
|
-
const
|
|
35
|
+
const [conversationTitle, setConversationTitle] = useState(null);
|
|
22
36
|
|
|
23
|
-
const
|
|
37
|
+
const prevConversationIdRef = useRef(null);
|
|
24
38
|
const serverMessageHandlerRef = useRef(null);
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const
|
|
39
|
+
const triggerRef = useRef(null);
|
|
40
|
+
const panelRef = useRef(null);
|
|
41
|
+
const closeTimerRef = useRef(null);
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
const handleOpen = (e) => {
|
|
45
|
+
const id = e.detail?.conversationId;
|
|
46
|
+
if (!id) return;
|
|
47
|
+
clearTimeout(closeTimerRef.current);
|
|
48
|
+
setIsClosing(false);
|
|
49
|
+
selectConversation(id);
|
|
50
|
+
setIsOpen(true);
|
|
51
|
+
};
|
|
52
|
+
window.addEventListener("assistant:open", handleOpen);
|
|
53
|
+
return () => window.removeEventListener("assistant:open", handleOpen);
|
|
54
|
+
}, [selectConversation]);
|
|
28
55
|
|
|
29
56
|
useEffect(() => {
|
|
30
57
|
if (conversationId !== prevConversationIdRef.current) {
|
|
31
58
|
prevConversationIdRef.current = conversationId;
|
|
32
59
|
if (conversationId != null) {
|
|
33
60
|
setConversation(EMPTY_CONVERSATION);
|
|
61
|
+
const fromList = conversations.find(
|
|
62
|
+
(c) => String(c.id) === String(conversationId)
|
|
63
|
+
);
|
|
64
|
+
setConversationTitle(fromList?.title ?? null);
|
|
34
65
|
}
|
|
35
66
|
}
|
|
36
|
-
}, [conversationId]);
|
|
67
|
+
}, [conversationId, conversations]);
|
|
68
|
+
|
|
69
|
+
useEffect(() => () => clearTimeout(closeTimerRef.current), []);
|
|
37
70
|
|
|
38
71
|
const onChannelJoined = useCallback(() => {}, []);
|
|
39
|
-
const handleServerMessage = useCallback(
|
|
40
|
-
|
|
41
|
-
|
|
72
|
+
const handleServerMessage = useCallback(
|
|
73
|
+
(event, payload) => {
|
|
74
|
+
if (event === "history") {
|
|
75
|
+
const messages = payload.messages || [];
|
|
76
|
+
const restored = messages
|
|
77
|
+
.filter((m) => m.role === "user" || m.role === "assistant")
|
|
78
|
+
.map((m) =>
|
|
79
|
+
m.role === "user"
|
|
80
|
+
? { eventType: "request", side: "user", content: m.message }
|
|
81
|
+
: {
|
|
82
|
+
eventType: "response",
|
|
83
|
+
side: "agent",
|
|
84
|
+
content: m.message,
|
|
85
|
+
streaming: false,
|
|
86
|
+
source: "history",
|
|
87
|
+
priority: 0,
|
|
88
|
+
},
|
|
89
|
+
);
|
|
90
|
+
setConversation(restored);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (event === "title") {
|
|
94
|
+
const title = payload?.title;
|
|
95
|
+
if (title) {
|
|
96
|
+
setConversationTitle(title);
|
|
97
|
+
refreshConversations();
|
|
98
|
+
}
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
serverMessageHandlerRef.current?.(event, payload);
|
|
102
|
+
},
|
|
103
|
+
[refreshConversations],
|
|
104
|
+
);
|
|
105
|
+
|
|
42
106
|
const handleNewChat = useCallback(() => {
|
|
43
107
|
setConversation(EMPTY_CONVERSATION);
|
|
44
108
|
startNewConversation();
|
|
45
109
|
}, [startNewConversation]);
|
|
46
110
|
|
|
111
|
+
const handleLoadLastConversation = useCallback(() => {
|
|
112
|
+
if (conversations.length === 0) return;
|
|
113
|
+
selectConversation(conversations[0].id);
|
|
114
|
+
}, [conversations, selectConversation]);
|
|
115
|
+
|
|
116
|
+
const handleMoreConversations = useCallback(() => {
|
|
117
|
+
const isInteracted =
|
|
118
|
+
conversationId != null &&
|
|
119
|
+
conversations.some((c) => String(c.id) === String(conversationId));
|
|
120
|
+
navigate(
|
|
121
|
+
AI_ASSISTANT_PATH,
|
|
122
|
+
isInteracted ? { state: { conversationId } } : undefined
|
|
123
|
+
);
|
|
124
|
+
}, [navigate, conversationId, conversations]);
|
|
125
|
+
|
|
126
|
+
const startClose = useCallback(() => {
|
|
127
|
+
setIsClosing(true);
|
|
128
|
+
closeTimerRef.current = setTimeout(() => {
|
|
129
|
+
setIsOpen(false);
|
|
130
|
+
setIsClosing(false);
|
|
131
|
+
}, PANEL_ANIM_MS);
|
|
132
|
+
}, []);
|
|
133
|
+
|
|
134
|
+
const handleClose = useCallback(() => startClose(), [startClose]);
|
|
135
|
+
|
|
136
|
+
const handleToggle = useCallback(() => {
|
|
137
|
+
if (!isOpen) {
|
|
138
|
+
setIsOpen(true);
|
|
139
|
+
} else if (isClosing) {
|
|
140
|
+
clearTimeout(closeTimerRef.current);
|
|
141
|
+
setIsClosing(false);
|
|
142
|
+
} else {
|
|
143
|
+
startClose();
|
|
144
|
+
}
|
|
145
|
+
}, [isOpen, isClosing, startClose]);
|
|
146
|
+
|
|
47
147
|
const effectiveConversationId = isOpen ? conversationId : null;
|
|
148
|
+
const currentTitle = useMemo(
|
|
149
|
+
() =>
|
|
150
|
+
conversationTitle ??
|
|
151
|
+
conversations.find((c) => String(c.id) === String(conversationId))?.title ??
|
|
152
|
+
null,
|
|
153
|
+
[conversationTitle, conversationId, conversations]
|
|
154
|
+
);
|
|
48
155
|
const { socketReady, sendPrompt } = useAssistantSocket(disableTdAi, {
|
|
49
156
|
conversationId: effectiveConversationId,
|
|
50
157
|
onChannelJoined,
|
|
51
158
|
onServerMessage: handleServerMessage,
|
|
159
|
+
assistantName,
|
|
160
|
+
welcomeMessage,
|
|
161
|
+
exact,
|
|
52
162
|
});
|
|
53
163
|
|
|
54
|
-
const handleTransitionEnd = useCallback(() => {
|
|
55
|
-
if (!pendingTransitionEndRef.current) return;
|
|
56
|
-
pendingTransitionEndRef.current = false;
|
|
57
|
-
setContentFadeIn(true);
|
|
58
|
-
fadeInTimeoutRef.current = setTimeout(() => {
|
|
59
|
-
setContentFadeIn(false);
|
|
60
|
-
}, FADE_DURATION_MS);
|
|
61
|
-
}, []);
|
|
62
|
-
|
|
63
164
|
useEffect(() => {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
165
|
+
if (!isOpen) return;
|
|
166
|
+
const handleClickOutside = (e) => {
|
|
167
|
+
if (
|
|
168
|
+
panelRef.current &&
|
|
169
|
+
!panelRef.current.contains(e.target) &&
|
|
170
|
+
triggerRef.current &&
|
|
171
|
+
!triggerRef.current.contains(e.target)
|
|
172
|
+
) {
|
|
173
|
+
startClose();
|
|
174
|
+
}
|
|
69
175
|
};
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return () => {
|
|
74
|
-
if (fadeTimeoutRef.current) clearTimeout(fadeTimeoutRef.current);
|
|
75
|
-
if (fadeInTimeoutRef.current) clearTimeout(fadeInTimeoutRef.current);
|
|
76
|
-
};
|
|
77
|
-
}, []);
|
|
78
|
-
|
|
79
|
-
const startOpenTransition = useCallback(() => {
|
|
80
|
-
setContentFadeOut(true);
|
|
81
|
-
fadeTimeoutRef.current = setTimeout(() => {
|
|
82
|
-
setContentFadeOut(false);
|
|
83
|
-
setIsOpen(true);
|
|
84
|
-
pendingTransitionEndRef.current = true;
|
|
85
|
-
}, FADE_DURATION_MS);
|
|
86
|
-
}, []);
|
|
87
|
-
|
|
88
|
-
const startCloseTransition = useCallback(() => {
|
|
89
|
-
setContentFadeOut(true);
|
|
90
|
-
fadeTimeoutRef.current = setTimeout(() => {
|
|
91
|
-
setContentFadeOut(false);
|
|
92
|
-
setIsOpen(false);
|
|
93
|
-
pendingTransitionEndRef.current = true;
|
|
94
|
-
}, FADE_DURATION_MS);
|
|
95
|
-
}, []);
|
|
176
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
177
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
178
|
+
}, [isOpen, startClose]);
|
|
96
179
|
|
|
97
180
|
if (!socketReady) return null;
|
|
98
181
|
|
|
182
|
+
const isLogoSrcString = typeof truedatLogo === "string";
|
|
183
|
+
|
|
99
184
|
return (
|
|
100
|
-
<div
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
185
|
+
<div className="assistant-container">
|
|
186
|
+
<button
|
|
187
|
+
ref={triggerRef}
|
|
188
|
+
type="button"
|
|
189
|
+
className={[
|
|
190
|
+
"assistant-bubble",
|
|
191
|
+
isOpen && !isClosing && "assistant-bubble--active",
|
|
192
|
+
]
|
|
193
|
+
.filter(Boolean)
|
|
194
|
+
.join(" ")}
|
|
195
|
+
onClick={handleToggle}
|
|
196
|
+
aria-label={formatMessage({ id: "assistant.bubble.label" })}
|
|
197
|
+
aria-expanded={isOpen && !isClosing}
|
|
198
|
+
>
|
|
199
|
+
{isLogoSrcString ? (
|
|
200
|
+
<img src={truedatLogo} alt="" className="assistant-bubble__logo" />
|
|
201
|
+
) : (
|
|
202
|
+
<Icon name="comments" />
|
|
203
|
+
)}
|
|
204
|
+
</button>
|
|
205
|
+
{isOpen && (
|
|
206
|
+
<div
|
|
207
|
+
ref={panelRef}
|
|
208
|
+
className={[
|
|
209
|
+
"assistant-panel",
|
|
210
|
+
isClosing && "assistant-panel--closing",
|
|
211
|
+
]
|
|
212
|
+
.filter(Boolean)
|
|
213
|
+
.join(" ")}
|
|
214
|
+
>
|
|
215
|
+
<AssistantChat
|
|
216
|
+
conversation={conversation}
|
|
217
|
+
setConversation={setConversation}
|
|
218
|
+
onNewChat={handleNewChat}
|
|
219
|
+
onClose={handleClose}
|
|
220
|
+
onSendMessage={sendPrompt}
|
|
221
|
+
onLoadLastConversation={
|
|
222
|
+
conversations.length > 0 ? handleLoadLastConversation : undefined
|
|
223
|
+
}
|
|
224
|
+
onMoreConversations={handleMoreConversations}
|
|
225
|
+
serverMessageHandlerRef={serverMessageHandlerRef}
|
|
226
|
+
title={currentTitle}
|
|
227
|
+
/>
|
|
228
|
+
</div>
|
|
121
229
|
)}
|
|
122
230
|
</div>
|
|
123
231
|
);
|
|
@@ -42,7 +42,7 @@ function getContentFromPayload(event, payload) {
|
|
|
42
42
|
const err = payload.error;
|
|
43
43
|
const errMsg =
|
|
44
44
|
err && typeof err === "object"
|
|
45
|
-
? err.message ?? err.reason ?? JSON.stringify(err)
|
|
45
|
+
? (err.message ?? err.reason ?? JSON.stringify(err))
|
|
46
46
|
: String(err ?? "");
|
|
47
47
|
return { content: errMsg, isError: true, priority, source };
|
|
48
48
|
}
|
|
@@ -50,7 +50,7 @@ function getContentFromPayload(event, payload) {
|
|
|
50
50
|
const log = payload.content;
|
|
51
51
|
const logMsg =
|
|
52
52
|
log && typeof log === "object"
|
|
53
|
-
? log.message ?? log.level ?? JSON.stringify(log)
|
|
53
|
+
? (log.message ?? log.level ?? JSON.stringify(log))
|
|
54
54
|
: String(log ?? "");
|
|
55
55
|
return { content: logMsg, isError: false, priority, source };
|
|
56
56
|
}
|
|
@@ -66,10 +66,7 @@ function appendStreamChunk(prev, source, chunk, priority) {
|
|
|
66
66
|
last?.streaming &&
|
|
67
67
|
last?.source === source;
|
|
68
68
|
if (sameSource) {
|
|
69
|
-
return [
|
|
70
|
-
...prev.slice(0, -1),
|
|
71
|
-
{ ...last, content: last.content + chunk },
|
|
72
|
-
];
|
|
69
|
+
return [...prev.slice(0, -1), { ...last, content: last.content + chunk }];
|
|
73
70
|
}
|
|
74
71
|
return [
|
|
75
72
|
...prev,
|
|
@@ -105,7 +102,8 @@ function buildRenderItems(messages) {
|
|
|
105
102
|
logBuffer.push(msg);
|
|
106
103
|
} else {
|
|
107
104
|
if (logBuffer.length > 0) {
|
|
108
|
-
const isFollowedByP0 =
|
|
105
|
+
const isFollowedByP0 =
|
|
106
|
+
msg.eventType === "response" && msg.priority === 0;
|
|
109
107
|
result.push({
|
|
110
108
|
type: "thinking-block",
|
|
111
109
|
items: logBuffer,
|
|
@@ -116,7 +114,11 @@ function buildRenderItems(messages) {
|
|
|
116
114
|
logBuffer = [];
|
|
117
115
|
startIdx = null;
|
|
118
116
|
}
|
|
119
|
-
result.push({
|
|
117
|
+
result.push({
|
|
118
|
+
type: "message",
|
|
119
|
+
msg,
|
|
120
|
+
key: `${msg.eventType}-${msg.index}`,
|
|
121
|
+
});
|
|
120
122
|
}
|
|
121
123
|
});
|
|
122
124
|
if (logBuffer.length > 0) {
|
|
@@ -137,9 +139,12 @@ const AssistantChat = ({
|
|
|
137
139
|
onNewChat,
|
|
138
140
|
onClose,
|
|
139
141
|
onSendMessage,
|
|
142
|
+
onLoadLastConversation,
|
|
143
|
+
onMoreConversations,
|
|
140
144
|
serverMessageHandlerRef,
|
|
141
145
|
fadeOut,
|
|
142
146
|
fadeIn,
|
|
147
|
+
title,
|
|
143
148
|
}) => {
|
|
144
149
|
const { formatMessage } = useIntl();
|
|
145
150
|
const [inputValue, setInputValue] = useState("");
|
|
@@ -179,15 +184,26 @@ const AssistantChat = ({
|
|
|
179
184
|
const ref = serverMessageHandlerRef;
|
|
180
185
|
if (!ref) return;
|
|
181
186
|
ref.current = (event, payload) => {
|
|
182
|
-
const { content, isError, priority, source } = getContentFromPayload(
|
|
187
|
+
const { content, isError, priority, source } = getContentFromPayload(
|
|
188
|
+
event,
|
|
189
|
+
payload,
|
|
190
|
+
);
|
|
183
191
|
if (event === "stream") {
|
|
184
192
|
requestAnimationFrame(() => {
|
|
185
|
-
setConversation((prev) =>
|
|
193
|
+
setConversation((prev) =>
|
|
194
|
+
appendStreamChunk(prev, source, content, priority),
|
|
195
|
+
);
|
|
186
196
|
});
|
|
187
197
|
} else if (event === "log") {
|
|
188
198
|
setConversation((prev) => [
|
|
189
199
|
...prev,
|
|
190
|
-
{
|
|
200
|
+
{
|
|
201
|
+
eventType: "log",
|
|
202
|
+
side: "agent",
|
|
203
|
+
content: content || " ",
|
|
204
|
+
priority,
|
|
205
|
+
source,
|
|
206
|
+
},
|
|
191
207
|
]);
|
|
192
208
|
} else if (event === "error" && Number(priority) === 2) {
|
|
193
209
|
setConversation((prev) => [
|
|
@@ -223,17 +239,17 @@ const AssistantChat = ({
|
|
|
223
239
|
|
|
224
240
|
const visibleMessages = useMemo(
|
|
225
241
|
() => getVisibleMessages(conversation),
|
|
226
|
-
[conversation]
|
|
242
|
+
[conversation],
|
|
227
243
|
);
|
|
228
244
|
|
|
229
245
|
const renderItems = useMemo(
|
|
230
246
|
() => buildRenderItems(visibleMessages),
|
|
231
|
-
[visibleMessages]
|
|
247
|
+
[visibleMessages],
|
|
232
248
|
);
|
|
233
249
|
|
|
234
250
|
const canSendMessages = useMemo(
|
|
235
251
|
() => conversation.some((m) => m.side === "agent"),
|
|
236
|
-
[conversation]
|
|
252
|
+
[conversation],
|
|
237
253
|
);
|
|
238
254
|
|
|
239
255
|
const handleSubmit = useCallback(
|
|
@@ -252,7 +268,7 @@ const AssistantChat = ({
|
|
|
252
268
|
onSendMessage(text);
|
|
253
269
|
}
|
|
254
270
|
},
|
|
255
|
-
[inputValue, onSendMessage, canSendMessages]
|
|
271
|
+
[inputValue, onSendMessage, canSendMessages],
|
|
256
272
|
);
|
|
257
273
|
|
|
258
274
|
const handleKeyDown = useCallback(
|
|
@@ -263,7 +279,7 @@ const AssistantChat = ({
|
|
|
263
279
|
handleSubmit();
|
|
264
280
|
}
|
|
265
281
|
},
|
|
266
|
-
[handleSubmit, canSendMessages]
|
|
282
|
+
[handleSubmit, canSendMessages],
|
|
267
283
|
);
|
|
268
284
|
|
|
269
285
|
const handleNewChat = useCallback(() => {
|
|
@@ -320,7 +336,7 @@ const AssistantChat = ({
|
|
|
320
336
|
if (!entry) return;
|
|
321
337
|
setShowScrollToBottom(!entry.isIntersecting);
|
|
322
338
|
},
|
|
323
|
-
{ root, threshold: 0 }
|
|
339
|
+
{ root, threshold: 0 },
|
|
324
340
|
);
|
|
325
341
|
io.observe(sentinel);
|
|
326
342
|
return () => io.disconnect();
|
|
@@ -346,24 +362,56 @@ const AssistantChat = ({
|
|
|
346
362
|
|
|
347
363
|
return (
|
|
348
364
|
<div className={className}>
|
|
365
|
+
<div className="assistant-chat__header">
|
|
366
|
+
<span className="assistant-chat__header-title">
|
|
367
|
+
{title || <FormattedMessage id="assistant.newConversation" />}
|
|
368
|
+
</span>
|
|
369
|
+
<button
|
|
370
|
+
type="button"
|
|
371
|
+
className="assistant-chat__action assistant-chat__action--close"
|
|
372
|
+
onClick={onClose}
|
|
373
|
+
aria-label={formatMessage({ id: "assistant.actions.close" })}
|
|
374
|
+
>
|
|
375
|
+
<Icon name="close" />
|
|
376
|
+
</button>
|
|
377
|
+
</div>
|
|
349
378
|
<div className="assistant-chat__actions">
|
|
350
379
|
<button
|
|
351
380
|
type="button"
|
|
352
|
-
className="assistant-chat__action
|
|
381
|
+
className="assistant-chat__action"
|
|
353
382
|
onClick={handleNewChat}
|
|
354
383
|
aria-label={formatMessage({ id: "assistant.actions.newChat" })}
|
|
384
|
+
title={formatMessage({ id: "assistant.actions.newChat" })}
|
|
355
385
|
>
|
|
356
386
|
<Icon name="comment outline" />
|
|
357
387
|
<FormattedMessage id="assistant.actions.newChat" />
|
|
358
388
|
</button>
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
389
|
+
{onLoadLastConversation && (
|
|
390
|
+
<button
|
|
391
|
+
type="button"
|
|
392
|
+
className="assistant-chat__action"
|
|
393
|
+
onClick={onLoadLastConversation}
|
|
394
|
+
aria-label={formatMessage({ id: "assistant.actions.loadLast" })}
|
|
395
|
+
title={formatMessage({ id: "assistant.actions.loadLast" })}
|
|
396
|
+
>
|
|
397
|
+
<Icon name="history" />
|
|
398
|
+
<FormattedMessage id="assistant.actions.loadLast" />
|
|
399
|
+
</button>
|
|
400
|
+
)}
|
|
401
|
+
{onMoreConversations && (
|
|
402
|
+
<button
|
|
403
|
+
type="button"
|
|
404
|
+
className="assistant-chat__action"
|
|
405
|
+
onClick={onMoreConversations}
|
|
406
|
+
aria-label={formatMessage({
|
|
407
|
+
id: "assistant.actions.moreConversations",
|
|
408
|
+
})}
|
|
409
|
+
title={formatMessage({ id: "assistant.actions.moreConversations" })}
|
|
410
|
+
>
|
|
411
|
+
<Icon name="expand arrows alternate" />
|
|
412
|
+
<FormattedMessage id="assistant.actions.moreConversations" />
|
|
413
|
+
</button>
|
|
414
|
+
)}
|
|
367
415
|
</div>
|
|
368
416
|
<div className="assistant-chat__messages-wrapper">
|
|
369
417
|
<div
|
|
@@ -386,7 +434,8 @@ const AssistantChat = ({
|
|
|
386
434
|
const { msg } = item;
|
|
387
435
|
const isP0 = msg.priority === 0;
|
|
388
436
|
const isErrorP0 = msg.isError && isP0;
|
|
389
|
-
const sourceLabel =
|
|
437
|
+
const sourceLabel =
|
|
438
|
+
isP0 && msg.source ? getSourceLabel(msg.source) : null;
|
|
390
439
|
return (
|
|
391
440
|
<div
|
|
392
441
|
key={item.key}
|
|
@@ -395,10 +444,14 @@ const AssistantChat = ({
|
|
|
395
444
|
`assistant-chat__message--${msg.eventType}`,
|
|
396
445
|
`assistant-chat__message--${msg.side}`,
|
|
397
446
|
isErrorP0 && "assistant-chat__message--error",
|
|
398
|
-
]
|
|
447
|
+
]
|
|
448
|
+
.filter(Boolean)
|
|
449
|
+
.join(" ")}
|
|
399
450
|
>
|
|
400
451
|
{sourceLabel && (
|
|
401
|
-
<div className="assistant-chat__message-source">
|
|
452
|
+
<div className="assistant-chat__message-source">
|
|
453
|
+
{sourceLabel}
|
|
454
|
+
</div>
|
|
402
455
|
)}
|
|
403
456
|
<div className="assistant-chat__message-content">
|
|
404
457
|
{msg.content?.trim() ? (
|
|
@@ -431,7 +484,9 @@ const AssistantChat = ({
|
|
|
431
484
|
type="button"
|
|
432
485
|
className="assistant-chat__scroll-to-bottom"
|
|
433
486
|
onClick={scrollToBottom}
|
|
434
|
-
aria-label={formatMessage({
|
|
487
|
+
aria-label={formatMessage({
|
|
488
|
+
id: "assistant.actions.scrollToBottom",
|
|
489
|
+
})}
|
|
435
490
|
>
|
|
436
491
|
<Icon name="chevron down" />
|
|
437
492
|
</button>
|
|
@@ -481,17 +536,23 @@ AssistantChat.propTypes = {
|
|
|
481
536
|
onNewChat: PropTypes.func,
|
|
482
537
|
onClose: PropTypes.func.isRequired,
|
|
483
538
|
onSendMessage: PropTypes.func,
|
|
539
|
+
onLoadLastConversation: PropTypes.func,
|
|
540
|
+
onMoreConversations: PropTypes.func,
|
|
484
541
|
serverMessageHandlerRef: PropTypes.shape({ current: PropTypes.any }),
|
|
485
542
|
fadeOut: PropTypes.bool,
|
|
486
543
|
fadeIn: PropTypes.bool,
|
|
544
|
+
title: PropTypes.string,
|
|
487
545
|
};
|
|
488
546
|
|
|
489
547
|
AssistantChat.defaultProps = {
|
|
490
548
|
onNewChat: undefined,
|
|
491
549
|
onSendMessage: undefined,
|
|
550
|
+
onLoadLastConversation: undefined,
|
|
551
|
+
onMoreConversations: undefined,
|
|
492
552
|
serverMessageHandlerRef: null,
|
|
493
553
|
fadeOut: false,
|
|
494
554
|
fadeIn: false,
|
|
555
|
+
title: null,
|
|
495
556
|
};
|
|
496
557
|
|
|
497
558
|
export default AssistantChat;
|