Skip to content

Commit

Permalink
feat: assistant improvements (#324)
Browse files Browse the repository at this point in the history
## Motivation

- overall code section improvement
- adds dark/light syntax highlighter theme
- warp lines in code blocks
- automatic chat titles
- prepare the code for further extractions
- improved markdown images -
  • Loading branch information
pixelass authored May 28, 2024
1 parent 36296c4 commit 509e474
Show file tree
Hide file tree
Showing 17 changed files with 1,057 additions and 751 deletions.
172 changes: 172 additions & 0 deletions src/client/apps/assistant/components/chat-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import { useSDK } from "@captn/react/use-sdk";
import DeleteForeverIcon from "@mui/icons-material/DeleteForever";
import MoreVertIcon from "@mui/icons-material/MoreVert";
import PushPinIcon from "@mui/icons-material/PushPin";
import StarIcon from "@mui/icons-material/Star";
import Dropdown from "@mui/joy/Dropdown";
import IconButton from "@mui/joy/IconButton";
import List from "@mui/joy/List";
import ListItem from "@mui/joy/ListItem";
import ListItemButton from "@mui/joy/ListItemButton";
import ListItemDecorator from "@mui/joy/ListItemDecorator";
import Menu from "@mui/joy/Menu";
import MenuButton from "@mui/joy/MenuButton";
import MenuItem from "@mui/joy/MenuItem";
import type { ColorPaletteProp } from "@mui/joy/styles";
import Typography from "@mui/joy/Typography";
import { useTranslation } from "next-i18next";
import type { Except } from "type-fest";

import { APP_ID } from "@/client/apps/assistant/constants";
import type { ChatModel } from "@/shared/types/assistant";

export function ChatList({
chats,
chatId,
color,
onDelete,
onChatSelect,
}: {
chatId: string;
chats: (Except<ChatModel, "messages"> & { id: string })[];
color: ColorPaletteProp;
onDelete?(chatId: string): void;
onChatSelect(chatId: string): void;
}) {
const { t } = useTranslation(["labels", "common"]);
const { send } = useSDK<unknown, object>(APP_ID, {});
return (
<List
sx={{
flex: 1,
width: "100%",
overflowY: "auto",
overflowX: "hidden",
flexShrink: 0,
}}
>
{chats
.sort((a, b) => {
if (a.pinned && b.pinned) {
return 0;
}

return a.pinned ? -1 : 1;
})
.map(chat => (
<ListItem
key={chat.id}
sx={{
"& .MuiListItem-endAction": {
mr: -0.75,
},
}}
endAction={
<Dropdown>
<MenuButton
aria-label={t("common:menu")}
slots={{ root: IconButton }}
slotProps={{
root: {
variant: "plain",
color: "neutral",
sx: {
"&:focus-visible": {
zIndex: "unset",
outlineOffset: -2,
},
},
},
}}
>
<MoreVertIcon />
</MenuButton>
<Menu color="neutral" variant="plain">
<MenuItem
onClick={() => {
send({
action: "assistant:update",
payload: {
id: chat.id,
update: { pinned: !chat.pinned },
},
});
}}
>
<ListItemDecorator
sx={theme => ({
"--Icon-color": chat.pinned
? theme.vars.palette.primary["500"]
: "currentColor",
})}
>
<PushPinIcon />
</ListItemDecorator>{" "}
{t("labels:pin")}
</MenuItem>
<MenuItem
onClick={() => {
send({
action: "assistant:update",
payload: {
id: chat.id,
update: {
favorite: !chat.favorite,
},
},
});
}}
>
<ListItemDecorator
sx={theme => ({
"--Icon-color": chat.favorite
? theme.vars.palette.primary["500"]
: "currentColor",
})}
>
<StarIcon />
</ListItemDecorator>{" "}
{t("labels:favorite")}
</MenuItem>
<MenuItem
onClick={() => {
if (onDelete) {
onDelete(chat.id);
}
}}
>
<ListItemDecorator
sx={theme => ({
"--Icon-color": theme.vars.palette.red["500"],
})}
>
<DeleteForeverIcon />
</ListItemDecorator>{" "}
{t("labels:delete")}
</MenuItem>
</Menu>
</Dropdown>
}
>
<ListItemButton
selected={chatId === chat.id}
color={color}
sx={{
"&.Mui-focusVisible": {
zIndex: "unset",
outlineOffset: -2,
},
}}
onClick={() => {
if (onChatSelect) {
onChatSelect(chat.id);
}
}}
>
<Typography noWrap>{chat.label}</Typography>
</ListItemButton>
</ListItem>
))}
</List>
);
}
37 changes: 37 additions & 0 deletions src/client/apps/assistant/components/chat-name.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import Box from "@mui/joy/Box";
import Input from "@mui/joy/Input";
import type { ColorPaletteProp } from "@mui/joy/styles";
import { useEffect, useState } from "react";

export function ChatName({
name,
onUpdate,
color,
}: {
name: string;
onUpdate(_label: string): void;
color: ColorPaletteProp;
}) {
const [currentName, setCurrentName] = useState(name);
useEffect(() => {
setCurrentName(name);
}, [name]);
return (
<Box sx={{ flex: 1, display: "flex", alignItems: "center" }}>
<Input
fullWidth
color={color}
variant="soft"
value={currentName}
onChange={event => {
setCurrentName(event.target.value);
}}
onBlur={() => {
if (onUpdate && currentName !== name) {
onUpdate(currentName);
}
}}
/>
</Box>
);
}
43 changes: 43 additions & 0 deletions src/client/apps/assistant/components/data-type-selector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import FindInPageIcon from "@mui/icons-material/FindInPage";
import List from "@mui/joy/List";
import ListItem from "@mui/joy/ListItem";
import ListItemButton from "@mui/joy/ListItemButton";
import ListItemContent from "@mui/joy/ListItemContent";
import ListItemDecorator from "@mui/joy/ListItemDecorator";
import Switch from "@mui/joy/Switch";
import { useTranslation } from "next-i18next";
import type { ChangeEvent as ReactChangeEvent } from "react";

import { PopupButton } from "@/client/apps/shared/components";
import type { DataTypeItem } from "@/shared/types/assistant";

export function DataTypeSelector({
dataTypes,
onChange,
}: {
dataTypes: DataTypeItem[];
onChange(id: string, checked?: boolean): void;
}) {
const { t } = useTranslation(["labels"]);
return (
<PopupButton label={t("labels:dataUsage")} icon={<FindInPageIcon />}>
<List sx={{ minWidth: 200 }}>
{dataTypes.map(dataType => (
<ListItem key={dataType.id}>
<ListItemButton tabIndex={-1} component="label" selected={dataType.active}>
<ListItemDecorator>
<Switch
checked={Boolean(dataType.active)}
onChange={(event: ReactChangeEvent<HTMLInputElement>) => {
onChange(dataType.id, event.target.checked);
}}
/>
</ListItemDecorator>
<ListItemContent>{t(`labels:${dataType.type}`)}</ListItemContent>
</ListItemButton>
</ListItem>
))}
</List>
</PopupButton>
);
}
56 changes: 56 additions & 0 deletions src/client/apps/assistant/components/message-stack.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import CloseIcon from "@mui/icons-material/Close";
import Box from "@mui/joy/Box";
import IconButton from "@mui/joy/IconButton";
import Snackbar from "@mui/joy/Snackbar";
import Typography from "@mui/joy/Typography";
import { useTranslation } from "next-i18next";

export function MessageStack({
provider,
isWarningOpen,
error,
onClose,
}: {
provider: string;
isWarningOpen: boolean;
error: string;
onClose(): void;
}) {
const { t } = useTranslation(["labels", "texts"]);
return (
<Box
sx={{
position: "absolute",
bottom: 0,
right: 0,
m: 1,
maxHeight: 500,
overflow: "auto",
}}
>
<Box sx={{ overflow: "hidden", display: "flex", flexDirection: "column", gap: 1 }}>
<Snackbar
color="danger"
variant="soft"
open={Boolean(error)}
sx={{ position: "relative", bottom: "unset", right: "unset" }}
endDecorator={
<IconButton aria-label={t("labels:close")} onClick={onClose}>
<CloseIcon />
</IconButton>
}
>
<Typography>{error}</Typography>
</Snackbar>
<Snackbar
color="warning"
variant="soft"
open={isWarningOpen}
sx={{ position: "relative", bottom: "unset", right: "unset" }}
>
<Typography>{t("texts:enterCommonAPIKey", { provider })}</Typography>
</Snackbar>
</Box>
</Box>
);
}
Loading

0 comments on commit 509e474

Please sign in to comment.