Skip to content

Commit 7be1897

Browse files
committed
LLM intigrated with chat window
1 parent 843380a commit 7be1897

File tree

13 files changed

+469
-6448
lines changed

13 files changed

+469
-6448
lines changed

package-lock.json

Lines changed: 0 additions & 6419 deletions
This file was deleted.

package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,26 @@
1717
"src/**/*.{js,jsx,ts,tsx}": "npm run lint-prettier"
1818
},
1919
"dependencies": {
20+
"@langchain/community": "^0.2.15",
2021
"@langchain/pinecone": "^0.0.7",
2122
"@pinecone-database/pinecone": "2.2.2",
22-
"@langchain/community": "^0.2.15",
23+
"@radix-ui/react-accordion": "^1.2.0",
2324
"@radix-ui/react-slot": "^1.1.0",
2425
"ai": "^2.1.34",
2526
"ai-stream-experimental": "^2.2.2",
26-
"langchain": "^0.2.8",
27-
"git-repo-parser": "^2.0.6",
2827
"class-variance-authority": "^0.7.0",
2928
"clsx": "^2.1.1",
3029
"framer-motion": "^11.2.12",
30+
"git-repo-parser": "^2.0.6",
31+
"langchain": "^0.2.8",
3132
"lucide-react": "^0.399.0",
3233
"next": "^14.1.0",
3334
"react": "^18.2.0",
3435
"react-dom": "^18.2.0",
3536
"react-icons": "^5.2.1",
3637
"react-intersection-observer": "^9.10.3",
38+
"react-markdown": "^8.0.7",
39+
"react-wrap-balancer": "^1.1.1",
3740
"tailwind-merge": "^2.3.0",
3841
"tailwindcss-animate": "^1.0.7"
3942
},

src/app/api/chat/route.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { NextRequest, NextResponse } from "next/server";
2+
import { callChain } from "@/lib/langchain";
3+
import { Message } from "ai";
4+
5+
const formatMessage = (message: Message) => {
6+
return `${message.role === "user" ? "Human" : "Assistant"}: ${
7+
message.content
8+
}`;
9+
};
10+
11+
export async function POST(req: NextRequest) {
12+
const body = await req.json();
13+
const messages: Message[] = body.messages ?? [];
14+
console.log("Messages ", messages);
15+
const formattedPreviousMessages = messages.slice(0, -1).map(formatMessage);
16+
const question = messages[messages.length - 1].content;
17+
18+
console.log("Chat history ", formattedPreviousMessages.join("\n"));
19+
20+
if (!question) {
21+
return NextResponse.json("Error: No question in the request", {
22+
status: 400,
23+
});
24+
}
25+
26+
try {
27+
const streamingTextResponse = callChain({
28+
question,
29+
chatHistory: formattedPreviousMessages.join("\n"),
30+
});
31+
32+
return streamingTextResponse;
33+
} catch (error) {
34+
console.error("Internal server error ", error);
35+
return NextResponse.json("Error: Something went wrong. Try again!", {
36+
status: 500,
37+
});
38+
}
39+
}

src/app/chat/page.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
import { Chat } from "@/components/chat/chat";
3+
4+
export default function chat() {
5+
return (
6+
<main className="relative container flex min-h-screen flex-col">
7+
<div className="flex flex-1 py-4">
8+
<div className="w-full">
9+
<Chat />
10+
</div>
11+
</div>
12+
</main>
13+
);
14+
}

src/components/chat/chat-line.tsx

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import Balancer from "react-wrap-balancer";
2+
import {
3+
Card,
4+
CardContent,
5+
CardDescription,
6+
CardFooter,
7+
CardHeader,
8+
CardTitle,
9+
} from "@/components/ui/card";
10+
import {
11+
Accordion,
12+
AccordionContent,
13+
AccordionItem,
14+
AccordionTrigger,
15+
} from "@/components/ui/accordion";
16+
import { Message } from "ai/react";
17+
import ReactMarkdown from "react-markdown";
18+
import { formattedText } from "@/lib/utils";
19+
20+
const convertNewLines = (text: string) =>
21+
text.split("\n").map((line, i) => (
22+
<span key={i}>
23+
{line}
24+
<br />
25+
</span>
26+
));
27+
28+
interface ChatLineProps extends Partial<Message> {
29+
sources: string[];
30+
}
31+
32+
export function ChatLine({
33+
role = "assistant",
34+
content,
35+
sources,
36+
}: ChatLineProps) {
37+
if (!content) {
38+
return null;
39+
}
40+
const formattedMessage = convertNewLines(content);
41+
42+
return (
43+
<div>
44+
<Card className="mb-2">
45+
<CardHeader>
46+
<CardTitle
47+
className={
48+
role != "assistant"
49+
? "text-amber-500 dark:text-amber-200"
50+
: "text-blue-500 dark:text-blue-200"
51+
}
52+
>
53+
{role == "assistant" ? "AI" : "You"}
54+
</CardTitle>
55+
</CardHeader>
56+
<CardContent className="text-sm">
57+
<Balancer>{formattedMessage}</Balancer>
58+
</CardContent>
59+
<CardFooter>
60+
<CardDescription className="w-full">
61+
{sources && sources.length ? (
62+
<Accordion type="single" collapsible className="w-full">
63+
{sources.map((source, index) => (
64+
<AccordionItem value={`source-${index}`} key={index}>
65+
<AccordionTrigger>{`Source ${index + 1}`}</AccordionTrigger>
66+
<AccordionContent>
67+
<ReactMarkdown >
68+
{formattedText(source)}
69+
</ReactMarkdown>
70+
</AccordionContent>
71+
</AccordionItem>
72+
))}
73+
</Accordion>
74+
) : (
75+
<></>
76+
)}
77+
</CardDescription>
78+
</CardFooter>
79+
</Card>
80+
</div>
81+
);
82+
}

src/components/chat/chat.tsx

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"use client";
2+
3+
import { scrollToBottom, initialMessages, getSources } from "@/lib/utils";
4+
import { ChatLine } from "./chat-line";
5+
import { useChat, Message } from "ai-stream-experimental/react";
6+
import { Input } from "@/components/ui/input";
7+
import { Button } from "@/components/ui/button";
8+
import { Spinner } from "@/components/ui/spinner";
9+
import { useEffect, useRef } from "react";
10+
11+
export function Chat() {
12+
const containerRef = useRef<HTMLDivElement | null>(null);
13+
const { messages, input, handleInputChange, handleSubmit, isLoading, data } =
14+
useChat({
15+
initialMessages: initialMessages.map((message) => ({
16+
...message,
17+
role: message.role as "function" | "system" | "user" | "assistant",
18+
})),
19+
});
20+
21+
useEffect(() => {
22+
setTimeout(() => scrollToBottom(containerRef), 100);
23+
}, [messages]);
24+
25+
return (
26+
<div className="rounded-2xl border h-[75vh] flex flex-col justify-between">
27+
<div className="p-6 overflow-auto" ref={containerRef}>
28+
{messages.map(({ id, role, content }: Message, index) => (
29+
<ChatLine
30+
key={id}
31+
role={role}
32+
content={content}
33+
// Start from the third message of the assistant
34+
sources={data?.length ? getSources(data, role, index) : []}
35+
/>
36+
))}
37+
</div>
38+
39+
<form onSubmit={handleSubmit} className="p-4 flex clear-both">
40+
<Input
41+
value={input}
42+
placeholder={"Type to chat with AI..."}
43+
onChange={handleInputChange}
44+
className="mr-2"
45+
/>
46+
47+
<Button type="submit" className="w-24">
48+
{isLoading ? <Spinner /> : "Ask"}
49+
</Button>
50+
</form>
51+
</div>
52+
);
53+
}

src/components/ui/accordion.tsx

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"use client"
2+
3+
import * as React from "react"
4+
import * as AccordionPrimitive from "@radix-ui/react-accordion"
5+
import { ChevronDown } from "lucide-react"
6+
7+
import { cn } from "@/lib/utils"
8+
9+
const Accordion = AccordionPrimitive.Root
10+
11+
const AccordionItem = React.forwardRef<
12+
React.ElementRef<typeof AccordionPrimitive.Item>,
13+
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
14+
>(({ className, ...props }, ref) => (
15+
<AccordionPrimitive.Item
16+
ref={ref}
17+
className={cn("border-b", className)}
18+
{...props}
19+
/>
20+
))
21+
AccordionItem.displayName = "AccordionItem"
22+
23+
const AccordionTrigger = React.forwardRef<
24+
React.ElementRef<typeof AccordionPrimitive.Trigger>,
25+
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
26+
>(({ className, children, ...props }, ref) => (
27+
<AccordionPrimitive.Header className="flex">
28+
<AccordionPrimitive.Trigger
29+
ref={ref}
30+
className={cn(
31+
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
32+
className
33+
)}
34+
{...props}
35+
>
36+
{children}
37+
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
38+
</AccordionPrimitive.Trigger>
39+
</AccordionPrimitive.Header>
40+
))
41+
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
42+
43+
const AccordionContent = React.forwardRef<
44+
React.ElementRef<typeof AccordionPrimitive.Content>,
45+
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
46+
>(({ className, children, ...props }, ref) => (
47+
<AccordionPrimitive.Content
48+
ref={ref}
49+
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
50+
{...props}
51+
>
52+
<div className={cn("pb-4 pt-0", className)}>{children}</div>
53+
</AccordionPrimitive.Content>
54+
))
55+
56+
AccordionContent.displayName = AccordionPrimitive.Content.displayName
57+
58+
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }

src/components/ui/button.tsx

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,56 @@
1-
import * as React from 'react';
2-
import { Slot } from '@radix-ui/react-slot';
3-
import { cva, type VariantProps } from 'class-variance-authority';
1+
import * as React from "react"
2+
import { Slot } from "@radix-ui/react-slot"
3+
import { cva, type VariantProps } from "class-variance-authority"
44

5-
import { cn } from '@/lib/utils';
5+
import { cn } from "@/lib/utils"
66

77
const buttonVariants = cva(
8-
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-gray-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 dark:ring-offset-gray-950 dark:focus-visible:ring-gray-300',
8+
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-gray-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 dark:ring-offset-gray-950 dark:focus-visible:ring-gray-300",
99
{
1010
variants: {
1111
variant: {
12-
default:
13-
'bg-gray-900 text-gray-50 hover:bg-gray-900/90 dark:bg-gray-50 dark:text-gray-900 dark:hover:bg-gray-50/90',
12+
default: "bg-gray-900 text-gray-50 hover:bg-gray-900/90 dark:bg-gray-50 dark:text-gray-900 dark:hover:bg-gray-50/90",
1413
destructive:
15-
'bg-red-500 text-gray-50 hover:bg-red-500/90 dark:bg-red-900 dark:text-gray-50 dark:hover:bg-red-900/90',
14+
"bg-red-500 text-gray-50 hover:bg-red-500/90 dark:bg-red-900 dark:text-gray-50 dark:hover:bg-red-900/90",
1615
outline:
17-
'border border-gray-200 bg-white hover:bg-gray-100 hover:text-gray-900 dark:border-gray-800 dark:bg-gray-950 dark:hover:bg-gray-800 dark:hover:text-gray-50',
16+
"border border-gray-200 bg-white hover:bg-gray-100 hover:text-gray-900 dark:border-gray-800 dark:bg-gray-950 dark:hover:bg-gray-800 dark:hover:text-gray-50",
1817
secondary:
19-
'bg-gray-100 text-gray-900 hover:bg-gray-100/80 dark:bg-gray-800 dark:text-gray-50 dark:hover:bg-gray-800/80',
20-
ghost:
21-
'hover:bg-gray-100 hover:text-gray-900 dark:hover:bg-gray-800 dark:hover:text-gray-50',
22-
link: 'text-gray-900 underline-offset-4 hover:underline dark:text-gray-50',
18+
"bg-gray-100 text-gray-900 hover:bg-gray-100/80 dark:bg-gray-800 dark:text-gray-50 dark:hover:bg-gray-800/80",
19+
ghost: "hover:bg-gray-100 hover:text-gray-900 dark:hover:bg-gray-800 dark:hover:text-gray-50",
20+
link: "text-gray-900 underline-offset-4 hover:underline dark:text-gray-50",
2321
},
2422
size: {
25-
default: 'h-10 px-4 py-2',
26-
sm: 'h-9 rounded-md px-3',
27-
lg: 'h-11 rounded-md px-8',
28-
icon: 'h-10 w-10',
23+
default: "h-10 px-4 py-2",
24+
sm: "h-9 rounded-md px-3",
25+
lg: "h-11 rounded-md px-8",
26+
icon: "h-10 w-10",
2927
},
3028
},
3129
defaultVariants: {
32-
variant: 'default',
33-
size: 'default',
30+
variant: "default",
31+
size: "default",
3432
},
3533
}
36-
);
34+
)
3735

3836
export interface ButtonProps
3937
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
4038
VariantProps<typeof buttonVariants> {
41-
asChild?: boolean;
39+
asChild?: boolean
4240
}
4341

4442
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
4543
({ className, variant, size, asChild = false, ...props }, ref) => {
46-
const Comp = asChild ? Slot : 'button';
44+
const Comp = asChild ? Slot : "button"
4745
return (
4846
<Comp
4947
className={cn(buttonVariants({ variant, size, className }))}
5048
ref={ref}
5149
{...props}
5250
/>
53-
);
51+
)
5452
}
55-
);
56-
Button.displayName = 'Button';
53+
)
54+
Button.displayName = "Button"
5755

58-
export { Button, buttonVariants };
56+
export { Button, buttonVariants }

0 commit comments

Comments
 (0)