├── .eslintrc.json
├── .github
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── pull_request_template.md
└── workflows
│ └── ci.yml
├── .gitignore
├── LICENSE
├── README.md
├── app
├── app
│ ├── chatbots
│ │ ├── [chatbotId]
│ │ │ ├── client-page.js
│ │ │ ├── loading.js
│ │ │ └── page.js
│ │ ├── client-page.js
│ │ ├── loading.js
│ │ └── page.js
│ ├── client-page.js
│ ├── container.js
│ ├── datasources
│ │ ├── client-page.js
│ │ ├── loading.js
│ │ └── page.js
│ ├── layout.js
│ ├── loading.js
│ ├── page.js
│ ├── prompt-templates
│ │ ├── client-page.js
│ │ ├── loading.js
│ │ └── page.js
│ └── sidebar.js
├── favicon.ico
├── landing-page.js
├── layout.js
├── loading.js
├── page.js
├── providers.js
└── user
│ └── login
│ ├── login.js
│ └── page.js
├── components
├── chat
│ ├── index.js
│ ├── input.js
│ ├── message.js
│ ├── output.js
│ └── prompt-templates.js
├── code-block.js
├── datasources
│ └── csv.js
├── file-picker.js
├── page-header.js
└── user-menu.js
├── env.example
├── jsconfig.json
├── lib
├── api-docs.js
├── api.js
├── datasources.js
├── markdown.js
├── metal.js
├── prisma.js
├── s3.js
├── sidebar.js
├── theme.js
├── upload-file.js
└── upload-url.js
├── next.config.js
├── package-lock.json
├── package.json
├── pages
└── api
│ ├── auth
│ └── [...nextauth].js
│ ├── chatbots
│ ├── [chatbotId]
│ │ ├── index.js
│ │ └── messages
│ │ │ └── index.js
│ └── index.js
│ ├── datasources
│ ├── [datasourceId]
│ │ └── index.js
│ ├── index.js
│ └── ingest.js
│ └── prompt-templates
│ ├── [promptId]
│ └── index.js
│ └── index.js
├── prisma
├── migrations
│ ├── 20230327164641_user_defaults
│ │ └── migration.sql
│ ├── 20230327201911_prompt_templates
│ │ └── migration.sql
│ ├── 20230329205257_chatbots
│ │ └── migration.sql
│ ├── 20230406080854_chatbot_message
│ │ └── migration.sql
│ ├── 20230409152256_chatbot_prompt_template
│ │ └── migration.sql
│ ├── 20230428114658_
│ │ └── migration.sql
│ └── migration_lock.toml
└── schema.prisma
└── public
├── chatbot.png
├── next.svg
├── thirteen.svg
├── user.png
└── vercel.svg
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to LangChain UI
2 |
3 | Thanks for being interested in contributing to LangChain UI ❤️.
4 | We are extremely open to any and all contributions you might be intersted in making.
5 | To contribute to this project, please follow a ["fork and pull request"](https://docs.github.com/en/get-started/quickstart/contributing-to-projects) workflow.
6 | Please do not try to push directly to this repo unless you are maintainer.
7 |
8 | ## Guidelines
9 |
10 | ### Issues
11 |
12 | The [issues](https://github.com/homanp/langchain-ui/issues) contains all current bugs, improvements, and feature requests.
13 | Please use the correspondnig label when creating an issue.
14 |
15 | The current set of labels are:
16 |
17 | - `api` for LangChain related API integrations
18 | - `infrastructure` for general issues regarding the infra of LangChain UI
19 | - `user interface` for UI/UX related issues.
20 |
21 | ### Getting Help
22 |
23 | Contact a maintainer of LangChain UI with any questions or help you might need.
24 |
25 | ### Release process
26 |
27 | LangChain UI tries to follow the same ad hoc realease proccess as [Langchain](https://github.com/hwchase17/langchain).
28 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 🐞 Bug Report
3 | about: Create a report to help us improve
4 | title: ""
5 | labels: bug
6 | assignees: ""
7 | ---
8 |
9 | **Describe the bug**
10 | A clear and concise description of what the bug is.
11 |
12 | **To Reproduce**
13 | Steps to reproduce the behavior:
14 |
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 🚀 Feature request
3 | about: Suggest an idea for improving Langchain UI
4 | title:
5 | labels: enhancement
6 | assignees:
7 | ---
8 |
9 | ### Is your proposal related to a problem?
10 |
11 |
15 |
16 | (Write your answer here.)
17 |
18 | ### Describe the solution you'd like
19 |
20 |
23 |
24 | (Describe your proposed solution here.)
25 |
26 | ### Describe alternatives you've considered
27 |
28 |
31 |
32 | (Write your answer here.)
33 |
34 | ### Additional context
35 |
36 |
40 |
41 | (Write your answer here.)
42 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## Summary
2 |
3 |
4 |
5 | Fixes
6 |
7 | Depends on
8 |
9 | ## Test plan
10 |
11 |
12 |
13 | -
14 |
15 | ## Screenshots
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on:
3 | - push
4 | - pull_request
5 | jobs:
6 | test:
7 | name: Node.js ${{ matrix.node-version }}
8 | runs-on: ubuntu-latest
9 | strategy:
10 | fail-fast: false
11 | matrix:
12 | node-version:
13 | - 18
14 | - 16
15 | steps:
16 | - uses: actions/checkout@v3
17 | - uses: actions/setup-node@v3
18 | with:
19 | node-version: ${{ matrix.node-version }}
20 | - run: npm install
21 | - run: npm test
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 | .pnpm-debug.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 | .env
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) Ismail Pelaseyed
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## 🚨🚨 This repository is unmaintained 🚨🚨
4 | Note that this repository is unmaintained. We've started working on another approach which gives more granular access to LLM-powered Agents. Please check out out https://github.com/homanp/superagent. We appreciate all the feedback and contributions 🙏🙏🙏
5 |
6 | # 🧬 LangChain UI
7 | The no-code open source chat-ai toolkit built on top of [LangChain](https://github.com/hwchase17/langchain).
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | ## About the project
19 |
20 | LangChain UI enables anyone to create and host chatbots using a no-code type of inteface.
21 |
22 | Features:
23 |
24 | 👉 Create custom chatGPT like Chatbot.
25 |
26 | 👉 Give context to the chatbot using external datasources, chatGPT plugins and prompts.
27 |
28 | 👉 Dedicated API endpoint for each Chatbot.
29 |
30 | 👉 Bring your own DB
31 |
32 | 👉 Bring your own Auth provider (defaults to Github)
33 |
34 | 👉 Usage quoutas
35 |
36 | 👉 Embed Chatbots to any site or application
37 |
38 | 👉 Chatbot themes
39 |
40 | ... and more
41 |
42 | ## Roadmap
43 |
44 | - [x] Bring your own db
45 | - [x] Bring your own Auth provider
46 | - [x] Chatbots
47 | - [x] Prompt templates
48 | - [ ] API endpoints to chatbot
49 | - [ ] External datasources
50 | - [ ] chatGPT plugins
51 | - [ ] Chatbots themes
52 | - [ ] Chatbot embedding
53 |
54 | ## Stack
55 |
56 | - [Next.js](https://nextjs.org/?ref=langchain-ui)
57 | - [Chakra UI](https://chakra-ui.com/?ref=langchain-ui)
58 | - [Prisma](https://prisma.io/?ref=langchain-ui)
59 | - [NextAuth](https://next-auth.js.org/?ref=langchain-ui)
60 |
61 | LangChain UI utilizes NextJS 13 `appDir`. Read more about it [here](https://nextjs.org/blog/next-13#new-app-directory-beta)
62 |
63 | ## Getting started
64 |
65 | ### Langchain UI API
66 |
67 | We have migrated all agent functionality from LangChain Typescript to LangChain Python. Thus you will need to run the [Langchain UI API](https://github.com/homanp/langchain-ui-api) in order to interact with the chatbot. In the future when the TS package is on par with the Python package we will migrate to only using Javascript.
68 |
69 | ### Installation
70 |
71 | 1. Setup the [Langchain UI API](https://github.com/homanp/langchain-ui-api)
72 |
73 | 1. Clone the repo into a public GitHub repository (or fork https://github.com/homanp/langchain-ui/fork). If you plan to distribute the code, keep the source code public.
74 |
75 | ```sh
76 | git clone https://github.com/homanp/langchain-ui.git
77 | ```
78 |
79 | 1. Go to the project folder
80 |
81 | ```sh
82 | cd langchain-ui
83 | ```
84 |
85 | 1. Install packages with npm
86 |
87 | ```sh
88 | npm install
89 | ```
90 |
91 | 1. Set up your .env file
92 |
93 | - Duplicate `.env.example` to `.env`
94 |
95 | 1. Run the project
96 |
97 | ```sh
98 | npm run dev
99 | ```
100 |
101 | 1. Run the linter
102 |
103 | ```sh
104 | npm run lint
105 | ```
106 |
107 | 1. Build the project
108 |
109 | ```sh
110 | npm run build
111 | ```
112 |
113 | ## Contributions
114 |
115 | Our mission is to make it easy for anyone to create and run LLM apps in the cloud. We are super happy for any contributions you would like to make. Create new features, fix bugs or improve on infra.
116 |
117 | You can read more on how to contribute [here](https://github.com/homanp/langchain-ui/blob/main/.github/CONTRIBUTING.md).
118 |
--------------------------------------------------------------------------------
/app/app/chatbots/[chatbotId]/client-page.js:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useState } from "react";
3 | import PropTypes from "prop-types";
4 | import {
5 | Center,
6 | GridItem,
7 | HStack,
8 | SimpleGrid,
9 | Spinner,
10 | Stack,
11 | StackDivider,
12 | Tag,
13 | Text,
14 | } from "@chakra-ui/react";
15 | import { useAsync } from "react-use";
16 | import { getChatbotById } from "@/lib/api";
17 | import Chat from "@/components/chat";
18 | import CodeBlock from "@/components/code-block";
19 |
20 | import { API_DOCS } from "@/lib/api-docs";
21 |
22 | export default function ChatbotClientPage({ chatbotId }) {
23 | const [chatbot, setChatbot] = useState();
24 | const { loading: isLoading } = useAsync(async () => {
25 | const { data } = await getChatbotById(chatbotId);
26 |
27 | setChatbot(data);
28 | }, [chatbotId, getChatbotById]);
29 |
30 | return (
31 |
32 | {isLoading && (
33 |
34 |
35 |
36 | )}
37 | {!isLoading && (
38 |
39 |
40 |
41 |
42 |
43 | } spacing={0}>
44 |
45 | {chatbot.name}
46 |
47 |
48 |
49 |
50 | Datasources
51 |
52 | {chatbot.datasource ? (
53 |
54 |
55 | {chatbot.datasource.name}{" "}
56 |
57 | {chatbot.datasource.type}
58 |
59 |
60 |
61 | ) : (
62 | No datasource selected...
63 | )}
64 |
65 |
66 |
67 | API
68 |
69 |
70 | Interact with your chatbot using the following API call
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | )}
79 |
80 | );
81 | }
82 |
83 | ChatbotClientPage.propTypes = {
84 | chatbotId: PropTypes.string,
85 | };
86 |
--------------------------------------------------------------------------------
/app/app/chatbots/[chatbotId]/loading.js:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { Center, Spinner } from "@chakra-ui/react";
3 |
4 | export default function Loading() {
5 | return (
6 |
7 |
8 |
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/app/app/chatbots/[chatbotId]/page.js:
--------------------------------------------------------------------------------
1 | import ChatbotClientPage from "./client-page";
2 |
3 | export const metadata = {
4 | title: "Chatbot",
5 | description: "Manage your chatbot",
6 | };
7 |
8 | export default async function ChatbotsPage({ params }) {
9 | const { chatbotId } = params;
10 |
11 | return ;
12 | }
13 |
--------------------------------------------------------------------------------
/app/app/chatbots/client-page.js:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useCallback, useState } from "react";
3 | import { useRouter } from "next/navigation";
4 | import Link from "next/link";
5 | import {
6 | Button,
7 | Center,
8 | Container,
9 | FormControl,
10 | HStack,
11 | Icon,
12 | IconButton,
13 | Input,
14 | Select,
15 | Spinner,
16 | Stack,
17 | Table,
18 | TableContainer,
19 | Tbody,
20 | Text,
21 | Tr,
22 | Td,
23 | useColorModeValue,
24 | } from "@chakra-ui/react";
25 | import { TbPlus, TbTrashX } from "react-icons/tb";
26 | import { useAsync } from "react-use";
27 | import { useForm } from "react-hook-form";
28 | import PageHeader from "@/components/page-header";
29 | import { useSidebar } from "@/lib/sidebar";
30 | import {
31 | createChatbot,
32 | getChatbots,
33 | getDatasources,
34 | getPrompTemplates,
35 | removeChatbotById,
36 | } from "@/lib/api";
37 |
38 | export default function ChatbotsClientPage() {
39 | const [showForm, setShowForm] = useState();
40 | const [chatbots, setChatbots] = useState([]);
41 | const [promptTemplates, setPromptTemplates] = useState([]);
42 | const [datasources, setDatasources] = useState([]);
43 | const buttonColorScheme = useColorModeValue("blackAlpha", "whiteAlpha");
44 | const buttonBackgroundColor = useColorModeValue("black", "white");
45 | const borderBottomColor = useColorModeValue("gray.50", "#333");
46 | const router = useRouter();
47 | const menu = useSidebar();
48 |
49 | const {
50 | formState: { errors, isSubmitting },
51 | handleSubmit,
52 | register,
53 | } = useForm();
54 |
55 | const { loading: isLoading } = useAsync(async () => {
56 | const [
57 | { data: chatbots },
58 | { data: promptTemplates },
59 | { data: datasources },
60 | ] = await Promise.all([
61 | getChatbots(),
62 | getPrompTemplates(),
63 | getDatasources(),
64 | ]);
65 |
66 | setChatbots(chatbots);
67 | setPromptTemplates(promptTemplates);
68 | setDatasources(datasources);
69 |
70 | return;
71 | }, [getChatbots, getPrompTemplates, setChatbots]);
72 |
73 | const handleRemoveChatbot = useCallback(async (chatbotId) => {
74 | await removeChatbotById(chatbotId);
75 |
76 | setChatbots((prev) => prev.filter(({ id }) => id !== chatbotId));
77 | }, []);
78 |
79 | const onSubmit = useCallback(
80 | async (values) => {
81 | const { name, promptTemplateId, datasourceId } = values;
82 | const { data: chatbot } = await createChatbot({
83 | name,
84 | promptTemplateId: parseInt(promptTemplateId),
85 | datasourceId: parseInt(datasourceId),
86 | });
87 |
88 | router.push(`/app/chatbots/${chatbot.id}`);
89 | },
90 | [router]
91 | );
92 |
93 | return (
94 |
95 | id === "chatbots").icon}
97 | title="Chatbots"
98 | >
99 |
100 | }
102 | colorScheme={buttonColorScheme}
103 | backgroundColor={buttonBackgroundColor}
104 | size="sm"
105 | onClick={() => setShowForm(true)}
106 | isLoading={isSubmitting}
107 | loadingText="Creating..."
108 | >
109 | New chatbot
110 |
111 |
112 |
113 | {isLoading && (
114 |
115 |
116 |
117 | )}
118 | {!isLoading && !showForm && (
119 |
120 |
121 |
122 | {chatbots.map(({ id, name }) => (
123 |
124 |
129 | {name}
130 | |
131 |
132 | }
135 | variant="ghost"
136 | onClick={() => handleRemoveChatbot(id)}
137 | />
138 | |
139 |
140 | ))}
141 |
142 |
143 |
144 | )}
145 | {showForm && (
146 |
147 |
148 |
149 |
150 | id === "chatbots").icon}
153 | />
154 | New chatbot
155 |
156 | Create a new chatbot
157 |
158 |
159 |
160 |
161 |
166 |
167 |
168 |
179 |
180 |
181 |
192 |
193 |
194 |
195 |
198 |
207 |
208 |
209 |
210 |
211 | )}
212 |
213 | );
214 | }
215 |
--------------------------------------------------------------------------------
/app/app/chatbots/loading.js:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { Center, Spinner } from "@chakra-ui/react";
3 |
4 | export default function Loading() {
5 | return (
6 |
7 |
8 |
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/app/app/chatbots/page.js:
--------------------------------------------------------------------------------
1 | import ChatbotsClientPage from "./client-page";
2 |
3 | export const metadata = {
4 | title: "Chatbots",
5 | description: "Manage your chatbots",
6 | };
7 |
8 | export default function ChatbotsPage() {
9 | return ;
10 | }
11 |
--------------------------------------------------------------------------------
/app/app/client-page.js:
--------------------------------------------------------------------------------
1 | "use client";
2 | import {
3 | Card,
4 | CardBody,
5 | CardHeader,
6 | SimpleGrid,
7 | Stack,
8 | Text,
9 | } from "@chakra-ui/react";
10 | import PageHeader from "@/components/page-header";
11 | import { useSidebar } from "@/lib/sidebar";
12 |
13 | export default function AppClientPage() {
14 | const menu = useSidebar();
15 |
16 | return (
17 |
18 | id === "home").icon}
20 | title="Home"
21 | />
22 |
23 |
24 | Connected datasources
25 |
26 | -
27 |
28 |
29 |
30 | Active chatbots
31 |
32 | -
33 |
34 |
35 |
36 | Used tokens
37 |
38 | -
39 |
40 |
41 |
42 | Estimated costs
43 |
44 | -
45 |
46 |
47 |
48 |
49 | );
50 | }
51 |
--------------------------------------------------------------------------------
/app/app/container.js:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React from "react";
3 | import { Flex } from "@chakra-ui/react";
4 | import Sidebar from "./sidebar";
5 |
6 | export default function AppContainer({ children }) {
7 | return (
8 |
9 |
10 | {children}
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/app/app/datasources/client-page.js:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useCallback, useState } from "react";
3 | import {
4 | Box,
5 | Button,
6 | Center,
7 | Container,
8 | FormControl,
9 | FormErrorMessage,
10 | HStack,
11 | Icon,
12 | IconButton,
13 | Input,
14 | Spinner,
15 | Stack,
16 | Table,
17 | TableContainer,
18 | Tbody,
19 | Text,
20 | Tr,
21 | Td,
22 | useColorModeValue,
23 | Select,
24 | Tag,
25 | } from "@chakra-ui/react";
26 | import { TbPlus, TbTrashX } from "react-icons/tb";
27 | import { useAsync } from "react-use";
28 | import { useForm } from "react-hook-form";
29 | import PageHeader from "@/components/page-header";
30 | import { useSidebar } from "@/lib/sidebar";
31 | import {
32 | createDatasource,
33 | getDatasources,
34 | removeDatasourceById,
35 | } from "@/lib/api";
36 | import { DATASOURCE_TYPES } from "@/lib/datasources";
37 | import { uploadFile } from "@/lib/upload-file.js";
38 | import { getUploadUrl } from "@/lib/upload-url.js";
39 |
40 | export default function DatasourcesClientPage() {
41 | const buttonColorScheme = useColorModeValue("blackAlpha", "whiteAlpha");
42 | const buttonBackgroundColor = useColorModeValue("black", "white");
43 | const borderBottomColor = useColorModeValue("gray.50", "#333");
44 | const menu = useSidebar();
45 | const [datasources, setDatasources] = useState([]);
46 |
47 | const { loading: isLoading } = useAsync(async () => {
48 | const { data } = await getDatasources();
49 | setDatasources(data);
50 |
51 | return data;
52 | }, [getDatasources, setDatasources]);
53 | const [showForm, setShowForm] = useState(
54 | !isLoading && datasources.length === 0
55 | );
56 |
57 | const {
58 | formState: { isSubmitting, errors },
59 | handleSubmit,
60 | register,
61 | reset,
62 | watch,
63 | } = useForm({ values: { type: "csv" } });
64 |
65 | const files = watch("file");
66 | const type = watch("type");
67 | const validate = useCallback((value) => value.length > 0, []);
68 |
69 | const onSubmit = useCallback(
70 | async ({ name, type }) => {
71 | const fileType = files[0].type;
72 | const uploadUrl = await getUploadUrl({ type: fileType });
73 | const s3Url = `${uploadUrl.url}/${uploadUrl.fields.key}`;
74 |
75 | await uploadFile(files[0], uploadUrl);
76 |
77 | const { data: datasource } = await createDatasource({
78 | url: s3Url,
79 | name: name,
80 | type: type,
81 | });
82 |
83 | setDatasources((prev) => [datasource, ...prev]);
84 | setShowForm();
85 | reset();
86 | },
87 | [files, reset, setDatasources]
88 | );
89 |
90 | const handleRemoveDatasource = useCallback(async (datasourceId) => {
91 | await removeDatasourceById(datasourceId);
92 |
93 | setDatasources((prev) => prev.filter(({ id }) => id !== datasourceId));
94 | }, []);
95 |
96 | return (
97 |
98 | id === "datasources").icon}
100 | title="Datasources"
101 | >
102 |
103 | }
105 | colorScheme={buttonColorScheme}
106 | backgroundColor={buttonBackgroundColor}
107 | size="sm"
108 | onClick={() => setShowForm(true)}
109 | >
110 | New datasource
111 |
112 |
113 |
114 | {isLoading && (
115 |
116 |
117 |
118 | )}
119 | {!isLoading && !showForm && (
120 |
121 |
122 |
123 | {datasources.map(({ id, name, type }) => (
124 |
125 |
130 |
131 | {name}{" "}
132 |
133 | {type}
134 |
135 |
136 | |
137 |
138 | }
141 | variant="ghost"
142 | onClick={() => handleRemoveDatasource(id)}
143 | />
144 | |
145 |
146 | ))}
147 |
148 |
149 |
150 | )}
151 | {showForm && (
152 |
153 |
154 |
155 |
156 | id === "datasources").icon}
159 | />
160 | Datasources
161 |
162 | Create a datasource that you can use in your chat app.
163 |
164 |
165 |
166 |
167 |
172 | {errors?.file && (
173 | Please choose a name
174 | )}
175 |
176 |
177 |
184 | {errors?.file && (
185 |
186 | Please select a datasource
187 |
188 | )}
189 |
190 |
191 | id === type).component
194 | }
195 | files={files}
196 | register={register}
197 | validate={validate}
198 | />
199 | {errors?.file && (
200 | Please select a file
201 | )}
202 |
203 |
204 |
205 |
208 |
217 |
218 |
219 |
220 |
221 | )}
222 |
223 | );
224 | }
225 |
--------------------------------------------------------------------------------
/app/app/datasources/loading.js:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { Center, Spinner } from "@chakra-ui/react";
3 |
4 | export default function Loading() {
5 | return (
6 |
7 |
8 |
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/app/app/datasources/page.js:
--------------------------------------------------------------------------------
1 | import DatasourcesClientPage from "./client-page";
2 |
3 | export const metadata = {
4 | title: "Datasources",
5 | description: "Manage your datasources",
6 | };
7 |
8 | export default function PromptTemplatesPage() {
9 | return ;
10 | }
11 |
--------------------------------------------------------------------------------
/app/app/layout.js:
--------------------------------------------------------------------------------
1 | import { getServerSession } from "next-auth/next";
2 | import { redirect } from "next/navigation";
3 | import { authOptions } from "pages/api/auth/[...nextauth]";
4 | import AppContainer from "./container";
5 |
6 | export const metadata = {
7 | title: "Home",
8 | description: "LangChain UI app home page",
9 | };
10 |
11 | export default async function AppLayout({ children }) {
12 | const session = await getServerSession(authOptions);
13 |
14 | if (!session) {
15 | redirect("/");
16 | }
17 |
18 | return {children};
19 | }
20 |
--------------------------------------------------------------------------------
/app/app/loading.js:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { Center, Spinner } from "@chakra-ui/react";
3 |
4 | export default function Loading() {
5 | return (
6 |
7 |
8 |
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/app/app/page.js:
--------------------------------------------------------------------------------
1 | import { Inter } from "next/font/google";
2 | import AppClientPage from "./client-page";
3 |
4 | const inter = Inter({ subsets: ["latin"] });
5 |
6 | export default function Home() {
7 | return ;
8 | }
9 |
--------------------------------------------------------------------------------
/app/app/prompt-templates/client-page.js:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useCallback, useState } from "react";
3 | import {
4 | Box,
5 | Button,
6 | Center,
7 | Container,
8 | FormControl,
9 | FormHelperText,
10 | HStack,
11 | Icon,
12 | IconButton,
13 | Input,
14 | Spinner,
15 | Stack,
16 | Table,
17 | TableContainer,
18 | Tag,
19 | Tbody,
20 | Textarea,
21 | Text,
22 | Tr,
23 | Td,
24 | useColorModeValue,
25 | } from "@chakra-ui/react";
26 | import { TbPlus, TbTrashX } from "react-icons/tb";
27 | import { useAsync } from "react-use";
28 | import { useForm } from "react-hook-form";
29 | import PageHeader from "@/components/page-header";
30 | import { useSidebar } from "@/lib/sidebar";
31 | import {
32 | createPromptTemplate,
33 | getPrompTemplates,
34 | getPromptVariables,
35 | removePromptTemplateById,
36 | } from "@/lib/api";
37 |
38 | export default function PromptTemplatesClientPage() {
39 | const buttonColorScheme = useColorModeValue("blackAlpha", "whiteAlpha");
40 | const buttonBackgroundColor = useColorModeValue("black", "white");
41 | const borderBottomColor = useColorModeValue("gray.50", "#333");
42 | const menu = useSidebar();
43 | const [promptTemplates, setPromptTemplates] = useState([]);
44 |
45 | const { loading: isLoading } = useAsync(async () => {
46 | const { data } = await getPrompTemplates();
47 | setPromptTemplates(data);
48 |
49 | return data;
50 | }, [getPrompTemplates, setPromptTemplates]);
51 | const [showForm, setShowForm] = useState(
52 | !isLoading && promptTemplates.length === 0
53 | );
54 |
55 | const {
56 | formState: { isSubmitting, errors },
57 | handleSubmit,
58 | register,
59 | reset,
60 | watch,
61 | } = useForm();
62 |
63 | const prompt = watch("prompt");
64 |
65 | const onSubmit = useCallback(
66 | async ({ name, prompt }) => {
67 | const payload = {
68 | name,
69 | prompt,
70 | inputs: getPromptVariables(prompt),
71 | };
72 |
73 | const { data: promptTemplate } = await createPromptTemplate(payload);
74 |
75 | setPromptTemplates((prev) => [promptTemplate, ...prev]);
76 | setShowForm();
77 | reset();
78 | },
79 | [reset, setPromptTemplates]
80 | );
81 |
82 | const handleRemovePromptTemplate = useCallback(async (promptTemplateId) => {
83 | await removePromptTemplateById(promptTemplateId);
84 |
85 | setPromptTemplates((prev) =>
86 | prev.filter(({ id }) => id !== promptTemplateId)
87 | );
88 | }, []);
89 |
90 | return (
91 |
92 | id === "prompt_templates").icon}
94 | title="Prompt templates"
95 | >
96 |
97 | }
99 | colorScheme={buttonColorScheme}
100 | backgroundColor={buttonBackgroundColor}
101 | size="sm"
102 | onClick={() => setShowForm(true)}
103 | >
104 | New template
105 |
106 |
107 |
108 | {isLoading && (
109 |
110 |
111 |
112 | )}
113 | {!isLoading && !showForm && (
114 |
115 |
116 |
117 | {promptTemplates.map(({ id, name }) => (
118 |
119 |
124 | {name}
125 | |
126 |
127 | }
130 | variant="ghost"
131 | onClick={() => handleRemovePromptTemplate(id)}
132 | />
133 | |
134 |
135 | ))}
136 |
137 |
138 |
139 | )}
140 | {showForm && (
141 |
142 |
143 |
144 |
145 | id === "prompt_templates").icon}
148 | />
149 | New prompt template
150 |
151 | Create a prompt template to use in your chat apps
152 |
153 |
154 |
155 |
156 |
161 |
162 |
163 |
169 |
175 |
176 |
183 |
192 |
193 |
194 |
195 | Type {`{{myVariable}}`} to insert
196 | variables into your template.
197 |
198 |
199 |
200 |
201 | Inputs:
202 |
203 | {getPromptVariables(prompt).map((variable) => (
204 |
205 | {variable}
206 |
207 | ))}
208 |
209 |
210 |
211 |
212 |
213 | )}
214 |
215 | );
216 | }
217 |
--------------------------------------------------------------------------------
/app/app/prompt-templates/loading.js:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { Center, Spinner } from "@chakra-ui/react";
3 |
4 | export default function Loading() {
5 | return (
6 |
7 |
8 |
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/app/app/prompt-templates/page.js:
--------------------------------------------------------------------------------
1 | import PromptTemplatesClientPage from "./client-page";
2 |
3 | export const metadata = {
4 | title: "Prompt templates",
5 | description: "Manage your prompt templates",
6 | };
7 |
8 | export default function PromptTemplatesPage() {
9 | return ;
10 | }
11 |
--------------------------------------------------------------------------------
/app/app/sidebar.js:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React from "react";
3 | import PropTypes from "prop-types";
4 | import { useRouter } from "next/navigation";
5 | import { Box, Button, Icon, Stack, useColorMode } from "@chakra-ui/react";
6 | import { useSidebar } from "@/lib/sidebar";
7 | import UserMenu from "@/components/user-menu";
8 |
9 | function SidebarItem({ id, href, label, icon, ...properties }) {
10 | const router = useRouter();
11 |
12 | return (
13 | }
20 | onClick={() => router.push(href)}
21 | {...properties}
22 | >
23 | {label}
24 |
25 | );
26 | }
27 |
28 | SidebarItem.propTypes = {
29 | id: PropTypes.string,
30 | href: PropTypes.string,
31 | label: PropTypes.string,
32 | icon: PropTypes.func,
33 | };
34 |
35 | export default function Sidebar() {
36 | const menu = useSidebar();
37 | const { colorMode } = useColorMode();
38 | const isLight = colorMode === "light";
39 | const backgroundColor = isLight ? "gray.50" : "gray.900";
40 |
41 | return (
42 |
50 |
51 |
52 |
53 | {menu
54 | .filter(({ placement }) => placement === "top")
55 | .map(({ id, label, href, icon, iconDark, ...properties }) => (
56 |
64 | ))}
65 |
66 |
67 |
68 | {menu
69 | .filter(({ placement }) => placement === "bottom")
70 | .map(
71 | ({ id, label, labelDark, href, icon, iconDark, ...properties }) => (
72 |
80 | )
81 | )}
82 |
83 |
84 | );
85 | }
86 |
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/homanp/langchain-ui/75614100a9a28ae2d4bc9d16c58ed971d9cd3c4e/app/favicon.ico
--------------------------------------------------------------------------------
/app/landing-page.js:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { Inter } from "next/font/google";
3 | import {
4 | Button,
5 | Container,
6 | Flex,
7 | Heading,
8 | HStack,
9 | Icon,
10 | Link,
11 | Stack,
12 | Text,
13 | useColorModeValue,
14 | } from "@chakra-ui/react";
15 | import { TbBrandGithub, TbLink } from "react-icons/tb";
16 | import { signIn } from "next-auth/react";
17 |
18 | const inter = Inter({ subsets: ["latin"] });
19 |
20 | export default function LandingPage() {
21 | const backgroundColor = useColorModeValue("#131416", "#131416");
22 | const buttonColorScheme = useColorModeValue("blackAlpha", "whiteAlpha");
23 | const buttonBackgroundColor = useColorModeValue("black", "white");
24 | const fontColor = useColorModeValue("white", "white");
25 |
26 | return (
27 |
32 |
33 |
34 |
35 | LangChain
36 |
41 | UI
42 |
43 |
44 |
45 |
49 | Github
50 |
51 |
55 | Contribute
56 |
57 |
61 | Docs
62 |
63 |
64 |
65 |
71 |
77 | The open source{" "}
78 |
82 | chat-ai toolkit
83 |
84 |
85 |
86 | }
88 | colorScheme={buttonColorScheme}
89 | backgroundColor={buttonBackgroundColor}
90 | size="sm"
91 | onClick={() => signIn("github", { callbackUrl: "/app" })}
92 | >
93 | Sign in with Github
94 |
95 |
96 |
97 |
103 | Contribute on Github
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 | );
114 | }
115 |
--------------------------------------------------------------------------------
/app/layout.js:
--------------------------------------------------------------------------------
1 | import { Inter } from "next/font/google";
2 | import { Analytics } from "@vercel/analytics/react";
3 | import Providers from "./providers";
4 |
5 | const inter = Inter({ subsets: ["latin"] });
6 |
7 | export const metadata = {
8 | title: "LangChain UI",
9 | description: "The opensource Chat AI toolkit",
10 | };
11 |
12 | export default function RootLayout({ children }) {
13 | return (
14 |
15 |
16 | {children}
17 |
18 |
19 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/app/loading.js:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { Center, Spinner } from "@chakra-ui/react";
3 |
4 | export default function Loading() {
5 | return (
6 |
7 |
8 |
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/app/page.js:
--------------------------------------------------------------------------------
1 | import LandingPage from "./landing-page";
2 |
3 | export default function Home() {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/app/providers.js:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { CacheProvider } from "@chakra-ui/next-js";
3 | import { ChakraProvider } from "@chakra-ui/react";
4 | import { SessionProvider } from "next-auth/react";
5 | import theme from "@/lib/theme";
6 |
7 | export default function Providers({ children }) {
8 | return (
9 |
10 |
11 | {children}
12 |
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/app/user/login/login.js:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React from "react";
3 | import {
4 | Button,
5 | Center,
6 | Container,
7 | Flex,
8 | Icon,
9 | Stack,
10 | Text,
11 | } from "@chakra-ui/react";
12 | import { signIn } from "next-auth/react";
13 | import { TbBrandGithub } from "react-icons/tb";
14 |
15 | export default function Login() {
16 | return (
17 |
18 |
19 |
20 |
21 |
22 | LangChain UI
23 |
24 |
25 | }
29 | onClick={() => signIn("github", { callbackUrl: "/app" })}
30 | >
31 | Login with Github
32 |
33 |
34 |
35 |
36 |
37 |
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/app/user/login/page.js:
--------------------------------------------------------------------------------
1 | import Login from "./login";
2 |
3 | export const metadata = {
4 | title: "Login - LangChain UI",
5 | description: "Login to your account",
6 | };
7 |
8 | export default function LoginPage() {
9 | return ;
10 | }
11 |
--------------------------------------------------------------------------------
/components/chat/index.js:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useState } from "react";
2 | import PropTypes from "prop-types";
3 | import { Stack } from "@chakra-ui/react";
4 | import { fetchEventSource } from "@microsoft/fetch-event-source";
5 | import { createChatbotMessage } from "@/lib/api";
6 | import ChatInput from "./input";
7 | import ChatOuput from "./output";
8 |
9 | const API_URL = process.env.NEXT_PUBLIC_LANGCHAIN_UI_API_URL;
10 |
11 | export default function Chat({ id, ...properties }) {
12 | const [messages, setMessages] = useState([]);
13 | const [newMessage, setNewMessage] = useState();
14 | const [isSendingMessage, setIsSendingMessage] = useState();
15 | const decoder = new TextDecoder();
16 |
17 | const onSubmit = useCallback(
18 | async (values) => {
19 | let message = "";
20 |
21 | setIsSendingMessage(true);
22 | setMessages((previousMessages) => [
23 | ...previousMessages,
24 | { data: { response: values } },
25 | ]);
26 |
27 | createChatbotMessage(id, {
28 | message: values,
29 | agent: "user",
30 | });
31 |
32 | await fetchEventSource(`${API_URL}chatbots/${id}`, {
33 | method: "POST",
34 | headers: {
35 | "Content-Type": "application/json",
36 | },
37 | body: JSON.stringify({
38 | message: values,
39 | }),
40 | async onmessage(event) {
41 | if (event.data !== "CLOSE") {
42 | message += event.data === "" ? `${event.data} \n` : event.data;
43 | setNewMessage(message);
44 | }
45 |
46 | if (event.data === "CLOSE") {
47 | setMessages((previousMessages) => [
48 | ...previousMessages,
49 | { agent: "ai", data: { response: message } },
50 | ]);
51 |
52 | createChatbotMessage(id, {
53 | message,
54 | agent: "ai",
55 | });
56 |
57 | setNewMessage();
58 | }
59 | },
60 | });
61 | setIsSendingMessage();
62 | },
63 | [id]
64 | );
65 |
66 | return (
67 |
74 |
81 |
89 |
90 | );
91 | }
92 |
--------------------------------------------------------------------------------
/components/chat/input.js:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useEffect, useRef, useState } from "react";
2 | import PropTypes from "prop-types";
3 | import {
4 | Box,
5 | Container,
6 | HStack,
7 | Icon,
8 | IconButton,
9 | Stack,
10 | Textarea,
11 | useColorModeValue,
12 | } from "@chakra-ui/react";
13 | import { TbSend } from "react-icons/tb";
14 | import autosize from "autosize";
15 | import { BeatLoader } from "react-spinners";
16 |
17 | export default function ChatInput({ isLoading, onSubmit, ...properties }) {
18 | const backgroundColor = useColorModeValue("gray.100", "gray.700");
19 | const backgroundGradient = useColorModeValue(
20 | "linear(to-t, white, transparent)",
21 | "linear(to-t, gray.800, transparent)"
22 | );
23 | const loaderColor = useColorModeValue("gray.100", "white");
24 | const iconColor = useColorModeValue("gray.500", "white");
25 | const [message, setMessage] = useState();
26 | const textareaReference = useRef();
27 |
28 | const handleKeyDown = useCallback(
29 | (event) => {
30 | if (event.keyCode === 13 && !event.shiftKey) {
31 | event.preventDefault();
32 |
33 | onSubmit(message);
34 | setMessage("");
35 |
36 | autosize.destroy(textareaReference?.current);
37 | }
38 | },
39 | [message, onSubmit]
40 | );
41 |
42 | useEffect(() => {
43 | const ref = textareaReference?.current;
44 |
45 | autosize(ref);
46 |
47 | return () => {
48 | autosize.destroy(ref);
49 | };
50 | }, []);
51 |
52 | useEffect(() => {
53 | const ref = textareaReference?.current;
54 |
55 | if (!isLoading) {
56 | ref.focus();
57 | }
58 | }, [isLoading]);
59 |
60 | return (
61 |
62 |
63 |
72 |
108 |
109 |
110 | );
111 | }
112 |
113 | ChatInput.propTypes = {
114 | isLoading: PropTypes.bool,
115 | onSubmit: PropTypes.func,
116 | };
117 |
--------------------------------------------------------------------------------
/components/chat/message.js:
--------------------------------------------------------------------------------
1 | import React, { useRef } from "react";
2 | import {
3 | Avatar,
4 | Box,
5 | Code,
6 | HStack,
7 | Icon,
8 | IconButton,
9 | Stack,
10 | Text,
11 | useColorModeValue,
12 | } from "@chakra-ui/react";
13 | import remarkGfm from "remark-gfm";
14 | import PropTypes from "prop-types";
15 | import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
16 | import { BeatLoader } from "react-spinners";
17 | import { dracula } from "react-syntax-highlighter/dist/esm/styles/prism";
18 | import { TbCopy } from "react-icons/tb";
19 | import { MemoizedReactMarkdown } from "@/lib/markdown";
20 |
21 | export default function Message({ agent, message, isLastMessage }) {
22 | const loaderColor = useColorModeValue("gray.100", "white");
23 | const lastMessageReference = useRef();
24 | const unevenBackgroundColor = useColorModeValue("gray.100", "gray.700");
25 |
26 | return (
27 |
32 |
33 |
40 |
41 | {message ? (
42 | {
49 | navigator.clipboard.writeText(value);
50 | };
51 |
52 | return !inline ? (
53 |
54 |
55 | {match && match[1]}
56 | }
59 | onClick={() => handleCopyCode()}
60 | />
61 |
62 |
75 | {value}
76 |
77 |
78 | ) : (
79 |
80 | {children}
81 |
82 | );
83 | },
84 | }}
85 | remarkPlugins={[remarkGfm]}
86 | >
87 | {message}
88 |
89 | ) : (
90 |
91 | )}
92 |
93 |
94 |
95 | );
96 | }
97 |
98 | Message.propTypes = {
99 | agent: PropTypes.string,
100 | message: PropTypes.string,
101 | isLastMessage: PropTypes.bool,
102 | };
103 |
--------------------------------------------------------------------------------
/components/chat/output.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from "react";
2 | import { Box, Stack } from "@chakra-ui/react";
3 | import PropTypes from "prop-types";
4 | import Message from "./message";
5 |
6 | export default function ChatOuput({
7 | messages,
8 | newMessage,
9 | isLoading,
10 | ...properties
11 | }) {
12 | const lastMessageReference = useRef();
13 |
14 | useEffect(() => {
15 | if (lastMessageReference?.current) {
16 | lastMessageReference?.current.scrollIntoView();
17 | }
18 | }, [messages]);
19 |
20 | const showAIMessage = isLoading || newMessage;
21 |
22 | return (
23 |
24 |
25 | {messages.map(({ agent, data: { response } }, index) => (
26 |
32 | ))}
33 | {showAIMessage && (
34 |
35 |
36 |
37 | )}
38 |
39 |
40 | );
41 | }
42 |
43 | ChatOuput.propTypes = {
44 | messages: PropTypes.array,
45 | newMessage: PropTypes.string,
46 | isLoading: PropTypes.bool,
47 | };
48 |
--------------------------------------------------------------------------------
/components/chat/prompt-templates.js:
--------------------------------------------------------------------------------
1 | import React, { useCallback } from "react";
2 | import PropTypes from "prop-types";
3 | import { useRouter } from "next/navigation";
4 | import {
5 | Button,
6 | Center,
7 | HStack,
8 | Popover,
9 | PopoverTrigger,
10 | PopoverContent,
11 | PopoverHeader,
12 | PopoverBody,
13 | PopoverCloseButton,
14 | Spinner,
15 | Stack,
16 | Text,
17 | useColorModeValue,
18 | Icon,
19 | useDisclosure,
20 | } from "@chakra-ui/react";
21 | import { useAsync, useAsyncFn } from "react-use";
22 | import { getPrompTemplates, updateChatbotById } from "@/lib/api";
23 | import { TbChevronDown } from "react-icons/tb";
24 |
25 | export default function AssignPromptTemplate({ chatbot, onChange }) {
26 | const popoverBackgroundColor = useColorModeValue("white", "#2F3239");
27 | const { isOpen, onToggle, onClose } = useDisclosure();
28 | const { loading: isLoading, value: promptTemplates = [] } =
29 | useAsync(async () => {
30 | const { data } = await getPrompTemplates();
31 |
32 | return data;
33 | }, []);
34 |
35 | const [{ loading: isSelectingPromptTemplate }, handleSelect] = useAsyncFn(
36 | async (promptTemplateId) => {
37 | onToggle();
38 | const { id, chatbotData } = chatbot;
39 | const { data } = await updateChatbotById(id, {
40 | ...chatbotData,
41 | promptTemplateId: promptTemplateId || null,
42 | });
43 |
44 | onChange(data);
45 | },
46 | [onChange, onToggle, updateChatbotById]
47 | );
48 |
49 | return (
50 |
51 |
52 | Prompt templates
53 |
54 |
55 |
56 | }
62 | >
63 | {promptTemplates.find(({ id }) => id === chatbot.promptTemplateId)
64 | ?.name || "Select template"}
65 |
66 |
67 |
72 |
73 |
74 | Prompt templates
75 |
76 |
82 |
83 | {isLoading && (
84 |
85 |
86 |
87 | )}
88 |
97 | {!isLoading &&
98 | promptTemplates.map(({ id, name }) => (
99 |
109 | ))}
110 |
111 |
112 |
113 |
114 |
115 | );
116 | }
117 |
118 | AssignPromptTemplate.propTypes = {
119 | chatbot: PropTypes.object,
120 | onChange: PropTypes.func,
121 | };
122 |
--------------------------------------------------------------------------------
/components/code-block.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import PropTypes from "prop-types";
3 | import { Box, Button, HStack, Stack, StackDivider } from "@chakra-ui/react";
4 | import CodeMirror from "@uiw/react-codemirror";
5 | import { markdown, markdownLanguage } from "@codemirror/lang-markdown";
6 | import { languages } from "@codemirror/language-data";
7 | import { xcodeDark } from "@uiw/codemirror-theme-xcode";
8 |
9 | export default function CodeBlock({ items }) {
10 | const [item, setItem] = useState(items[0].id);
11 |
12 | return (
13 | }
18 | spacing={0}
19 | >
20 |
21 | {items.map(({ id, label }) => (
22 |
31 | ))}
32 |
33 |
34 | id === item).code}
41 | />
42 |
43 |
44 | );
45 | }
46 |
--------------------------------------------------------------------------------
/components/datasources/csv.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import { Button, Icon, Stack, Text } from "@chakra-ui/react";
4 | import { TbFileUpload } from "react-icons/tb";
5 | import FilePicker from "../file-picker";
6 |
7 | export default function CsvDocument({ files, register, validate }) {
8 | return (
9 |
17 |
18 | }
23 | color="gray.500"
24 | >
25 | Select a file to import
26 |
27 |
28 | {files?.length > 0 && (
29 |
30 | {files[0].name}
31 |
32 | )}
33 |
34 | );
35 | }
36 |
37 | CsvDocument.propTypes = {
38 | files: PropTypes.object,
39 | register: PropTypes.func,
40 | validate: PropTypes.func,
41 | };
42 |
--------------------------------------------------------------------------------
/components/file-picker.js:
--------------------------------------------------------------------------------
1 | import React, { forwardRef, useRef } from "react";
2 | import PropTypes from "prop-types";
3 | import { Input, useMergeRefs } from "@chakra-ui/react";
4 |
5 | // eslint-disable-next-line react/display-name
6 | const FilePicker = forwardRef(({ children, ...properties }, reference_) => {
7 | const reference = useRef();
8 | const references = useMergeRefs(reference, reference_);
9 | const children_ = React.cloneElement(React.Children.only(children), {
10 | onClick: () => reference.current.click(),
11 | });
12 |
13 | return (
14 | <>
15 |
16 | {children_}
17 | >
18 | );
19 | });
20 |
21 | FilePicker.propTypes = {
22 | children: PropTypes.node.isRequired,
23 | };
24 |
25 | export default FilePicker;
26 |
--------------------------------------------------------------------------------
/components/page-header.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import { HStack, Icon, Stack, Text } from "@chakra-ui/react";
4 |
5 | export default function PageHeader({ title, children, icon, ...properties }) {
6 | return (
7 |
8 |
9 |
10 |
11 | {title}
12 |
13 |
14 | {children}
15 |
16 | );
17 | }
18 |
19 | PageHeader.propTypes = {
20 | title: PropTypes.string,
21 | icon: PropTypes.func,
22 | children: PropTypes.node,
23 | };
24 |
--------------------------------------------------------------------------------
/components/user-menu.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Avatar, Divider, HStack, Stack, Text } from "@chakra-ui/react";
3 | import { useSession } from "next-auth/react";
4 |
5 | export default function UserMenu({ ...properties }) {
6 | const { data: session, status } = useSession();
7 |
8 | return (
9 |
10 |
11 |
12 |
13 |
14 | {session?.user.name}
15 |
16 |
17 | {session?.user.email}
18 |
19 |
20 |
21 |
22 |
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/env.example:
--------------------------------------------------------------------------------
1 | DATABASE_URL=""
2 | NEXTAUTH_SECRET=""
3 | NEXTAUTH_URL=""
4 | NEXT_PUBLIC_GITHUB_ID=""
5 | NEXT_PUBLIC_GITHUB_SECRET=""
6 | OPENAI_API_KEY=""
7 | METAL_API_KEY=""
8 | METAL_APP_ID=""
9 | METAL_INDEX_ID=""
10 | METAL_CLIENT_ID=""
11 | NEXT_PUBLIC_AMAZON_S3_BUCKET_NAME=""
12 | NEXT_PUBLIC_AMAZON_S3_SECRET_ACCESS_KEY=""
13 | NEXT_PUBLIC_AMAZON_S3_ACCESS_KEY_ID=""
14 | NEXT_PUBLIC_LANGCHAIN_UI_API_URL=""
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "paths": {
4 | "@/*": ["./*"]
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/lib/api-docs.js:
--------------------------------------------------------------------------------
1 | const cURL = `\`\`\`bash
2 | # cURL request to chatbot API endpoint.
3 | curl -X POST https://dolphin-app-tmpol.ondigitalocean.app/api/v1/chatbot/{{id}}
4 | -H "Content-Type: application/json"
5 | -H "Authorization: Bearer {token}"
6 | -d '{"message": "Hello!"}'
7 | \`\`\``;
8 |
9 | const javascript = `\`\`\`jsx
10 | # Javascript request to chatbot API endpoint.
11 | const requestOptions = {
12 | method: 'POST',
13 | headers: {
14 | 'Content-Type': 'application/json'
15 | 'Authorization': 'Bearer {token}'
16 | },
17 | body: JSON.stringify({ message: 'Hello!' })
18 | };
19 |
20 | const response = await fetch(
21 | 'https://dolphin-app-tmpol.ondigitalocean.app/api/v1/chatbot/{{id}}',
22 | requestOptions
23 | );
24 | const data = await response.json();
25 | \`\`\``;
26 |
27 | const python = `\`\`\`python
28 | # Python request to chatbot API endpoint using the requests library.
29 | import requests
30 |
31 | url = 'https://dolphin-app-tmpol.ondigitalocean.app/api/v1/chatbot/{{id}}'
32 | headers = {'Authorization': 'Bearer {token}'}
33 | payload = {'messsage': 'Hello!'}
34 |
35 | response = requests.post(url, data = payload, headers = headers)
36 |
37 | print(response.text)
38 | \`\`\``;
39 |
40 | const php = `\`\`\`php
41 | # PHP request to chatbot API endpoint.
42 | "Hello!"
46 | );
47 |
48 | $headers = array(
49 | "Content-Type: application/json",
50 | "Authorization: Bearer {TOKEN}"
51 | );
52 |
53 | $ch = curl_init();
54 | curl_setopt($ch, CURLOPT_URL, $url);
55 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
56 | curl_setopt($ch, CURLOPT_POST, true);
57 | curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
58 | curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
59 |
60 | $response = curl_exec($ch);
61 | curl_close($ch);
62 |
63 | echo $response;
64 | ?>
65 | \`\`\``;
66 |
67 | export const API_DOCS = [
68 | {
69 | id: "curl",
70 | label: "cURL",
71 | code: cURL,
72 | },
73 | {
74 | id: "javascript",
75 | label: "Javascript",
76 | code: javascript,
77 | },
78 | {
79 | id: "python",
80 | label: "Python",
81 | code: python,
82 | },
83 | {
84 | id: "php",
85 | label: "PHP",
86 | code: php,
87 | },
88 | ];
89 |
--------------------------------------------------------------------------------
/lib/api.js:
--------------------------------------------------------------------------------
1 | import ky from "ky";
2 |
3 | const baseUrl =
4 | process.env.NEXT_ENV === "production"
5 | ? "https://langchain-ui.vercel.app"
6 | : "http://localhost:3000";
7 |
8 | const api = ky.create({ prefixUrl: baseUrl });
9 |
10 | export const ingestData = async (data) =>
11 | ky.post("/api/datasources/ingest", { json: data }).json();
12 |
13 | export const createDatasource = async (data) =>
14 | ky.post("/api/datasources", { json: data }).json();
15 |
16 | export const createChatbot = async (data) =>
17 | ky.post("/api/chatbots", { json: data }).json();
18 |
19 | export const createChatbotMessage = async (id, data) =>
20 | ky
21 | .post(`/api/chatbots/${id}/messages`, {
22 | json: { ...data },
23 | timeout: 60000,
24 | })
25 | .json();
26 |
27 | export const createPromptTemplate = (data) =>
28 | ky.post("/api/prompt-templates", { json: data }).json();
29 |
30 | export const getChatbotById = async (id) =>
31 | ky.get(`/api/chatbots/${id}`).json();
32 |
33 | export const getChatbots = async () => ky.get("/api/chatbots").json();
34 |
35 | export const getDatasources = async () => ky.get("/api/datasources").json();
36 |
37 | export const getMessagesByChatbotId = async (chatbotId) =>
38 | ky.get(`/api/chatbots/${chatbotId}/messages`).json();
39 |
40 | export const getPromptVariables = (string) => {
41 | let variables = [];
42 | let regex = /{{(.*?)}}/g;
43 | let match;
44 |
45 | while ((match = regex.exec(string))) {
46 | variables.push(match[1].trim());
47 | }
48 |
49 | return variables;
50 | };
51 |
52 | export const getPrompTemplates = async () =>
53 | ky.get("/api/prompt-templates").json();
54 |
55 | export const removePromptTemplateById = async (id) =>
56 | ky.delete(`/api/prompt-templates/${id}`).json();
57 |
58 | export const removeChatbotById = async (id) =>
59 | ky.delete(`/api/chatbots/${id}`).json();
60 |
61 | export const removeDatasourceById = async (id) =>
62 | ky.delete(`/api/datasources/${id}`).json();
63 |
64 | export const sendChatMessage = async ({ id, message, history }) =>
65 | ky
66 | .post(`/api/v1/chatbots/${id}`, {
67 | json: { message, history },
68 | timeout: 60000,
69 | })
70 | .json();
71 |
72 | export const updateChatbotById = async (id, data) =>
73 | ky
74 | .patch(`/api/chatbots/${id}`, {
75 | json: { ...data },
76 | })
77 | .json();
78 |
--------------------------------------------------------------------------------
/lib/datasources.js:
--------------------------------------------------------------------------------
1 | import CsvDocument from "@/components/datasources/csv";
2 |
3 | export const DATASOURCE_TYPES = [
4 | {
5 | id: "csv",
6 | type: "csv",
7 | name: "CSV file",
8 | component: CsvDocument,
9 | },
10 | ];
11 |
--------------------------------------------------------------------------------
/lib/markdown.js:
--------------------------------------------------------------------------------
1 | import { memo } from "react";
2 | import ReactMarkdown from "react-markdown";
3 |
4 | export const MemoizedReactMarkdown = memo(
5 | ReactMarkdown,
6 | (prevProps, nextProps) => prevProps.children === nextProps.children
7 | );
8 |
--------------------------------------------------------------------------------
/lib/metal.js:
--------------------------------------------------------------------------------
1 | import Metal from "@getmetal/metal-sdk";
2 | import { MetalRetriever } from "langchain/retrievers/metal";
3 |
4 | export const useMetal = () => {
5 | const client = new Metal(
6 | process.env.METAL_API_KEY,
7 | process.env.METAL_CLIENT_ID,
8 | process.env.METAL_INDEX_ID
9 | );
10 |
11 | const retriever = new MetalRetriever({ client });
12 |
13 | return { retriever };
14 | };
15 |
--------------------------------------------------------------------------------
/lib/prisma.js:
--------------------------------------------------------------------------------
1 | import { PrismaClient } from "@prisma/client";
2 |
3 | const globalPrisma = {
4 | prisma: undefined,
5 | };
6 |
7 | export const prismaClient = globalPrisma.prisma || new PrismaClient();
8 |
9 | if (process.env.NODE_ENV !== "production") {
10 | globalPrisma.prisma = prismaClient;
11 | }
12 |
13 | if (process.env.NODE_ENV !== "production") globalPrisma.prisma = prismaClient;
14 |
--------------------------------------------------------------------------------
/lib/s3.js:
--------------------------------------------------------------------------------
1 | import { S3Client } from "@aws-sdk/client-s3";
2 |
3 | export const s3Client = new S3Client({
4 | credentials: {
5 | accessKeyId: process.env.NEXT_PUBLIC_AMAZON_S3_ACCESS_KEY_ID,
6 | secretAccessKey: process.env.NEXT_PUBLIC_AMAZON_S3_SECRET_ACCESS_KEY,
7 | },
8 | region: "eu-north-1",
9 | });
10 |
--------------------------------------------------------------------------------
/lib/sidebar.js:
--------------------------------------------------------------------------------
1 | import { signOut } from "next-auth/react";
2 | import { useColorMode } from "@chakra-ui/react";
3 | import {
4 | TbDatabase,
5 | TbLogout,
6 | TbMessage,
7 | TbPrompt,
8 | TbSettings,
9 | TbMoon,
10 | TbSun,
11 | TbHome,
12 | } from "react-icons/tb";
13 |
14 | const PLACEMENT = {
15 | top: "top",
16 | bottom: "bottom",
17 | };
18 |
19 | export const useSidebar = () => {
20 | const { toggleColorMode } = useColorMode();
21 |
22 | return [
23 | {
24 | id: "home",
25 | label: "Home",
26 | href: "/app",
27 | icon: TbHome,
28 | placement: PLACEMENT.top,
29 | },
30 | {
31 | id: "datasources",
32 | label: "Datasources",
33 | href: "/app/datasources",
34 | icon: TbDatabase,
35 | placement: PLACEMENT.top,
36 | },
37 | {
38 | id: "prompt_templates",
39 | label: "Prompt templates",
40 | href: "/app/prompt-templates",
41 | icon: TbPrompt,
42 | placement: PLACEMENT.top,
43 | },
44 | {
45 | id: "chatbots",
46 | label: "Chatbots",
47 | href: "/app/chatbots",
48 | icon: TbMessage,
49 | placement: PLACEMENT.top,
50 | },
51 | {
52 | id: "dark_mode",
53 | label: "Dark",
54 | labelDark: "Light",
55 | onClick: () => toggleColorMode(),
56 | icon: TbMoon,
57 | iconDark: TbSun,
58 | placement: PLACEMENT.bottom,
59 | },
60 | {
61 | id: "settings",
62 | label: "Settings",
63 | href: "/app/settings",
64 | icon: TbSettings,
65 | placement: PLACEMENT.bottom,
66 | },
67 | {
68 | id: "singout",
69 | label: "Sign out",
70 | onClick: () => signOut(),
71 | icon: TbLogout,
72 | placement: PLACEMENT.bottom,
73 | },
74 | ];
75 | };
76 |
--------------------------------------------------------------------------------
/lib/theme.js:
--------------------------------------------------------------------------------
1 | import { mode } from "@chakra-ui/theme-tools";
2 | import { extendTheme } from "@chakra-ui/react";
3 |
4 | const theme = extendTheme({
5 | config: {
6 | initialColorMode: "dark",
7 | },
8 | fonts: {
9 | heading: "Inter, sans-serif",
10 | body: "Inter, sans-serif",
11 | },
12 | });
13 |
14 | export default theme;
15 |
--------------------------------------------------------------------------------
/lib/upload-file.js:
--------------------------------------------------------------------------------
1 | import ky from "ky";
2 |
3 | const defaultFetch = ky.extend({ timeout: 60_000 });
4 |
5 | export const getUploadFileUrl = ({ fields, url }) => `${url}/${fields.key}`;
6 |
7 | // TODO: Remove `fetch` option when Node.js 20 is available
8 | export const uploadFile = async (
9 | file,
10 | { fetch: fetch_ = defaultFetch, fields, FormData: FormData_ = FormData, url }
11 | ) => {
12 | const formData = new FormData_();
13 |
14 | for (const [key, value] of Object.entries({ ...fields, file })) {
15 | formData.append(key, value);
16 | }
17 |
18 | await fetch_.post(url, { body: formData });
19 |
20 | return getUploadFileUrl({ fields, url });
21 | };
22 |
--------------------------------------------------------------------------------
/lib/upload-url.js:
--------------------------------------------------------------------------------
1 | import { createPresignedPost } from "@aws-sdk/s3-presigned-post";
2 | import { v4 as uuid } from "uuid";
3 | import { s3Client } from "./s3.js";
4 |
5 | export const getUploadUrl = async ({ type } = {}) =>
6 | createPresignedPost(s3Client, {
7 | Bucket: process.env.NEXT_PUBLIC_AMAZON_S3_BUCKET_NAME,
8 | Conditions: [
9 | ["content-length-range", 0, 1_048_576 * 50], // TODO: Change me from 50 MB to a production value
10 | ],
11 | Expires: 60, // TODO: Change me from 60 seconds to a production value
12 | Fields: {
13 | "Content-Type": type,
14 | },
15 | Key: uuid(),
16 | });
17 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const nextConfig = {
2 | experimental: {
3 | appDir: true,
4 | },
5 | webpack(config) {
6 | config.experiments = {
7 | asyncWebAssembly: true,
8 | layers: true,
9 | };
10 |
11 | return config;
12 | },
13 | };
14 |
15 | module.exports = nextConfig;
16 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "langchain-ui",
3 | "version": "0.1.2",
4 | "engines": {
5 | "node": ">=18"
6 | },
7 | "private": true,
8 | "scripts": {
9 | "dev": "next dev",
10 | "build": "next build",
11 | "start": "next start",
12 | "lint": "next lint",
13 | "test": "next lint"
14 | },
15 | "dependencies": {
16 | "@aws-sdk/client-s3": "^3.319.0",
17 | "@aws-sdk/s3-presigned-post": "^3.319.0",
18 | "@chakra-ui/next-js": "^2.0.1",
19 | "@chakra-ui/react": "^2.5.2",
20 | "@chakra-ui/theme-tools": "^2.0.17",
21 | "@codemirror/lang-json": "^6.0.1",
22 | "@codemirror/lang-markdown": "^6.1.0",
23 | "@codemirror/language-data": "^6.2.0",
24 | "@dqbd/tiktoken": "^1.0.2",
25 | "@emotion/react": "^11.10.6",
26 | "@emotion/styled": "^11.10.6",
27 | "@getmetal/metal-sdk": "^2.0.1",
28 | "@microsoft/fetch-event-source": "^2.0.1",
29 | "@next-auth/prisma-adapter": "^1.0.5",
30 | "@prisma/client": "^4.11.0",
31 | "@uiw/codemirror-theme-xcode": "^4.19.11",
32 | "@uiw/react-codemirror": "^4.19.11",
33 | "@vercel/analytics": "^0.1.11",
34 | "autosize": "^6.0.1",
35 | "d3-dsv": "^2.0.0",
36 | "eslint": "8.36.0",
37 | "eslint-config-next": "13.2.4",
38 | "framer-motion": "^10.8.5",
39 | "install": "^0.13.0",
40 | "ky": "^0.33.3",
41 | "langchain": "^0.0.63",
42 | "next": "^13.3.0",
43 | "next-auth": "^4.20.1",
44 | "npm": "^9.6.3",
45 | "react": "18.2.0",
46 | "react-dom": "18.2.0",
47 | "react-hook-form": "^7.43.7",
48 | "react-icons": "^4.8.0",
49 | "react-markdown": "^8.0.6",
50 | "react-spinners": "^0.13.8",
51 | "react-syntax-highlighter": "^15.5.0",
52 | "react-use": "^17.4.0",
53 | "rehype-raw": "^6.1.1",
54 | "remark-gfm": "^3.0.1",
55 | "typeorm": "^0.3.12",
56 | "uuid": "^9.0.0"
57 | },
58 | "devDependencies": {
59 | "prisma": "^4.11.0"
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/pages/api/auth/[...nextauth].js:
--------------------------------------------------------------------------------
1 | import NextAuth from "next-auth";
2 | import GithubProvider from "next-auth/providers/github";
3 | import { PrismaAdapter } from "@next-auth/prisma-adapter";
4 | import { prismaClient } from "@/lib/prisma";
5 |
6 | export const authOptions = {
7 | adapter: PrismaAdapter(prismaClient),
8 | providers: [
9 | GithubProvider({
10 | clientId: process.env.NEXT_PUBLIC_GITHUB_ID,
11 | clientSecret: process.env.NEXT_PUBLIC_GITHUB_SECRET,
12 | }),
13 | ],
14 | pages: {
15 | signIn: "/",
16 | signOut: "/user/logout",
17 | },
18 | };
19 |
20 | export default NextAuth(authOptions);
21 |
--------------------------------------------------------------------------------
/pages/api/chatbots/[chatbotId]/index.js:
--------------------------------------------------------------------------------
1 | import { getServerSession } from "next-auth/next";
2 | import { authOptions } from "../../auth/[...nextauth]";
3 | import { prismaClient } from "@/lib/prisma";
4 |
5 | const chatbotHandler = async (request, response) => {
6 | const session = await getServerSession(request, response, authOptions);
7 | const { chatbotId } = request.query;
8 |
9 | if (!session) {
10 | return response
11 | .status(403)
12 | .json({ success: false, error: "Not authenticated" });
13 | }
14 |
15 | if (request.method === "DELETE") {
16 | const data = await prismaClient.chatbot.delete({
17 | where: {
18 | id: parseInt(chatbotId),
19 | },
20 | });
21 |
22 | return response.status(200).json({
23 | success: true,
24 | data,
25 | });
26 | }
27 |
28 | if (request.method === "GET") {
29 | const data = await prismaClient.chatbot.findUnique({
30 | where: {
31 | id: parseInt(chatbotId),
32 | },
33 | include: {
34 | datasource: true,
35 | promptTemplate: true,
36 | },
37 | });
38 |
39 | return response.status(200).json({
40 | success: true,
41 | data,
42 | });
43 | }
44 |
45 | if (request.method === "PATCH") {
46 | const data = await prismaClient.chatbot.update({
47 | where: {
48 | id: parseInt(chatbotId),
49 | },
50 | data: { ...request.body },
51 | });
52 |
53 | return response.status(200).json({
54 | success: true,
55 | data,
56 | });
57 | }
58 | };
59 |
60 | export default chatbotHandler;
61 |
--------------------------------------------------------------------------------
/pages/api/chatbots/[chatbotId]/messages/index.js:
--------------------------------------------------------------------------------
1 | import { prismaClient } from "@/lib/prisma";
2 |
3 | export const chatbotMessagesHandler = async (request, response) => {
4 | const { chatbotId } = request.query;
5 |
6 | if (request.method === "POST") {
7 | const message = await prismaClient.chatbotMessage.create({
8 | data: {
9 | chatbotId: parseInt(chatbotId),
10 | ...request.body,
11 | },
12 | });
13 |
14 | return response.status(200).json({ sucess: true, data: message });
15 | }
16 |
17 | if (request.method === "GET") {
18 | const messages = await prismaClient.chatbotMessage.findMany({
19 | where: { chatbotId: parseInt(chatbotId) },
20 | orderBy: {
21 | createdAt: "desc",
22 | },
23 | take: 5,
24 | });
25 |
26 | return response.status(200).json({ sucess: true, data: messages });
27 | }
28 | };
29 |
30 | export default chatbotMessagesHandler;
31 |
--------------------------------------------------------------------------------
/pages/api/chatbots/index.js:
--------------------------------------------------------------------------------
1 | import { getServerSession } from "next-auth/next";
2 | import { authOptions } from "../auth/[...nextauth]";
3 | import { prismaClient } from "@/lib/prisma";
4 |
5 | const chatbotsHandler = async (request, response) => {
6 | const session = await getServerSession(request, response, authOptions);
7 | const user = await prismaClient.user.findUnique({
8 | where: { email: session.user.email },
9 | });
10 |
11 | if (request.method === "GET") {
12 | const data = await prismaClient.chatbot.findMany({
13 | where: {
14 | userId: {
15 | equals: user.id,
16 | },
17 | },
18 | orderBy: {
19 | createdAt: "desc",
20 | },
21 | });
22 |
23 | return response.status(200).json({
24 | success: true,
25 | data,
26 | });
27 | }
28 |
29 | if (request.method === "POST") {
30 | const chatbot = await prismaClient.chatbot.create({
31 | data: {
32 | userId: user.id,
33 | ...request.body,
34 | },
35 | });
36 |
37 | return response.status(200).json({ sucess: true, data: chatbot });
38 | }
39 | };
40 |
41 | export default chatbotsHandler;
42 |
--------------------------------------------------------------------------------
/pages/api/datasources/[datasourceId]/index.js:
--------------------------------------------------------------------------------
1 | import { getServerSession } from "next-auth/next";
2 | import { authOptions } from "../../auth/[...nextauth]";
3 | import { prismaClient } from "@/lib/prisma";
4 |
5 | const datasourceHandler = async (request, response) => {
6 | const session = await getServerSession(request, response, authOptions);
7 | const { datasourceId } = request.query;
8 |
9 | if (!session) {
10 | return response
11 | .status(403)
12 | .json({ success: false, error: "Not authenticated" });
13 | }
14 |
15 | if (request.method === "DELETE") {
16 | const data = await prismaClient.datasource.delete({
17 | where: {
18 | id: parseInt(datasourceId),
19 | },
20 | });
21 |
22 | return response.status(200).json({
23 | success: true,
24 | data,
25 | });
26 | }
27 |
28 | if (request.method === "PATCH") {
29 | const data = await prismaClient.datasource.update({
30 | where: {
31 | id: parseInt(datasourceId),
32 | },
33 | data: { ...request.body },
34 | });
35 |
36 | return response.status(200).json({
37 | success: true,
38 | data,
39 | });
40 | }
41 | };
42 |
43 | export default datasourceHandler;
44 |
--------------------------------------------------------------------------------
/pages/api/datasources/index.js:
--------------------------------------------------------------------------------
1 | import { getServerSession } from "next-auth/next";
2 | import { authOptions } from "../auth/[...nextauth]";
3 | import { prismaClient } from "@/lib/prisma";
4 |
5 | const chatbotsHandler = async (request, response) => {
6 | const session = await getServerSession(request, response, authOptions);
7 | const user = await prismaClient.user.findUnique({
8 | where: { email: session.user.email },
9 | });
10 |
11 | if (request.method === "GET") {
12 | const data = await prismaClient.datasource.findMany({
13 | where: {
14 | userId: {
15 | equals: user.id,
16 | },
17 | },
18 | orderBy: {
19 | createdAt: "desc",
20 | },
21 | });
22 |
23 | return response.status(200).json({
24 | success: true,
25 | data,
26 | });
27 | }
28 |
29 | if (request.method === "POST") {
30 | const chatbot = await prismaClient.datasource.create({
31 | data: {
32 | userId: user.id,
33 | ...request.body,
34 | },
35 | });
36 |
37 | return response.status(200).json({ sucess: true, data: chatbot });
38 | }
39 | };
40 |
41 | export default chatbotsHandler;
42 |
--------------------------------------------------------------------------------
/pages/api/datasources/ingest.js:
--------------------------------------------------------------------------------
1 | import ky from "ky";
2 | import { CSVLoader } from "langchain/document_loaders/fs/csv";
3 | import Metal from "@getmetal/metal-sdk";
4 | import { v4 as uuidv4 } from "uuid";
5 |
6 | const datasourceIngestHandler = async (request, response) => {
7 | const { url, type } = request.body;
8 | const blob = await ky(url).blob();
9 | const loader = new CSVLoader(blob);
10 | const docs = await loader.load();
11 | const texts = docs.map((doc) => doc.pageContent);
12 | const metadata = { url, type, id: uuidv4() };
13 |
14 | const metal = new Metal(
15 | process.env.METAL_API_KEY,
16 | process.env.METAL_CLIENT_ID,
17 | process.env.METAL_INDEX_ID
18 | );
19 |
20 | await Promise.all(texts.map((text) => metal.index({ text, metadata })));
21 |
22 | response.status(200).json({ success: true, data: { metadata } });
23 | };
24 |
25 | export default datasourceIngestHandler;
26 |
--------------------------------------------------------------------------------
/pages/api/prompt-templates/[promptId]/index.js:
--------------------------------------------------------------------------------
1 | import { getServerSession } from "next-auth/next";
2 | import { authOptions } from "../../auth/[...nextauth]";
3 | import { prismaClient } from "@/lib/prisma";
4 |
5 | const promptTemplateHandler = async (request, response) => {
6 | const session = await getServerSession(request, response, authOptions);
7 | const { promptId } = request.query;
8 |
9 | if (!session) {
10 | return response
11 | .status(403)
12 | .json({ success: false, error: "Not authenticated" });
13 | }
14 |
15 | if (request.method === "DELETE") {
16 | const data = await prismaClient.promptTemplate.delete({
17 | where: {
18 | id: parseInt(promptId),
19 | },
20 | });
21 |
22 | return response.status(200).json({
23 | success: true,
24 | data,
25 | });
26 | }
27 |
28 | if (request.method === "PATCH") {
29 | const data = await prismaClient.promptTemplate.update({
30 | where: {
31 | id: parseInt(promptId),
32 | },
33 | data: { ...request.body },
34 | });
35 |
36 | return response.status(200).json({
37 | success: true,
38 | data,
39 | });
40 | }
41 | };
42 |
43 | export default promptTemplateHandler;
44 |
--------------------------------------------------------------------------------
/pages/api/prompt-templates/index.js:
--------------------------------------------------------------------------------
1 | import { getServerSession } from "next-auth/next";
2 | import { authOptions } from "../auth/[...nextauth]";
3 | import { prismaClient } from "@/lib/prisma";
4 |
5 | const promptTemplatesHandler = async (request, response) => {
6 | const session = await getServerSession(request, response, authOptions);
7 | const user = await prismaClient.user.findUnique({
8 | where: { email: session.user.email },
9 | });
10 |
11 | if (request.method === "GET") {
12 | const data = await prismaClient.promptTemplate.findMany({
13 | where: {
14 | userId: {
15 | equals: user.id,
16 | },
17 | },
18 | orderBy: {
19 | createdAt: "desc",
20 | },
21 | });
22 |
23 | return response.status(200).json({
24 | success: true,
25 | data,
26 | });
27 | }
28 |
29 | if (request.method === "POST") {
30 | const promptTemplate = await prismaClient.promptTemplate.create({
31 | data: {
32 | userId: user.id,
33 | ...request.body,
34 | },
35 | });
36 |
37 | return response.status(200).json({ sucess: true, data: promptTemplate });
38 | }
39 | };
40 |
41 | export default promptTemplatesHandler;
42 |
--------------------------------------------------------------------------------
/prisma/migrations/20230327164641_user_defaults/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateTable
2 | CREATE TABLE "Account" (
3 | "id" TEXT NOT NULL,
4 | "userId" TEXT NOT NULL,
5 | "type" TEXT NOT NULL,
6 | "provider" TEXT NOT NULL,
7 | "providerAccountId" TEXT NOT NULL,
8 | "refresh_token" TEXT,
9 | "access_token" TEXT,
10 | "expires_at" INTEGER,
11 | "token_type" TEXT,
12 | "scope" TEXT,
13 | "id_token" TEXT,
14 | "session_state" TEXT,
15 |
16 | CONSTRAINT "Account_pkey" PRIMARY KEY ("id")
17 | );
18 |
19 | -- CreateTable
20 | CREATE TABLE "Session" (
21 | "id" TEXT NOT NULL,
22 | "sessionToken" TEXT NOT NULL,
23 | "userId" TEXT NOT NULL,
24 | "expires" TIMESTAMP(3) NOT NULL,
25 |
26 | CONSTRAINT "Session_pkey" PRIMARY KEY ("id")
27 | );
28 |
29 | -- CreateTable
30 | CREATE TABLE "User" (
31 | "id" TEXT NOT NULL,
32 | "name" TEXT,
33 | "email" TEXT,
34 | "emailVerified" TIMESTAMP(3),
35 | "image" TEXT,
36 |
37 | CONSTRAINT "User_pkey" PRIMARY KEY ("id")
38 | );
39 |
40 | -- CreateTable
41 | CREATE TABLE "VerificationToken" (
42 | "identifier" TEXT NOT NULL,
43 | "token" TEXT NOT NULL,
44 | "expires" TIMESTAMP(3) NOT NULL
45 | );
46 |
47 | -- CreateIndex
48 | CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId");
49 |
50 | -- CreateIndex
51 | CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken");
52 |
53 | -- CreateIndex
54 | CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
55 |
56 | -- CreateIndex
57 | CREATE UNIQUE INDEX "VerificationToken_token_key" ON "VerificationToken"("token");
58 |
59 | -- CreateIndex
60 | CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token");
61 |
62 | -- AddForeignKey
63 | ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
64 |
65 | -- AddForeignKey
66 | ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
67 |
--------------------------------------------------------------------------------
/prisma/migrations/20230327201911_prompt_templates/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateTable
2 | CREATE TABLE "PromptTemplate" (
3 | "id" SERIAL NOT NULL,
4 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
5 | "updatedAt" TIMESTAMP(3) NOT NULL,
6 | "prompt" TEXT NOT NULL,
7 | "userId" TEXT NOT NULL,
8 | "inputs" JSONB NOT NULL,
9 | "name" TEXT NOT NULL,
10 |
11 | CONSTRAINT "PromptTemplate_pkey" PRIMARY KEY ("id")
12 | );
13 |
14 | -- AddForeignKey
15 | ALTER TABLE "PromptTemplate" ADD CONSTRAINT "PromptTemplate_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
16 |
--------------------------------------------------------------------------------
/prisma/migrations/20230329205257_chatbots/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateTable
2 | CREATE TABLE "Chatbot" (
3 | "id" SERIAL NOT NULL,
4 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
5 | "updatedAt" TIMESTAMP(3) NOT NULL,
6 | "userId" TEXT NOT NULL,
7 | "promtTemplateId" INTEGER,
8 | "name" TEXT NOT NULL,
9 |
10 | CONSTRAINT "Chatbot_pkey" PRIMARY KEY ("id")
11 | );
12 |
13 | -- AddForeignKey
14 | ALTER TABLE "Chatbot" ADD CONSTRAINT "Chatbot_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
15 |
16 | -- AddForeignKey
17 | ALTER TABLE "Chatbot" ADD CONSTRAINT "Chatbot_promtTemplateId_fkey" FOREIGN KEY ("promtTemplateId") REFERENCES "PromptTemplate"("id") ON DELETE CASCADE ON UPDATE CASCADE;
18 |
--------------------------------------------------------------------------------
/prisma/migrations/20230406080854_chatbot_message/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateTable
2 | CREATE TABLE "ChatbotMessage" (
3 | "id" SERIAL NOT NULL,
4 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
5 | "updatedAt" TIMESTAMP(3) NOT NULL,
6 | "chatbotId" INTEGER,
7 | "message" VARCHAR NOT NULL,
8 | "agent" TEXT NOT NULL,
9 |
10 | CONSTRAINT "ChatbotMessage_pkey" PRIMARY KEY ("id")
11 | );
12 |
13 | -- AddForeignKey
14 | ALTER TABLE "ChatbotMessage" ADD CONSTRAINT "ChatbotMessage_chatbotId_fkey" FOREIGN KEY ("chatbotId") REFERENCES "Chatbot"("id") ON DELETE CASCADE ON UPDATE CASCADE;
15 |
--------------------------------------------------------------------------------
/prisma/migrations/20230409152256_chatbot_prompt_template/migration.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Warnings:
3 |
4 | - You are about to drop the column `promtTemplateId` on the `Chatbot` table. All the data in the column will be lost.
5 |
6 | */
7 | -- DropForeignKey
8 | ALTER TABLE "Chatbot" DROP CONSTRAINT "Chatbot_promtTemplateId_fkey";
9 |
10 | -- AlterTable
11 | ALTER TABLE "Chatbot" DROP COLUMN "promtTemplateId",
12 | ADD COLUMN "promptTemplateId" INTEGER;
13 |
14 | -- AddForeignKey
15 | ALTER TABLE "Chatbot" ADD CONSTRAINT "Chatbot_promptTemplateId_fkey" FOREIGN KEY ("promptTemplateId") REFERENCES "PromptTemplate"("id") ON DELETE CASCADE ON UPDATE CASCADE;
16 |
--------------------------------------------------------------------------------
/prisma/migrations/20230428114658_/migration.sql:
--------------------------------------------------------------------------------
1 | -- DropForeignKey
2 | ALTER TABLE "Chatbot" DROP CONSTRAINT "Chatbot_promptTemplateId_fkey";
3 |
4 | -- AlterTable
5 | ALTER TABLE "Chatbot" ADD COLUMN "datasourceId" INTEGER;
6 |
7 | -- CreateTable
8 | CREATE TABLE "Datasource" (
9 | "id" SERIAL NOT NULL,
10 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
11 | "updatedAt" TIMESTAMP(3) NOT NULL,
12 | "userId" TEXT NOT NULL,
13 | "url" TEXT NOT NULL,
14 | "name" TEXT NOT NULL,
15 | "type" TEXT NOT NULL,
16 |
17 | CONSTRAINT "Datasource_pkey" PRIMARY KEY ("id")
18 | );
19 |
20 | -- AddForeignKey
21 | ALTER TABLE "Datasource" ADD CONSTRAINT "Datasource_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
22 |
23 | -- AddForeignKey
24 | ALTER TABLE "Chatbot" ADD CONSTRAINT "Chatbot_promptTemplateId_fkey" FOREIGN KEY ("promptTemplateId") REFERENCES "PromptTemplate"("id") ON DELETE SET NULL ON UPDATE CASCADE;
25 |
26 | -- AddForeignKey
27 | ALTER TABLE "Chatbot" ADD CONSTRAINT "Chatbot_datasourceId_fkey" FOREIGN KEY ("datasourceId") REFERENCES "Datasource"("id") ON DELETE SET NULL ON UPDATE CASCADE;
28 |
--------------------------------------------------------------------------------
/prisma/migrations/migration_lock.toml:
--------------------------------------------------------------------------------
1 | # Please do not edit this file manually
2 | # It should be added in your version-control system (i.e. Git)
3 | provider = "postgresql"
--------------------------------------------------------------------------------
/prisma/schema.prisma:
--------------------------------------------------------------------------------
1 | // This is your Prisma schema file,
2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema
3 |
4 | generator client {
5 | provider = "prisma-client-js"
6 | }
7 |
8 | datasource db {
9 | provider = "postgresql"
10 | url = env("DATABASE_URL")
11 | }
12 |
13 | model User {
14 | id String @id @default(cuid())
15 | name String?
16 | email String? @unique
17 | emailVerified DateTime?
18 | image String?
19 | accounts Account[]
20 | sessions Session[]
21 | PromptTemplate PromptTemplate[]
22 | Chatbot Chatbot[]
23 | Datasource Datasource[]
24 | }
25 |
26 | model Account {
27 | id String @id @default(cuid())
28 | userId String
29 | type String
30 | provider String
31 | providerAccountId String
32 | refresh_token String? @db.Text
33 | access_token String? @db.Text
34 | expires_at Int?
35 | token_type String?
36 | scope String?
37 | id_token String? @db.Text
38 | session_state String?
39 |
40 | user User @relation(fields: [userId], references: [id], onDelete: Cascade)
41 |
42 | @@unique([provider, providerAccountId])
43 | }
44 |
45 | model Session {
46 | id String @id @default(cuid())
47 | sessionToken String @unique
48 | userId String
49 | expires DateTime
50 | user User @relation(fields: [userId], references: [id], onDelete: Cascade)
51 | }
52 |
53 | model VerificationToken {
54 | identifier String
55 | token String @unique
56 | expires DateTime
57 |
58 | @@unique([identifier, token])
59 | }
60 |
61 | model PromptTemplate {
62 | id Int @id @default(autoincrement())
63 | createdAt DateTime @default(now())
64 | updatedAt DateTime @updatedAt
65 | prompt String
66 | user User @relation(fields: [userId], references: [id], onDelete: Cascade)
67 | userId String
68 | inputs Json
69 | name String
70 | Chatbot Chatbot[]
71 | }
72 |
73 | model Datasource {
74 | id Int @id @default(autoincrement())
75 | createdAt DateTime @default(now())
76 | updatedAt DateTime @updatedAt
77 | user User @relation(fields: [userId], references: [id], onDelete: Cascade)
78 | userId String
79 | url String
80 | name String
81 | type String
82 | Chatbot Chatbot[]
83 | }
84 |
85 | model Chatbot {
86 | id Int @id @default(autoincrement())
87 | createdAt DateTime @default(now())
88 | updatedAt DateTime @updatedAt
89 | user User @relation(fields: [userId], references: [id], onDelete: Cascade)
90 | userId String
91 | promptTemplate PromptTemplate? @relation(fields: [promptTemplateId], references: [id])
92 | promptTemplateId Int?
93 | datasource Datasource? @relation(fields: [datasourceId], references: [id])
94 | datasourceId Int?
95 | name String
96 | ChatbotMessage ChatbotMessage[]
97 | }
98 |
99 | model ChatbotMessage {
100 | id Int @id @default(autoincrement())
101 | createdAt DateTime @default(now())
102 | updatedAt DateTime @updatedAt
103 | chatbot Chatbot? @relation(fields: [chatbotId], references: [id], onDelete: Cascade)
104 | chatbotId Int?
105 | message String @db.VarChar()
106 | agent String
107 | }
108 |
--------------------------------------------------------------------------------
/public/chatbot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/homanp/langchain-ui/75614100a9a28ae2d4bc9d16c58ed971d9cd3c4e/public/chatbot.png
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/thirteen.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/homanp/langchain-ui/75614100a9a28ae2d4bc9d16c58ed971d9cd3c4e/public/user.png
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------