Skip to content

Commit

Permalink
Admin ui improvements (#27)
Browse files Browse the repository at this point in the history
- show user name on chat message
- show date
  • Loading branch information
bdb-dd authored Apr 19, 2024
1 parent d4b9741 commit f7a0c18
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 39 deletions.
5 changes: 4 additions & 1 deletion apps/admin/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@ function App() {
mainContent={
<>
{selectedChannel && (
<ChatMessageView selectedChannel={selectedChannel} />
<ChatMessageView
selectedChannel={selectedChannel}
selectedTeam={selectedTeam}
/>
)}
</>
}
Expand Down
46 changes: 36 additions & 10 deletions apps/admin/src/components/ChatMessageItemView.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
import React from "react";
import { Button } from "@mui/material";
import { DocsUserQuery, Message } from "../models/Models";
import React, { useEffect } from "react";
import { Button, Box } from "@mui/material";
import { DocsUserQuery, Message, User } from "../models/Models";
import { useUsers } from "../hooks/useUsers";

interface ChatMessageItemViewProps {
message: Message;
selectedTeam: string;
onClick: () => void;
}

const ChatMessageItemView: React.FC<ChatMessageItemViewProps> = ({
message,
selectedTeam,
onClick,
}) => {
const { data: users, error, isLoading } = useUsers({ selectedTeam });

useEffect(() => {
console.log(`Selected team: ${selectedTeam}`);
console.log(`message.user_id: ${message.user_id}`);
console.log(`Users.length: ${users?.length}`);
}, [users, selectedTeam]);

try {
return (
<Button
Expand All @@ -23,21 +34,36 @@ const ChatMessageItemView: React.FC<ChatMessageItemViewProps> = ({
textTransform: "none",
}}
>
<div>
<div>
<Box
display="flex"
flexDirection="column"
width="100%"
style={{ color: "black" }}
>
<Box
display="flex"
flexDirection="row"
width="100%"
justifyContent="space-between"
>
<span style={{ fontWeight: "bold" }}>
Username: {message.user_name}
{users?.filter((u) => u.user_id == message.user_id)[0]!?.name}
</span>

<span style={{ color: "gray" }}>
{new Date(message.ts_date * 1000).toTimeString().slice(0, 5)}
{
new Date(message.ts_date * 1000)
.toLocaleDateString("en-US", { day: "2-digit", month: "short" })
+ ' '
+ new Date(message.ts_date * 1000).toTimeString().slice(0, 5)
}
</span>
</div>
</Box>
<div>{(message.content as DocsUserQuery).original_user_query}</div>
</div>
</Box>
</Button>
);
} catch (error) {
} catch (error: any) {
console.error(
"Error displaying ChatMessageItemView:",
error.message,
Expand Down
7 changes: 6 additions & 1 deletion apps/admin/src/components/ChatMessageView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ import { useMessages } from "../hooks/useMessages";

interface Props {
selectedChannel: string;
selectedTeam: string;
}

const ChatMessageView: React.FC<Props> = ({ selectedChannel }) => {
const ChatMessageView: React.FC<Props> = ({
selectedChannel,
selectedTeam,
}) => {
const theme = useTheme();
const isSmallScreen = useMediaQuery(theme.breakpoints.down("sm"));
const { messages, error, isLoading, setCurrentMessageId, currentMessageId } =
Expand Down Expand Up @@ -38,6 +42,7 @@ const ChatMessageView: React.FC<Props> = ({ selectedChannel }) => {
{message.content_type === "docs_user_query" && (
<ChatMessageItemView
message={message}
selectedTeam={selectedTeam}
onClick={() => {
setCurrentMessageId({
ts_date: message.ts_date,
Expand Down
41 changes: 26 additions & 15 deletions apps/admin/src/components/ThreadViewPane.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect } from "react";
import { Link, List, ListItem } from "@mui/material";
import { Props } from "../models/Models";
import { Props, Message } from "../models/Models";
import ThreadStart from "./ThreadStart";
import { useThreadReplies } from "../hooks/useThreadReplies";
import { RagPipelineResult } from "@digdir/assistants";
Expand All @@ -16,53 +16,64 @@ const ThreadViewPane: React.FC<Props> = ({
isLoading,
} = useThreadReplies({ channelId, thread_ts_date, thread_ts_time });

const botReplyDetails = (message) => {
const botReplyDetails = (message: Message) => {
if (message?.content_type != "docs_bot_reply") {
return "";
}
const details = message?.content as RagPipelineResult;
return (
<>
<ListItem style={{ flexWrap: "wrap" }}>
<ListItem
style={{ flexWrap: "wrap" }}
key={message.ts_date + "." + message.ts_time + "_1"}
>
<h5 style={{ flexBasis: "100%" }}>
Phrases generated for retrieval:
</h5>
<ul>{details?.search_queries.map((query) => <li>{query}</li>)}</ul>
<ul>
{details?.search_queries.map((query: any) => <li>{query}</li>)}
</ul>
</ListItem>
<ListItem style={{ flexWrap: "wrap" }}>
<ListItem
style={{ flexWrap: "wrap" }}
key={message.ts_date + "." + message.ts_time + "_2"}
>
<h5 style={{ flexBasis: "100%" }}>Sources</h5>
<ul>
{details?.source_urls.map((url) => (
<li>
{details?.source_urls.map((url: string) => (
<li key={url}>
<Link href={url} target="_new" rel="noopener noreferrer">
{url.replace("https://docs.altinn.studio/", "")}
</Link>
</li>
))}
</ul>
</ListItem>
<ListItem style={{ flexWrap: "wrap" }}>
<ListItem
style={{ flexWrap: "wrap" }}
key={message.ts_date + "." + message.ts_time + "_3"}
>
<h5 style={{ flexBasis: "100%" }}>Processing times</h5>
<p style={{ flexBasis: "100%" }}>
Total: {message?.durations?.total.toFixed(1)}
</p>
<ul>
<li>Analyze: {message?.durations?.analyze.toFixed(1)}</li>
<li>
<li key="analyze">Analyze: {message?.durations?.analyze.toFixed(1)}</li>
<li key="generate_searches">
Generate searches:{" "}
{message?.durations?.generate_searches.toFixed(1)}
</li>
<li>
<li key="phrase_similarity_search">
Phrase similarity:{" "}
{message?.durations?.phrase_similarity_search.toFixed(1)}
</li>
<li>
<li key="execute_searches">
Execute searches:{" "}
{message?.durations?.execute_searches.toFixed(1)}
</li>
<li>Re-rank: {message?.durations?.colbert_rerank.toFixed(1)}</li>
<li>Generate answer: {message?.durations?.rag_query.toFixed(1)}</li>
<li>Translate: {message?.durations?.translation.toFixed(1)}</li>
<li key="rerank">Re-rank: {message?.durations?.colbert_rerank.toFixed(1)}</li>
<li key="generate_answer">Generate answer: {message?.durations?.rag_query.toFixed(1)}</li>
<li key="translate">Translate: {message?.durations?.translation.toFixed(1)}</li>
</ul>
</ListItem>
</>
Expand Down
3 changes: 2 additions & 1 deletion apps/admin/src/components/TopBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const StyledAppBar = styled(AppBar)(({ theme }) => ({
interface TopBarProps {
selectedTeam: string;
onTeamChange: (teamId: string) => void;
selectedChannel: string;
onChannelSelect: (channelId: string) => void;
}

Expand All @@ -32,7 +33,7 @@ const TopBar: React.FC<TopBarProps> = ({

return (
<StyledAppBar position="fixed">
<Toolbar sx={{ justifyContent: "space-evenly" }}>
<Toolbar sx={{ gap: "30px" }}>
<Typography variant="h6" noWrap component="div">
Digdir Assistants
</Typography>
Expand Down
108 changes: 108 additions & 0 deletions apps/admin/src/hooks/useUsers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { useEffect } from "react";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import supabase from "../supabase/SupabaseClient";
import { User } from "../models/Models";

interface UseUsersProps {
selectedTeam: string;
}

export const useUsers = ({ selectedTeam }: UseUsersProps) => {
const queryClient = useQueryClient();

// const getUserById = (userId: string) => {
// const { data: users } = useQuery<User[], Error>({
// queryKey: ["users", selectedTeam],
// queryFn: async () => {
// try {
// if (!selectedTeam) {
// return [];
// }
// const { data, error } = await supabase
// .from("user")
// .select("*")
// .eq("team_id", selectedTeam);
// if (error) {
// console.error("Error fetching users:", error.message, error.details);
// throw new Error(error.message);
// }
// return data;
// } catch (error) {
// console.error(
// "Error in useUsers query:",
// error instanceof Error ? error.message : "An unknown error occurred",
// );
// throw error;
// }
// },
// enabled: !!selectedTeam,
// });

// return users?.find((user) => user.user_id === userId);
// };

useEffect(() => {
if (!selectedTeam) return;

const subscription = supabase
.channel(`public:slack_user:team_id=eq.${selectedTeam}`)
.on(
"postgres_changes",
{ event: "*", schema: "public", table: "slack_user" },
(payload) => {
console.log(
`Realtime update received for users in team ${selectedTeam}:`,
payload.new,
);
queryClient.invalidateQueries(["users", selectedTeam]);
},
)
.subscribe(
(status) => {
console.log(`Subscription status: ${status}`);
},
(error) => {
console.error(
"Error subscribing to user updates:",
error.message,
error.stack,
);
},
);

return () => {
console.log("Unsubscribing from user updates.");
supabase.removeChannel(subscription);
};
}, [selectedTeam, queryClient]);

return useQuery<User[], Error>({
queryKey: ["users", selectedTeam],
enabled: !!selectedTeam,
queryFn: async (): Promise<User[]> => {
try {
if (!selectedTeam) {
console.log("No team selected, won't fetch users");
return [];
}
console.log(`Fetching users for team: ${selectedTeam}`);
const { data, error } = await supabase
.from("slack_user")
.select("*")
.eq("team_id", selectedTeam);
if (error) {
console.error("Error fetching users:", error.message, error.details);
throw new Error(error.message);
}
console.log("Users fetched successfully with React Query.", data);
return data;
} catch (error) {
console.error(
"Error in useUsers query:",
error instanceof Error ? error.message : "An unknown error occurred",
);
throw error;
}
},
});
};
32 changes: 21 additions & 11 deletions apps/admin/src/models/Models.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
import { z } from "zod";

export interface Message {
ts: string;
ts_date: number;
ts_time: number;
thread_ts_date: number;
thread_ts_time: number;
content: object | Array<object>;
content_type: string;
created_at: string;
user_name: string;
}
const UserSchema = z.object({
user_id: z.string(),
name: z.string(),
type: z.string(),
team_id: z.string(),
});
export type User = z.infer<typeof UserSchema>;

const MessageSchema = z.object({
ts: z.string(),
ts_date: z.number(),
ts_time: z.number(),
thread_ts_date: z.number(),
thread_ts_time: z.number(),
content: z.union([z.object({}), z.array(z.object({}))]),
content_type: z.string(),
created_at: z.string(),
user_id: z.string(),
durations: z.object({}).optional(),
});
export type Message = z.infer<typeof MessageSchema>;

const DocsUserQuerySchema = z.object({
bot_name: z.string(),
Expand Down

0 comments on commit f7a0c18

Please sign in to comment.