Skip to content

Commit 89ddffa

Browse files
author
Bogdan Tsechoev
committed
Bot UI: Display debug messages
1 parent da38a60 commit 89ddffa

File tree

16 files changed

+486
-197
lines changed

16 files changed

+486
-197
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import {request} from "../../helpers/request";
2+
import { DebugMessage } from "../../types/api/entities/bot";
3+
4+
type Req =
5+
| { thread_id: string; message_id?: string }
6+
| { thread_id?: string; message_id: string };
7+
8+
export const getDebugMessages = async (req: Req): Promise<{ response: DebugMessage[] | null; error: Response | null }> => {
9+
const { thread_id, message_id } = req;
10+
11+
const params: { [key: string]: string } = {};
12+
13+
if (thread_id) {
14+
params['chat_thread_id'] = `eq.${thread_id}`;
15+
}
16+
17+
if (message_id) {
18+
params['chat_msg_id'] = `eq.${message_id}`;
19+
}
20+
21+
const queryString = new URLSearchParams(params).toString();
22+
23+
const apiServer = process.env.REACT_APP_API_URL_PREFIX || '';
24+
25+
try {
26+
const response = await request(`${apiServer}/chat_debug_messages?${queryString}`);
27+
28+
if (!response.ok) {
29+
return { response: null, error: response };
30+
}
31+
32+
const responseData: DebugMessage[] = await response.json();
33+
34+
return { response: responseData, error: null };
35+
36+
} catch (error) {
37+
return { response: null, error: error as Response };
38+
}
39+
}

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { BotPage } from "./index";
22
import {RouteComponentProps} from "react-router";
33
import {AlertSnackbarProvider} from "@postgres.ai/shared/components/AlertSnackbar/useAlertSnackbar";
4+
import { AiBotProvider } from "./hooks";
45

56
export interface BotWrapperProps {
67
envData: {
@@ -25,7 +26,9 @@ export interface BotWrapperProps {
2526
export const BotWrapper = (props: BotWrapperProps) => {
2627
return (
2728
<AlertSnackbarProvider>
28-
<BotPage {...props} />
29+
<AiBotProvider args={{ threadId: props.match.params.threadId, orgId: props.orgData.id }}>
30+
<BotPage {...props} />
31+
</AiBotProvider>
2932
</AlertSnackbarProvider>
3033
)
3134
}

ui/packages/platform/src/pages/Bot/ChatsList/ChatsList.tsx

+5-8
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const useStyles = makeStyles<Theme, ChatsListProps>((theme) => ({
3030
[theme.breakpoints.down('sm')]: {
3131
height: '100vh!important',
3232
marginTop: '0!important',
33-
width: 260,
33+
width: 300,
3434
zIndex: 9999
3535
},
3636
'& > ul': {
@@ -94,7 +94,6 @@ type ChatsListProps = {
9494
loading: boolean;
9595
chatsList: BotMessage[] | null;
9696
onLinkClick?: (targetThreadId: string) => void;
97-
permalinkId?: string
9897
} & HeaderButtonsProps
9998

10099
export const ChatsList = (props: ChatsListProps) => {
@@ -104,11 +103,10 @@ export const ChatsList = (props: ChatsListProps) => {
104103
onClose,
105104
chatsList,
106105
loading,
107-
currentVisibility,
108106
withChatVisibilityButton,
109-
onChatVisibilityClick,
107+
onSettingsClick,
110108
onLinkClick,
111-
permalinkId
109+
onConsoleClick
112110
} = props;
113111
const classes = useStyles(props);
114112
const params = useParams<{ org?: string, threadId?: string }>();
@@ -150,10 +148,9 @@ export const ChatsList = (props: ChatsListProps) => {
150148
onClose={onClose}
151149
onCreateNewChat={onCreateNewChat}
152150
isOpen={isOpen}
153-
currentVisibility={currentVisibility}
154151
withChatVisibilityButton={withChatVisibilityButton}
155-
onChatVisibilityClick={onChatVisibilityClick}
156-
permalinkId={permalinkId}
152+
onSettingsClick={onSettingsClick}
153+
onConsoleClick={onConsoleClick}
157154
/>
158155
<Divider/>
159156
</ListSubheader>

ui/packages/platform/src/pages/Bot/Command/Command.tsx

+30-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useRef, useEffect } from 'react'
1+
import React, { useState, useRef, useEffect, useMemo } from 'react'
22
import { makeStyles } from '@material-ui/core'
33
import SendRoundedIcon from '@material-ui/icons/SendRounded';
44
import IconButton from "@material-ui/core/IconButton";
@@ -14,12 +14,13 @@ import { useBuffer } from './useBuffer'
1414
import { useCaret } from './useCaret'
1515
import { theme } from "@postgres.ai/shared/styles/theme";
1616
import { isMobileDevice } from "../../../utils/utils";
17+
import { useAiBot } from "../hooks";
18+
import { ReadyState } from "react-use-websocket";
1719

1820

1921
type Props = {
20-
sendDisabled: boolean
21-
onSend: (value: string) => void
2222
threadId?: string
23+
orgId: number | null
2324
}
2425

2526

@@ -74,10 +75,22 @@ const useStyles = makeStyles((theme) => (
7475
)
7576

7677
export const Command = React.memo((props: Props) => {
77-
const { sendDisabled, onSend, threadId } = props
78+
const { threadId, orgId } = props
7879

7980
const classes = useStyles()
8081
const isMobile = isMobileDevice();
82+
83+
const {
84+
error,
85+
wsReadyState,
86+
wsLoading,
87+
loading,
88+
sendMessage,
89+
chatVisibility
90+
} = useAiBot();
91+
92+
const sendDisabled = error !== null || loading || wsLoading || wsReadyState !== ReadyState.OPEN;
93+
8194
// Handle value.
8295
const [value, setValue] = useState('')
8396

@@ -90,10 +103,19 @@ export const Command = React.memo((props: Props) => {
90103
// Input caret.
91104
const caret = useCaret(inputRef)
92105

93-
const triggerSend = () => {
106+
const onSend = async (message: string) => {
107+
await sendMessage({
108+
content: message,
109+
thread_id: threadId || null,
110+
org_id: orgId,
111+
is_public: chatVisibility === 'public'
112+
})
113+
}
114+
115+
const triggerSend = async () => {
94116
if (!value.trim().length || sendDisabled) return
95117

96-
onSend(value)
118+
await onSend(value)
97119
buffer.addNew()
98120
setValue(buffer.getCurrent())
99121
}
@@ -122,13 +144,13 @@ export const Command = React.memo((props: Props) => {
122144
}
123145
}
124146

125-
const handleKeyDown = (e: React.KeyboardEvent) => {
147+
const handleKeyDown = async (e: React.KeyboardEvent) => {
126148
if (!inputRef.current) return
127149

128150
// Trigger to send.
129151
if (checkIsSendCmd(e.nativeEvent)) {
130152
e.preventDefault()
131-
triggerSend()
153+
await triggerSend()
132154
return
133155
}
134156

@@ -171,7 +193,6 @@ export const Command = React.memo((props: Props) => {
171193
setValue('')
172194
}, [threadId]);
173195

174-
175196
return (
176197
<div className={classes.root}>
177198
<TextField
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import React, { useEffect, useRef, useState } from "react";
2+
import Dialog from "@material-ui/core/Dialog";
3+
import { DialogContent, DialogTitle, makeStyles } from "@material-ui/core";
4+
import { useAiBot } from "../hooks";
5+
import rehypeRaw from "rehype-raw";
6+
import remarkGfm from "remark-gfm";
7+
import ReactMarkdown from "react-markdown";
8+
import IconButton from "@material-ui/core/IconButton";
9+
import CloseIcon from "@material-ui/icons/Close";
10+
import { disallowedHtmlTagsForMarkdown } from "../utils";
11+
import { DebugLogs } from "../DebugLogs/DebugLogs";
12+
import { DebugMessage } from "../../../types/api/entities/bot";
13+
14+
const useStyles = makeStyles(
15+
(theme) => ({
16+
dialogStyle: {
17+
top: '5%!important',
18+
right: '10%!important',
19+
left: 'unset!important',
20+
height: 'fit-content',
21+
width: 'fit-content',
22+
},
23+
paper: {
24+
width: '80vw',
25+
height: '70vh',
26+
opacity: '.5',
27+
transition: '.2s ease',
28+
'&:hover': {
29+
opacity: 1
30+
}
31+
},
32+
dialogTitle: {
33+
padding: '0.5rem',
34+
'& h2': {
35+
fontSize: '1rem',
36+
display: 'flex',
37+
justifyContent: 'space-between',
38+
alignItems: 'center'
39+
}
40+
},
41+
dialogContent: {
42+
padding: 0,
43+
margin: 0,
44+
}
45+
}))
46+
47+
type DebugConsoleProps = {
48+
isOpen: boolean
49+
onClose: () => void
50+
threadId?: string
51+
}
52+
53+
export const DebugConsole = (props: DebugConsoleProps) => {
54+
const { isOpen, onClose, threadId } = props;
55+
const { debugMessages, debugMessagesLoading } = useAiBot();
56+
const classes = useStyles();
57+
const containerRef = useRef<HTMLDivElement>(null);
58+
59+
useEffect(() => {
60+
if (containerRef.current && debugMessages?.length && threadId && !debugMessagesLoading && isOpen) {
61+
let code: HTMLElement = containerRef.current.getElementsByTagName('code')?.[0];
62+
if (code.hasChildNodes()) {
63+
code.appendChild(document.createTextNode(`[${debugMessages[debugMessages.length - 1].created_at}]: ${debugMessages[debugMessages.length - 1].content}\n`))
64+
} else {
65+
debugMessages.forEach((item) => {
66+
code.appendChild(document.createTextNode(`[${item.created_at}]: ${item.content}\n`))
67+
})
68+
const container = document.getElementById(`logs-container-${threadId}`);
69+
if (container) {
70+
container.appendChild(code)
71+
}
72+
}
73+
}
74+
}, [debugMessages, isOpen, threadId, debugMessagesLoading]);
75+
76+
return (
77+
<Dialog
78+
open={isOpen}
79+
disableEnforceFocus
80+
hideBackdrop
81+
className={classes.dialogStyle}
82+
classes={{paper: classes.paper}}
83+
scroll="paper"
84+
fullWidth
85+
maxWidth="xl"
86+
>
87+
<DialogTitle className={classes.dialogTitle}>
88+
Debug console
89+
<IconButton
90+
onClick={onClose}
91+
>
92+
<CloseIcon />
93+
</IconButton>
94+
</DialogTitle>
95+
<DialogContent
96+
classes={{root: classes.dialogContent}}
97+
ref={containerRef}
98+
>
99+
<DebugLogs
100+
isLoading={debugMessagesLoading}
101+
isEmpty={debugMessages?.length === 0}
102+
id={threadId!}
103+
/>
104+
</DialogContent>
105+
</Dialog>
106+
)
107+
}

0 commit comments

Comments
 (0)