Skip to content

Commit

Permalink
Chat list sidebar (#5)
Browse files Browse the repository at this point in the history
* fix chat input growing weirdly

* add naive chat history

* expandable chat history sidebar

* list chats on sidebar works

* add sidebar wrapper to root layout
  • Loading branch information
zolinthecow authored Aug 24, 2024
1 parent 68092d6 commit 9814825
Show file tree
Hide file tree
Showing 20 changed files with 657 additions and 42 deletions.
2 changes: 0 additions & 2 deletions apps/server/src/AIService/AIService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ export default class AIService implements IAIService {
const baseURL = process.env.OPENAI_BASE_URL;
const apiKey = process.env.OPENAI_KEY ?? 'EMPTY';

if (!baseURL) throw new Error('Please set an OpenAI base URL');

// Point this to your self-hosted OpenAI compatible server
this._openaiClient = new OpenAI({
baseURL,
Expand Down
2 changes: 0 additions & 2 deletions apps/server/src/trpc/routers/chat/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,11 @@ export const create = publicProcedure
INSERT INTO "Chat" (
id,
"userID",
"previewName",
"createdAt",
"updatedAt"
) VALUES (
${chatID},
${ctx.user.id},
${''}, -- Empty string for previewName
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
)
Expand Down
2 changes: 2 additions & 0 deletions apps/server/src/trpc/routers/chat/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { router } from '../../trpc';
import { create } from './create';
import { get } from './get';
import { infiniteList } from './infiniteList';

export const chatRouter = router({
get,
create,
infiniteList,
});
45 changes: 45 additions & 0 deletions apps/server/src/trpc/routers/chat/infiniteList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { type DBChat, DBChatSchema } from '@repo/db';
import { TRPCError } from '@trpc/server';
import { sql } from 'slonik';
import { z } from 'zod';
import { publicProcedure } from '../../trpc';

export const infiniteList = publicProcedure
.input(
z.object({
limit: z.number().min(1).max(100).default(50),
cursor: z.string().optional(),
}),
)
.query(async ({ input, ctx }) => {
const { limit, cursor } = input;

const chats = await ctx.dbPool.any(sql.type(DBChatSchema)`
SELECT *
FROM "Chat"
WHERE "userID" = ${ctx.user.id}
${cursor ? sql.fragment`AND id < ${cursor}` : sql.fragment``}
ORDER BY id DESC
LIMIT ${limit + 1} -- Get an extra item as the cursor (start of next query)
`);

const chatsToReturn: DBChat[] = [...chats];

let nextCursor: string | undefined = undefined;
if (chats.length > limit) {
const nextItem = chatsToReturn.pop();
if (!nextItem) {
console.error('POP FAILED???');
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'The impossible happened ¯\\_(ツ)_/¯',
});
}
nextCursor = nextItem.id;
}

return {
items: chatsToReturn,
nextCursor,
};
});
23 changes: 20 additions & 3 deletions apps/server/src/trpc/routers/chatMessages/generateResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import { type DBChatMessage, DBChatMessageSchema } from '@repo/db';
import { type DatabasePool, sql } from 'slonik';
import { z } from 'zod';
import { publicProcedure } from '../../trpc';
import { updateDBChatMessage, upsertDBChatMessage } from './send';
import {
getPreviousChatMessages,
maybeSetChatPreview,
updateDBChatMessage,
upsertDBChatMessage,
} from './send';

export const GenerateResponseSchema = z.object({
messageID: z.string().ulid(),
Expand Down Expand Up @@ -44,14 +49,26 @@ export const generateResponse = publicProcedure
message: chatMessage,
};

await maybeSetChatPreview(
{
chatID: chatMessage.chatID,
message: chatMessage.messageContent,
},
ctx.dbPool,
);

const previousMessages = await getPreviousChatMessages(
{ chatID: chatMessage.chatID },
ctx.dbPool,
);

let messageID = chatMessage.responseMessageID;
const chatIterator = ctx.chatService.generateResponse({
userID: ctx.user.id,
chatID: chatMessage.chatID,
message: chatMessage.messageContent,
messageID,
// TODO
previousMessages: [],
previousMessages: previousMessages.slice(1),
customSystemPrompt: undefined,
});

Expand Down
2 changes: 0 additions & 2 deletions apps/server/src/trpc/routers/chatMessages/infiniteList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ export const infiniteList = publicProcedure
// some reason
const messagesToReturn: DBChatMessage[] = [...messages];

console.log('ALL MESSAGES', messages);

let nextCursor: string | undefined = undefined;
if (messages.length > limit) {
const nextItem = messagesToReturn.pop();
Expand Down
58 changes: 54 additions & 4 deletions apps/server/src/trpc/routers/chatMessages/send.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
// This procedure creates a user message and optionally streams down an assistant
// message.

import { type DBChatMessage, DBChatMessageSchema } from '@repo/db';
import {
DBChat,
type DBChatMessage,
DBChatMessageSchema,
DBChatSchema,
} from '@repo/db';
import { DateTime } from 'luxon';
import { type DatabasePool, sql } from 'slonik';
import { ulid } from 'ulid';
Expand All @@ -11,7 +16,6 @@ import { publicProcedure } from '../../trpc';
export const SendMessageSchema = z.object({
message: z.string(),
customSystemPrompt: z.string().optional(),
previousMessages: z.array(DBChatMessageSchema).optional(),
chatID: z.string(),
generateResponse: z.boolean().default(true),
});
Expand All @@ -35,7 +39,6 @@ export const send = publicProcedure
input,
ctx,
}): AsyncGenerator<SendMessageOutput> {
console.log('SENDING CHAT MESSAGE');
// First create the user's message in the DB and send it back down.
const newUserMessage = await upsertDBChatMessage(
{
Expand All @@ -53,6 +56,11 @@ export const send = publicProcedure
message: newUserMessage,
};

await maybeSetChatPreview(
{ chatID: input.chatID, message: input.message },
ctx.dbPool,
);

if (!input.generateResponse) {
// Done
return;
Expand All @@ -68,12 +76,17 @@ export const send = publicProcedure
ctx.dbPool,
);

const previousMessages = await getPreviousChatMessages(
{ chatID: input.chatID },
ctx.dbPool,
);

const chatIterator = ctx.chatService.generateResponse({
userID: ctx.user.id,
chatID: input.chatID,
message: input.message,
messageID,
previousMessages: input.previousMessages,
previousMessages: previousMessages.slice(1),
customSystemPrompt: input.customSystemPrompt,
});

Expand Down Expand Up @@ -204,3 +217,40 @@ export async function updateDBChatMessage(
throw e;
}
}

type GetPreviousChatMessagesParams = {
chatID: string;
};
export async function getPreviousChatMessages(
params: GetPreviousChatMessagesParams,
pool: DatabasePool,
): Promise<Readonly<DBChatMessage[]>> {
const { chatID } = params;

const messages = await pool.any(sql.type(DBChatMessageSchema)`
SELECT *
FROM "ChatMessage"
WHERE "chatID" = ${chatID}
ORDER BY id DESC
`);

return messages;
}

type MaybeSetChatPreviewParams = {
chatID: string;
message: string;
};
export async function maybeSetChatPreview(
params: MaybeSetChatPreviewParams,
pool: DatabasePool,
): Promise<void> {
const { chatID, message } = params;

await pool.any(sql.type(DBChatSchema)`
UPDATE "Chat"
SET "previewName" = ${message}
WHERE id = ${chatID}
AND "previewName" IS NULL;
`);
}
1 change: 1 addition & 0 deletions apps/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@milkdown/theme-nord": "^7.5.0",
"@milkdown/utils": "^7.5.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2",
"@repo/server": "workspace:*",
"@supabase/supabase-js": "^2.45.1",
"@tanstack/react-query": "^5.51.23",
Expand Down
3 changes: 3 additions & 0 deletions apps/ui/src/assets/logo-black.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 17 additions & 7 deletions apps/ui/src/components/ui/chat/message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,37 @@ const Message: React.FC<MessageProps> = ({ message }) => {
if (message.messageType === 'assistant') {
return <AssistantMessage message={message.messageContent} />;
} else {
return <UserMessage message={message.messageContent} />;
return (
<UserMessage
userID={message.userID}
message={message.messageContent}
/>
);
}
};
type SpecificMessageProps = {
type BaseMessageProps = {
message: string;
};
export const UserMessage: React.FC<SpecificMessageProps> = ({ message }) => {

type UserMessageProps = BaseMessageProps & {
userID: string;
};
export const UserMessage: React.FC<UserMessageProps> = ({
message,
userID,
}) => {
return (
<div className="w-[40vw] p-2 rounded-lg mt-2 mb-2 flex flex-row bg-accent">
<div>
<UserIcon userId="temp" className="mr-4" />
<UserIcon userID={userID} className="mr-4" />
</div>
<div className="flex-1 pt-2">
<MarkdownRenderer content={message} />
</div>
</div>
);
};
export const AssistantMessage: React.FC<SpecificMessageProps> = ({
message,
}) => {
export const AssistantMessage: React.FC<BaseMessageProps> = ({ message }) => {
return (
<div className="w-[40vw] p-3 rounded-lg mt-2 mb-2 flex flex-row bg-muted/85">
<div className="flex w-full">
Expand Down
28 changes: 28 additions & 0 deletions apps/ui/src/components/ui/tooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
import * as React from 'react';

import { cn } from '@/lib/utils';

const TooltipProvider = TooltipPrimitive.Provider;

const Tooltip = TooltipPrimitive.Root;

const TooltipTrigger = TooltipPrimitive.Trigger;

const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
'z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className,
)}
{...props}
/>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;

export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
8 changes: 4 additions & 4 deletions apps/ui/src/components/ui/userIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const PASTEL_COLORS: string[] = [
'bg-red-300',
];

const getColorFromId = (id: string): string => {
const getColorFromID = (id: string): string => {
let hash = 0;
for (let i = 0; i < id.length; i++) {
const char = id.charCodeAt(i);
Expand All @@ -29,11 +29,11 @@ const getColorFromId = (id: string): string => {
};

type Props = {
userId: string;
userID: string;
name?: string;
} & ComponentProps<'div'>;
const UserIcon: React.FC<Props> = ({ userId, name, ...props }) => {
const bgColor = useMemo(() => getColorFromId(userId), [userId]);
const UserIcon: React.FC<Props> = ({ userID, name, ...props }) => {
const bgColor = useMemo(() => getColorFromID(userID), [userID]);
let display: string | ReactNode;
if (name) {
display = name
Expand Down
4 changes: 4 additions & 0 deletions apps/ui/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ export function cn(...inputs: ClassValue[]) {
export type AsyncGeneratorYieldType<
T extends AsyncGenerator<unknown, unknown, unknown>,
> = T extends AsyncGenerator<infer Y, unknown, unknown> ? Y : never;

export function truncateString(str: string, n: number) {
return str.length > n ? `${str.slice(0, n - 1)}...` : str;
}
4 changes: 2 additions & 2 deletions apps/ui/src/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
--popover-foreground: 7 36 66;
--primary: 108 113 196;
--primary-foreground: 240 240 240;
--secondary: 107 167 209;
--secondary-foreground: 240 240 240;
--secondary: 238 232 213;
--secondary-foreground: 127 141 141;
--muted: 238 232 213;
--muted-foreground: 127 141 141;
--accent: 230 229 221;
Expand Down
Loading

0 comments on commit 9814825

Please sign in to comment.