├── .env.example ├── .eslintrc.json ├── src ├── app │ ├── favicon.ico │ ├── layout.tsx │ ├── globals.css │ └── page.tsx ├── components │ ├── Chat │ │ ├── index.ts │ │ ├── Chat.tsx │ │ └── ChatInput.tsx │ ├── FileUpload │ │ ├── index.tsx │ │ ├── FileItem.tsx │ │ └── FileUpload.tsx │ ├── Message │ │ ├── index.ts │ │ ├── Message.tsx │ │ └── MessageBalloon.tsx │ ├── Avatar │ │ ├── index.ts │ │ ├── BotAvatar.tsx │ │ ├── UserAvatar.tsx │ │ └── Avatar.tsx │ ├── Root │ │ └── index.tsx │ └── ui │ │ ├── label.tsx │ │ ├── separator.tsx │ │ ├── textarea.tsx │ │ ├── input.tsx │ │ └── button.tsx └── lib │ ├── utils.ts │ └── store.ts ├── postcss.config.js ├── Dockerfile ├── flask ├── Dockerfile ├── requirements.txt ├── app.py └── txtdata │ ├── gyber_pitch_deck.txt │ ├── gyber_white_paper_v2.txt │ └── gyber_theory.txt ├── next.config.mjs ├── .prettierrc.cjs ├── components.json ├── public ├── vercel.svg └── next.svg ├── tsconfig.json ├── docker-compose.yaml ├── README.md ├── .gitignore ├── package.json └── tailwind.config.ts /.env.example: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY= -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noel319/Nectjs_LangChain-bot/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /src/components/Chat/index.ts: -------------------------------------------------------------------------------- 1 | import Chat from './Chat' 2 | import ChatInput from './ChatInput' 3 | 4 | export default Object.assign(Chat, { 5 | Input: ChatInput, 6 | }) 7 | -------------------------------------------------------------------------------- /src/components/FileUpload/index.tsx: -------------------------------------------------------------------------------- 1 | import FileItem from './FileItem' 2 | import FileUpload from './FileUpload' 3 | 4 | export default Object.assign(FileUpload, {File: FileItem}) 5 | -------------------------------------------------------------------------------- /src/components/Message/index.ts: -------------------------------------------------------------------------------- 1 | import Message from './Message' 2 | import MessageBalloon from './MessageBalloon' 3 | 4 | export default Object.assign(Message, { 5 | Balloon: MessageBalloon, 6 | }) 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG PNPM_VERSION=8.7.1 2 | FROM node:20.6.1 3 | 4 | COPY . ./python-chat-rag 5 | WORKDIR /python-chat-rag 6 | 7 | RUN npm install -g pnpm@${PNPM_VERSION} 8 | 9 | ENTRYPOINT pnpm install && pnpm run build && pnpm start -------------------------------------------------------------------------------- /src/components/Avatar/index.ts: -------------------------------------------------------------------------------- 1 | import Avatar from './Avatar' 2 | import BotAvatar from './BotAvatar' 3 | import UserAvatar from './UserAvatar' 4 | 5 | export default Object.assign(Avatar, { 6 | User: UserAvatar, 7 | Bot: BotAvatar, 8 | }) 9 | -------------------------------------------------------------------------------- /src/components/Avatar/BotAvatar.tsx: -------------------------------------------------------------------------------- 1 | import {Bot} from 'lucide-react' 2 | 3 | import Avatar from './Avatar' 4 | 5 | const BotAvatar = () => { 6 | return ( 7 | 8 | 9 | 10 | ) 11 | } 12 | 13 | export default BotAvatar 14 | -------------------------------------------------------------------------------- /src/components/Avatar/UserAvatar.tsx: -------------------------------------------------------------------------------- 1 | import {User} from 'lucide-react' 2 | 3 | import Avatar from './Avatar' 4 | 5 | const BotAvatar = () => { 6 | return ( 7 | 8 | 9 | 10 | ) 11 | } 12 | 13 | export default BotAvatar 14 | -------------------------------------------------------------------------------- /src/components/Avatar/Avatar.tsx: -------------------------------------------------------------------------------- 1 | type AvatarProps = { 2 | children: React.ReactNode 3 | } 4 | const Avatar = ({children}: AvatarProps) => { 5 | return ( 6 |
7 | {children} 8 |
9 | ) 10 | } 11 | 12 | export default Avatar 13 | -------------------------------------------------------------------------------- /flask/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11.5 2 | 3 | EXPOSE 5328 4 | 5 | WORKDIR /flask 6 | 7 | COPY requirements.txt . 8 | 9 | RUN pip3 install --no-cache-dir --upgrade pip 10 | RUN pip3 install --no-cache-dir -r requirements.txt 11 | 12 | ENV FLASK_DEBUG=1 13 | 14 | COPY . . 15 | 16 | CMD [ "python3", "-m" , "flask", "run", "--host=0.0.0.0", "-p", "5328"] -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | experimental: { 4 | typedRoutes: true, 5 | }, 6 | rewrites: async () => { 7 | return [ 8 | { 9 | source: '/api/:path*', 10 | destination: 'http://localhost:5328/:path*', 11 | }, 12 | ] 13 | }, 14 | }; 15 | 16 | export default nextConfig; 17 | -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | bracketSpacing: false, 3 | jsxBracketSameLine: true, 4 | singleQuote: true, 5 | trailingComma: 'es5', 6 | semi: false, 7 | printWidth: 100, 8 | tabWidth: 2, 9 | useTabs: false, 10 | importOrder: ['^\\u0000', '^@?\\w', '^[^.]', '^\\.'], 11 | importOrderSeparation: true, 12 | plugins: ["@trivago/prettier-plugin-sort-imports", "prettier-plugin-tailwindcss"] 13 | }; -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/app/globals.css", 9 | "baseColor": "gray", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import 'highlight.js/styles/github-dark.css' 2 | import type {Metadata} from 'next' 3 | 4 | import Root from '@/components/Root' 5 | 6 | import './globals.css' 7 | 8 | export const metadata: Metadata = { 9 | title: 'Chat APP', 10 | description: 'Simple Chat App', 11 | } 12 | 13 | export default function RootLayout({children}: {children: React.ReactNode}) { 14 | return {children} 15 | } 16 | -------------------------------------------------------------------------------- /src/components/Message/Message.tsx: -------------------------------------------------------------------------------- 1 | import {cn} from '@/lib/utils' 2 | 3 | export type TMessage = { 4 | id: string 5 | text: string 6 | createdAt: Date 7 | creator: 'USER' | 'AI' 8 | } 9 | 10 | type MessageProps = { 11 | sender: 'USER' | 'AI' 12 | children: React.ReactNode 13 | } 14 | 15 | const Message = ({sender, children}: MessageProps) => { 16 | return ( 17 |
18 | {children} 19 |
20 | ) 21 | } 22 | 23 | export default Message 24 | -------------------------------------------------------------------------------- /src/components/FileUpload/FileItem.tsx: -------------------------------------------------------------------------------- 1 | import {X} from 'lucide-react' 2 | 3 | import {cn} from '@/lib/utils' 4 | 5 | import {Label} from '../ui/label' 6 | 7 | type Props = { 8 | name: string 9 | onRemove: () => void 10 | className?: string 11 | } 12 | const FileItem = ({name, className, onRemove}: Props) => { 13 | return ( 14 |
15 | 16 | 17 |
18 | ) 19 | } 20 | 21 | export default FileItem 22 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./src/*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | flask: 3 | container_name: flask 4 | working_dir: /flask 5 | build: 6 | context: ./flask 7 | dockerfile: Dockerfile 8 | ports: 9 | - 5328:5328 10 | environment: 11 | OPENAI_API_KEY: ${OPENAI_API_KEY} 12 | python-chat-rag: 13 | container_name: python-chat-rag 14 | depends_on: 15 | - flask 16 | build: 17 | context: . 18 | dockerfile: Dockerfile 19 | environment: 20 | OPENAI_API_KEY: ${OPENAI_API_KEY} 21 | ports: 22 | - 3000:3000 23 | entrypoint: sh -c "pnpm install && pnpm run build && pnpm run dev" 24 | working_dir: /python-chat-rag 25 | volumes: 26 | - .:/python-chat-rag 27 | -------------------------------------------------------------------------------- /src/components/Root/index.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import {Inter} from 'next/font/google' 4 | import {useEffect} from 'react' 5 | 6 | import {useMessages} from '@/lib/store' 7 | import {cn} from '@/lib/utils' 8 | 9 | const inter = Inter({subsets: ['latin']}) 10 | 11 | type Props = { 12 | children: React.ReactNode 13 | } 14 | 15 | const Root = ({children}: Props) => { 16 | useEffect(() => { 17 | useMessages.persist.rehydrate() 18 | }, []) 19 | 20 | return ( 21 | 22 | 23 | {children} 24 | 25 | 26 | ) 27 | } 28 | 29 | export default Root 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # python-chat-rag 2 | 3 | Simple chat app integrating LangChain + RAG in a Python (Flask) server created for AI Series Part V blog post 4 | 5 | ## Running 6 | 7 | `cp .env.example .env` 8 | 9 | Edit .env and add your OpenAI API key 10 | 11 | Via docker: `docker compose up` 12 | 13 | Note: if you run using Docker, make sure to update the `destination` URL in the `next.config.mjs` as the following: 14 | ``` 15 | destination: 'http://flask:5328/:path*', 16 | ``` 17 | 18 | This will make sure to use the Docker container's name instead of localhost 19 | 20 | Or you can run the apps separately: 21 | 22 | `pnpm run dev` 23 | 24 | And in another terminal: 25 | 26 | `cd flask && flask run --host=0.0.0.0 --debug -p 5328` 27 | 28 | Either way, the app will start at `localhost:3000` 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | *flask/myenv 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env 30 | .env*.local 31 | 32 | # vercel 33 | .vercel 34 | 35 | # python 36 | */venv/**/* 37 | __pycache__ 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | 43 | # temp 44 | /public/tmp/* 45 | 46 | # python 47 | */venv/**/* 48 | __pycache__ 49 | 50 | vectorstore/ 51 | */vectorstores/ 52 | flask/src/** 53 | eng.** 54 | -------------------------------------------------------------------------------- /src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import * as LabelPrimitive from '@radix-ui/react-label' 4 | import {cva, type VariantProps} from 'class-variance-authority' 5 | import * as React from 'react' 6 | 7 | import {cn} from '@/lib/utils' 8 | 9 | const labelVariants = cva( 10 | 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70' 11 | ) 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & VariantProps 16 | >(({className, ...props}, ref) => ( 17 | 18 | )) 19 | Label.displayName = LabelPrimitive.Root.displayName 20 | 21 | export {Label} 22 | -------------------------------------------------------------------------------- /src/components/ui/separator.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import * as SeparatorPrimitive from '@radix-ui/react-separator' 4 | import * as React from 'react' 5 | 6 | import {cn} from '@/lib/utils' 7 | 8 | const Separator = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({className, orientation = 'horizontal', decorative = true, ...props}, ref) => ( 12 | 23 | )) 24 | Separator.displayName = SeparatorPrimitive.Root.displayName 25 | 26 | export {Separator} 27 | -------------------------------------------------------------------------------- /src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | import {cn} from '@/lib/utils' 4 | 5 | export interface TextareaProps extends React.TextareaHTMLAttributes {} 6 | 7 | const Textarea = React.forwardRef( 8 | ({className, ...props}, ref) => { 9 | return ( 10 |