Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit f2cddf6

Browse filesBrowse files
author
Bogdan Tsechoev
committed
Send email notifications from AI
1 parent 28ce76b commit f2cddf6
Copy full SHA for f2cddf6

File tree

13 files changed

+327
-54
lines changed
Filter options

13 files changed

+327
-54
lines changed

‎ui/packages/platform/src/actions/actions.js

Copy file name to clipboardExpand all lines: ui/packages/platform/src/actions/actions.js
+37Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const Actions = Reflux.createActions([{
3838
ASYNC_ACTION: ASYNC_ACTION,
3939
doAuth: ASYNC_ACTION,
4040
getUserProfile: ASYNC_ACTION,
41+
updateUserProfile: ASYNC_ACTION,
4142
getAccessTokens: ASYNC_ACTION,
4243
getAccessToken: ASYNC_ACTION,
4344
hideGeneratedAccessToken: {},
@@ -267,6 +268,42 @@ Actions.getUserProfile.listen(function (token) {
267268
);
268269
});
269270

271+
Actions.updateUserProfile.listen(function (token, data) {
272+
let action = this;
273+
274+
if (!api) {
275+
settings.init(function () {
276+
api = new Api(settings);
277+
});
278+
}
279+
280+
this.progressed();
281+
282+
timeoutPromise(REQUEST_TIMEOUT, api.updateUserProfile(token, data))
283+
.then(result => {
284+
result.json()
285+
.then(json => {
286+
if (json) {
287+
action.completed({ data: json?.result });
288+
} else {
289+
action.failed(new Error('wrong_reply'));
290+
}
291+
})
292+
.catch(err => {
293+
console.error(err);
294+
action.failed(new Error('wrong_reply'));
295+
});
296+
})
297+
.catch(err => {
298+
console.error(err);
299+
if (err && err.message && err.message === 'timeout') {
300+
action.failed(new Error('failed_fetch'));
301+
} else {
302+
action.failed(new Error('wrong_reply'));
303+
}
304+
});
305+
});
306+
270307
Actions.getAccessTokens.listen(function (token, orgId) {
271308
let action = this;
272309

‎ui/packages/platform/src/api/api.js

Copy file name to clipboardExpand all lines: ui/packages/platform/src/api/api.js
+25Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,31 @@ class Api {
112112
});
113113
}
114114

115+
updateUserProfile(token, data) {
116+
let headers = {
117+
Authorization: 'Bearer ' + token,
118+
Accept: 'application/vnd.pgrst.object+json'
119+
};
120+
121+
let body = {};
122+
123+
if (data.is_chats_email_notifications_enabled !== 'undefined') {
124+
body.chats_email_notifications_enabled = data.is_chats_email_notifications_enabled;
125+
}
126+
127+
if (data.first_name !== 'undefined') {
128+
body.first_name = data.first_name;
129+
}
130+
131+
if (data.last_name !== 'undefined') {
132+
body.last_name = data.last_name;
133+
}
134+
135+
return this.post(`${this.apiServer}/rpc/update_user_profile`, body, {
136+
headers: headers
137+
});
138+
}
139+
115140
getAccessTokens(token, orgId) {
116141
let params = {};
117142
let headers = {

‎ui/packages/platform/src/components/BotSettingsForm/BotSettingsForm.tsx

Copy file name to clipboardExpand all lines: ui/packages/platform/src/components/BotSettingsForm/BotSettingsForm.tsx
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ const BotSettingsForm: React.FC<BotSettingsFormProps> = (props) => {
161161
enableReinitialize: true,
162162
initialValues: {
163163
threadVisibility:
164-
data?.orgProfile?.data?.is_chat_public_by_default ? 'public' : 'private',
164+
data?.orgProfile?.data?.is_chat_public_by_default ? 'public' : 'private'
165165
},
166166
onSubmit: () => {
167167
const currentOrgId = orgId || null;

‎ui/packages/platform/src/pages/Bot/BotWrapper.tsx

Copy file name to clipboardExpand all lines: ui/packages/platform/src/pages/Bot/BotWrapper.tsx
+3-1Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export interface BotWrapperProps {
77
orgId?: number;
88
envData: {
99
info?: {
10+
id?: number | null
1011
user_name?: string
1112
}
1213
};
@@ -38,7 +39,8 @@ export const BotWrapper = (props: BotWrapperProps) => {
3839
args={{
3940
threadId: props.match.params.threadId,
4041
orgId: props.orgData.id,
41-
isPublicByDefault: props.orgData.is_chat_public_by_default
42+
isPublicByDefault: props.orgData.is_chat_public_by_default,
43+
userId: props.envData.info?.id,
4244
}}>
4345
<BotPage {...props} />
4446
</AiBotProvider>

‎ui/packages/platform/src/pages/Bot/Messages/Message/Message.tsx

Copy file name to clipboardExpand all lines: ui/packages/platform/src/pages/Bot/Messages/Message/Message.tsx
+44-11Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useMemo, useState } from 'react'
1+
import React, { useEffect, useMemo, useRef, useState } from 'react'
22
import cn from "classnames";
33
import ReactMarkdown, { Components } from "react-markdown";
44
import rehypeRaw from "rehype-raw";
@@ -9,8 +9,9 @@ import { icons } from "@postgres.ai/shared/styles/icons";
99
import { DebugDialog } from "../../DebugDialog/DebugDialog";
1010
import { CodeBlock } from "./CodeBlock";
1111
import { disallowedHtmlTagsForMarkdown, permalinkLinkBuilder } from "../../utils";
12-
import { StateMessage } from "../../../../types/api/entities/bot";
12+
import { MessageStatus, StateMessage } from "../../../../types/api/entities/bot";
1313
import { MermaidDiagram } from "./MermaidDiagram";
14+
import { useAiBot } from "../../hooks";
1415

1516

1617
type BaseMessageProps = {
@@ -20,17 +21,19 @@ type BaseMessageProps = {
2021
name?: string;
2122
isLoading?: boolean;
2223
formattedTime?: string;
23-
aiModel?: string
24-
stateMessage?: StateMessage | null
25-
isCurrentStreamMessage?: boolean
24+
aiModel?: string;
25+
stateMessage?: StateMessage | null;
26+
isCurrentStreamMessage?: boolean;
2627
isPublic?: boolean;
28+
threadId?: string;
29+
status?: MessageStatus
2730
}
2831

2932
type AiMessageProps = BaseMessageProps & {
3033
isAi: true;
3134
content: string;
32-
aiModel: string
33-
isCurrentStreamMessage?: boolean
35+
aiModel: string;
36+
isCurrentStreamMessage?: boolean;
3437
}
3538

3639
type HumanMessageProps = BaseMessageProps & {
@@ -42,8 +45,8 @@ type HumanMessageProps = BaseMessageProps & {
4245
type LoadingMessageProps = BaseMessageProps & {
4346
isLoading: true;
4447
isAi: true;
45-
content?: undefined
46-
stateMessage: StateMessage | null
48+
content?: undefined;
49+
stateMessage: StateMessage | null;
4750
}
4851

4952
type MessageProps = AiMessageProps | HumanMessageProps | LoadingMessageProps;
@@ -261,14 +264,44 @@ export const Message = React.memo((props: MessageProps) => {
261264
aiModel,
262265
stateMessage,
263266
isCurrentStreamMessage,
264-
isPublic
267+
isPublic,
268+
threadId,
269+
status
265270
} = props;
266271

272+
const { updateMessageStatus } = useAiBot()
273+
274+
const elementRef = useRef<HTMLDivElement | null>(null);
275+
276+
267277
const [isDebugVisible, setDebugVisible] = useState(false);
268278

269279

270280
const classes = useStyles();
271281

282+
useEffect(() => {
283+
if (!isAi || isCurrentStreamMessage || status === 'read') return;
284+
285+
const observer = new IntersectionObserver(
286+
(entries) => {
287+
const entry = entries[0];
288+
if (entry.isIntersecting && threadId && id) {
289+
updateMessageStatus(threadId, id, 'read');
290+
observer.disconnect();
291+
}
292+
},
293+
{ threshold: 0.1 }
294+
);
295+
296+
if (elementRef.current) {
297+
observer.observe(elementRef.current);
298+
}
299+
300+
return () => {
301+
observer.disconnect();
302+
};
303+
}, [id, updateMessageStatus, isCurrentStreamMessage, isAi, threadId, status]);
304+
272305
const contentToRender: string = content?.replace(/\n/g, ' \n') || ''
273306

274307
const toggleDebugDialog = () => {
@@ -301,7 +334,7 @@ export const Message = React.memo((props: MessageProps) => {
301334
onClose={toggleDebugDialog}
302335
messageId={id}
303336
/>}
304-
<div className={classes.message}>
337+
<div ref={elementRef} className={classes.message}>
305338
<div className={classes.messageAvatar}>
306339
{isAi
307340
? <img

‎ui/packages/platform/src/pages/Bot/Messages/Messages.tsx

Copy file name to clipboardExpand all lines: ui/packages/platform/src/pages/Bot/Messages/Messages.tsx
+5-2Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ type FormattedTime = {
125125
[id: string]: Time
126126
}
127127

128-
export const Messages = React.memo(({orgId}: {orgId: number}) => {
128+
export const Messages = React.memo(({orgId, threadId}: {orgId: number, threadId?: string}) => {
129129
const {
130130
messages,
131131
loading: isLoading,
@@ -254,7 +254,8 @@ export const Messages = React.memo(({orgId}: {orgId: number}) => {
254254
created_at,
255255
content,
256256
ai_model,
257-
is_public
257+
is_public,
258+
status
258259
} = message;
259260
let name = 'You';
260261

@@ -283,6 +284,8 @@ export const Messages = React.memo(({orgId}: {orgId: number}) => {
283284
formattedTime={formattedTime}
284285
aiModel={ai_model}
285286
isPublic={is_public}
287+
threadId={threadId}
288+
status={status}
286289
/>
287290
)
288291
})}

‎ui/packages/platform/src/pages/Bot/hooks.tsx

Copy file name to clipboardExpand all lines: ui/packages/platform/src/pages/Bot/hooks.tsx
+27-3Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
AiModel,
1515
StateMessage,
1616
StreamMessage,
17-
ErrorMessage
17+
ErrorMessage, MessageStatus
1818
} from "../../types/api/entities/bot";
1919
import {getChatsWithWholeThreads} from "../../api/bot/getChatsWithWholeThreads";
2020
import {getChats} from "api/bot/getChats";
@@ -73,16 +73,18 @@ type UseAiBotReturnType = {
7373
isStreamingInProcess: boolean;
7474
currentStreamMessage: StreamMessage | null;
7575
errorMessage: ErrorMessage | null;
76+
updateMessageStatus: (threadId: string, messageId: string, status: MessageStatus) => void
7677
}
7778

7879
type UseAiBotArgs = {
7980
threadId?: string;
8081
orgId?: number
8182
isPublicByDefault?: boolean
83+
userId?: number | null
8284
}
8385

8486
export const useAiBotProviderValue = (args: UseAiBotArgs): UseAiBotReturnType => {
85-
const { threadId, orgId, isPublicByDefault } = args;
87+
const { threadId, orgId, isPublicByDefault, userId } = args;
8688
const { showMessage, closeSnackbar } = useAlertSnackbar();
8789
const {
8890
aiModels,
@@ -413,6 +415,27 @@ export const useAiBotProviderValue = (args: UseAiBotArgs): UseAiBotReturnType =>
413415
}))
414416
}
415417

418+
const updateMessageStatus = (threadId: string, messageId: string, status: MessageStatus) => {
419+
wsSendMessage(JSON.stringify({
420+
action: 'message_status_update',
421+
payload: {
422+
thread_id: threadId,
423+
message_id: messageId,
424+
read_by: userId,
425+
status
426+
}
427+
}))
428+
if (messages && messages.length > 0) {
429+
const updatedMessages = messages.map((item) => {
430+
if (item.id === messageId) {
431+
item["status"] = status
432+
}
433+
return item
434+
});
435+
setMessages(updatedMessages)
436+
}
437+
}
438+
416439
const getDebugMessagesForWholeThread = async () => {
417440
setDebugMessagesLoading(true)
418441
if (threadId) {
@@ -478,7 +501,8 @@ export const useAiBotProviderValue = (args: UseAiBotArgs): UseAiBotReturnType =>
478501
stateMessage,
479502
isStreamingInProcess,
480503
currentStreamMessage,
481-
errorMessage
504+
errorMessage,
505+
updateMessageStatus
482506
}
483507
}
484508

‎ui/packages/platform/src/pages/Bot/index.tsx

Copy file name to clipboardExpand all lines: ui/packages/platform/src/pages/Bot/index.tsx
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ export const BotPage = (props: BotPageProps) => {
268268
</Box>
269269
</Box>
270270
<Box className={cn(classes.contentContainer, {[classes.isChatsListVisible]: isChatsListVisible})}>
271-
<Messages orgId={orgData.id} />
271+
<Messages orgId={orgData.id} threadId={match.params.threadId} />
272272
<Command
273273
threadId={match.params.threadId}
274274
orgId={orgData.id}

‎ui/packages/platform/src/pages/Profile/ProfileWrapper.tsx

Copy file name to clipboardExpand all lines: ui/packages/platform/src/pages/Profile/ProfileWrapper.tsx
+22Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,28 @@ export const ProfileWrapper = () => {
1717
marginLeft: theme.spacing(1),
1818
marginRight: theme.spacing(1),
1919
},
20+
formControlLabel: {
21+
marginLeft: theme.spacing(0),
22+
marginRight: theme.spacing(1),
23+
},
24+
formControlLabelCheckbox: {
25+
'& svg': {
26+
fontSize: 18
27+
}
28+
},
29+
updateButtonContainer: {
30+
marginTop: theme.spacing(3),
31+
marginLeft: theme.spacing(1),
32+
marginRight: theme.spacing(1),
33+
},
34+
label: {
35+
marginTop: theme.spacing(2),
36+
marginBottom: theme.spacing(1),
37+
marginLeft: theme.spacing(1),
38+
marginRight: theme.spacing(1),
39+
color: '#000!important',
40+
fontWeight: 'bold',
41+
},
2042
dense: {
2143
marginTop: 16,
2244
},

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.