├── .gitignore
├── LICENSE
├── README.md
├── assets
├── intellichat-demo.mp4
└── intellichat-screenshot-v1.png
└── intellichat
├── .dockerignore
├── .env.example
├── .eslintrc.json
├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── .prettierignore
├── .prettierrc.json
├── Dockerfile
├── README.md
├── components.json
├── intellinode.d.ts
├── next.config.js
├── package.json
├── postcss.config.js
├── public
├── next.svg
└── vercel.svg
├── src
├── app
│ ├── api
│ │ ├── chat
│ │ │ └── route.ts
│ │ └── route.ts
│ ├── favicon.ico
│ ├── globals.css
│ ├── layout.tsx
│ └── page.tsx
├── components
│ ├── apikey-input.tsx
│ ├── chat-message.tsx
│ ├── chat-panel.tsx
│ ├── chat-prompt.tsx
│ ├── chat-settings.tsx
│ ├── chat.tsx
│ ├── field-group.tsx
│ ├── field-tooltip.tsx
│ ├── form-ui.tsx
│ ├── shared
│ │ ├── container.tsx
│ │ ├── header.tsx
│ │ ├── logo.tsx
│ │ ├── providers.tsx
│ │ └── sidebar.tsx
│ └── ui
│ │ ├── button.tsx
│ │ ├── collapsible.tsx
│ │ ├── form.tsx
│ │ ├── input.tsx
│ │ ├── label.tsx
│ │ ├── popover.tsx
│ │ ├── scroll-area.tsx
│ │ ├── select.tsx
│ │ ├── separator.tsx
│ │ ├── sheet.tsx
│ │ ├── switch.tsx
│ │ ├── textarea.tsx
│ │ ├── toast.tsx
│ │ ├── toaster.tsx
│ │ ├── tooltip.tsx
│ │ └── use-toast.ts
├── lib
│ ├── ai-providers.ts
│ ├── helpers.ts
│ ├── intellinode.ts
│ ├── schema.ts
│ ├── types.ts
│ ├── utils.ts
│ └── validators.ts
└── store
│ └── chat-settings.ts
├── tailwind.config.js
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 | .pnpm-debug.log*
9 |
10 | # Diagnostic reports (https://nodejs.org/api/report.html)
11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
12 |
13 | # Runtime data
14 | pids
15 | *.pid
16 | *.seed
17 | *.pid.lock
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 | *.lcov
25 |
26 | # nyc test coverage
27 | .nyc_output
28 |
29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
30 | .grunt
31 |
32 | # Bower dependency directory (https://bower.io/)
33 | bower_components
34 |
35 | # node-waf configuration
36 | .lock-wscript
37 |
38 | # Compiled binary addons (https://nodejs.org/api/addons.html)
39 | build/Release
40 |
41 | # Dependency directories
42 | node_modules/
43 | jspm_packages/
44 |
45 | # Snowpack dependency directory (https://snowpack.dev/)
46 | web_modules/
47 |
48 | # TypeScript cache
49 | *.tsbuildinfo
50 |
51 | # Optional npm cache directory
52 | .npm
53 |
54 | # Optional eslint cache
55 | .eslintcache
56 |
57 | # Optional stylelint cache
58 | .stylelintcache
59 |
60 | # Microbundle cache
61 | .rpt2_cache/
62 | .rts2_cache_cjs/
63 | .rts2_cache_es/
64 | .rts2_cache_umd/
65 |
66 | # Optional REPL history
67 | .node_repl_history
68 |
69 | # Output of 'npm pack'
70 | *.tgz
71 |
72 | # Yarn Integrity file
73 | .yarn-integrity
74 |
75 | # dotenv environment variable files
76 | .env
77 | .env.development.local
78 | .env.test.local
79 | .env.production.local
80 | .env.local
81 |
82 | # parcel-bundler cache (https://parceljs.org/)
83 | .cache
84 | .parcel-cache
85 |
86 | # Next.js build output
87 | .next
88 | out
89 |
90 | # Nuxt.js build / generate output
91 | .nuxt
92 | dist
93 |
94 | # Gatsby files
95 | .cache/
96 | # Comment in the public line in if your project uses Gatsby and not Next.js
97 | # https://nextjs.org/blog/next-9-1#public-directory-support
98 | # public
99 |
100 | # vuepress build output
101 | .vuepress/dist
102 |
103 | # vuepress v2.x temp and cache directory
104 | .temp
105 | .cache
106 |
107 | # Docusaurus cache and generated files
108 | .docusaurus
109 |
110 | # Serverless directories
111 | .serverless/
112 |
113 | # FuseBox cache
114 | .fusebox/
115 |
116 | # DynamoDB Local files
117 | .dynamodb/
118 |
119 | # TernJS port file
120 | .tern-port
121 |
122 | # Stores VSCode versions used for testing VSCode extensions
123 | .vscode-test
124 |
125 | # yarn v2
126 | .yarn/cache
127 | .yarn/unplugged
128 | .yarn/build-state.yml
129 | .yarn/install-state.gz
130 | .pnp.*
131 |
132 | # custom
133 | */.idea/*
134 | .idea/*
135 | intellichat/instructions.txt
136 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 IntelliNode
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 all
13 | 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 THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # IntelliChat
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | IntelliChat is an open-source AI chatbot built with [IntelliNode](https://github.com/intelligentnode/IntelliNode) and Next.js. It is designed to accelerate the integration of multiple language models into chatbot apps.
12 |
13 |
14 |
15 | https://github.com/intelligentnode/IntelliChat/assets/2751950/47d7db12-e299-449f-9351-39185c659d84
16 |
17 |
18 |
19 | ## Features
20 |
21 | - Select your preferred AI Provider and model from the UI.
22 | - **OpenAI ChatGPT**: o1, o3-mini, gpt-4o.
23 | - **Google Gemini**.
24 | - **Azure Openai**.
25 | - **Cohere Coral**.
26 | - **Replicate**: Llama (70b-chat, 13b-chat, 34b-code, 34b-python 13b-code-instruct).
27 | - **Mistral AI**: Open-weight models.
28 | - **Anthropic**: claude 3.5
29 | - **vLLM**: any local model.
30 | - Manage your API keys via the UI.
31 | - Access your data using intellinode one key.
32 |
33 |
34 | ## Installing and Running the App
35 |
36 | 1. `cd intellichat`.
37 | 2. Install the dependencies: `pnpm install` or `npm install` or `yarn install`.
38 | 3. Start the Next.js server `pnpm dev` or `npm run dev` or `yarn dev`.
39 |
40 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
41 |
42 | ---
43 |
44 |
45 |
46 | **Built with:** [Intellinode](https://github.com/intelligentnode/IntelliNode), [Next.js](https://nextjs.org/), [Shadcn](https://ui.shadcn.com/), and [TailwindCSS](https://tailwindcss.com/).
47 |
--------------------------------------------------------------------------------
/assets/intellichat-demo.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/intelligentnode/IntelliChat/c741d1cfda9fd9120adc81a8e6c616541f25b7bc/assets/intellichat-demo.mp4
--------------------------------------------------------------------------------
/assets/intellichat-screenshot-v1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/intelligentnode/IntelliChat/c741d1cfda9fd9120adc81a8e6c616541f25b7bc/assets/intellichat-screenshot-v1.png
--------------------------------------------------------------------------------
/intellichat/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .next
3 | .git
4 | .gitignore
5 | .DS_Store
6 | instructions.txt
7 |
--------------------------------------------------------------------------------
/intellichat/.env.example:
--------------------------------------------------------------------------------
1 | OPENAI_API_KEY=
2 | REPLICATE_API_KEY=
--------------------------------------------------------------------------------
/intellichat/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["next/core-web-vitals", "plugin:jsx-a11y/recommended", "prettier"],
3 | "plugins": ["jsx-a11y", "react-hooks"],
4 | "rules": {
5 | "react-hooks/rules-of-hooks": "error",
6 | "react-hooks/exhaustive-deps": "error"
7 | }
8 | }
9 |
10 |
--------------------------------------------------------------------------------
/intellichat/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 |
9 | steps:
10 | - uses: actions/checkout@v2
11 |
12 | - name: Set up Node.js
13 | uses: actions/setup-node@v2
14 |
15 | - name: Install dependencies
16 | run: pnpm install --frozen-lockfile
17 |
18 | - name: Typecheck
19 | run: pnpm run typecheck
20 |
21 | - name: Run lint
22 | run: pnpm run lint
23 |
--------------------------------------------------------------------------------
/intellichat/.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 |
27 | # local env files
28 | .env*.local
29 |
30 | # vercel
31 | .vercel
32 |
33 | # typescript
34 | *.tsbuildinfo
35 | next-env.d.ts
36 |
37 | # lockfiles
38 | pnpm-lock.yaml
39 | yarn.lock
40 | package-lock.json
--------------------------------------------------------------------------------
/intellichat/.prettierignore:
--------------------------------------------------------------------------------
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 |
27 | # local env files
28 | .env*.local
29 |
30 | # vercel
31 | .vercel
32 |
33 | # typescript
34 | *.tsbuildinfo
35 | next-env.d.ts
36 |
--------------------------------------------------------------------------------
/intellichat/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "semi": true,
4 | "singleQuote": true,
5 | "jsxSingleQuote": true,
6 | "plugins": [
7 | "prettier-plugin-tailwindcss"
8 | ]
9 | }
--------------------------------------------------------------------------------
/intellichat/Dockerfile:
--------------------------------------------------------------------------------
1 | # ----------------------------------------
2 | # 1) Base Image
3 | # ----------------------------------------
4 | FROM node:18-alpine AS base
5 | RUN apk add --no-cache libc6-compat
6 | WORKDIR /app
7 |
8 | # ----------------------------------------
9 | # 2) Dependencies (deps)
10 | # ----------------------------------------
11 | FROM base AS deps
12 | COPY package.json pnpm-lock.yaml* ./
13 | RUN npm install -g pnpm
14 | RUN pnpm install --frozen-lockfile
15 |
16 | # ----------------------------------------
17 | # 3) Builder
18 | # ----------------------------------------
19 | FROM base AS builder
20 | WORKDIR /app
21 | # Install pnpm so the build command is available
22 | RUN npm install -g pnpm
23 | # Copy node_modules from deps
24 | COPY --from=deps /app/node_modules ./node_modules
25 | # Copy source code
26 | COPY . .
27 | # Build the Next.js app
28 | RUN pnpm build
29 |
30 | # ----------------------------------------
31 | # 4) Production Runner
32 | # ----------------------------------------
33 | FROM base AS runner
34 | WORKDIR /app
35 | ENV NODE_ENV=production
36 | RUN addgroup -g 1001 -S nodejs && adduser -S nextjs -u 1001
37 | # Install pnpm in runner stage (if needed by any command)
38 | RUN npm install -g pnpm
39 | # Copy package manifest and installed node_modules from builder stage
40 | COPY --from=builder /app/package.json ./
41 | COPY --from=builder /app/node_modules ./node_modules
42 | # Copy build output and public assets
43 | COPY --from=builder /app/public ./public
44 | COPY --from=builder /app/.next ./.next
45 | COPY --from=builder /app/.env ./.env
46 |
47 | USER nextjs
48 | EXPOSE 3000
49 | ENV PORT=3000
50 | ENV HOSTNAME=0.0.0.0
51 | CMD ["pnpm", "start"]
52 |
--------------------------------------------------------------------------------
/intellichat/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | 1. Install the dependencies: `pnpm install` or `npm install` or `yarn install`.
8 | 2. Start the Next.js server `pnpm dev` or `npm run` dev or `yarn dev`.
9 |
10 |
11 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
12 |
13 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
14 |
15 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
16 |
17 | ## Learn More
18 |
19 | To learn more about Next.js, take a look at the following resources:
20 |
21 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
22 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
23 |
24 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
25 |
26 | ## Deploy on Vercel
27 |
28 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
29 |
30 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
31 |
--------------------------------------------------------------------------------
/intellichat/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": "app/globals.css",
9 | "baseColor": "zinc",
10 | "cssVariables": true
11 | },
12 | "aliases": {
13 | "components": "@/components",
14 | "utils": "@/lib/utils"
15 | }
16 | }
--------------------------------------------------------------------------------
/intellichat/intellinode.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'intellinode' {
2 | type SupportedChatModels =
3 | | 'openai'
4 | | 'replicate'
5 | | 'sagemaker'
6 | | 'azure'
7 | | 'gemini'
8 | | 'cohere'
9 | | 'mistral'
10 | | 'anthropic'
11 | | 'vllm';
12 |
13 | class Chatbot {
14 | constructor(
15 | keyValue?: string,
16 | provider?: string,
17 | customProxy?: ProxyHelper | null,
18 | options?: {
19 | oneKey?: string;
20 | intelliBase?: string;
21 | }
22 | );
23 | chat(
24 | modelInput?:
25 | | ChatGPTInput
26 | | LLamaReplicateInput
27 | | CohereInput
28 | | GeminiInput
29 | | AnthropicInput
30 | | VLLMInput
31 | );
32 |
33 | stream(
34 | modelInput?:
35 | | ChatGPTInput
36 | | LLamaReplicateInput
37 | | CohereInput
38 | | GeminiInput
39 | | AnthropicInput
40 | | VLLMInput
41 | );
42 | }
43 |
44 | class ChatGPTInput {
45 | model: string = 'gpt-4o-mini';
46 | temperature: number = 1;
47 | maxTokens: number | null = null;
48 | numberOfOutputs: number = 1;
49 |
50 | constructor(
51 | systemMessage: string,
52 | options?: {
53 | model?: string;
54 | temperature?: number;
55 | maxTokens?: number;
56 | numberOfOutputs?: number;
57 | attachReference?: boolean;
58 | }
59 | );
60 |
61 | addMessage(message: ChatGPTMessage);
62 | addUserMessage(message: string): void;
63 | addAssistantMessage(message: string): void;
64 | }
65 | class ChatGPTMessage {
66 | constructor(message: string, role: string);
67 | }
68 |
69 | class LLamaReplicateInput {
70 | constructor(
71 | message: string,
72 | options?: {
73 | model?: string;
74 | attachReference?: boolean;
75 | }
76 | );
77 |
78 | addUserMessage(message: string): void;
79 | addAssistantMessage(message: string): void;
80 | }
81 |
82 | class CohereInput {
83 | constructor(
84 | message: string,
85 | options?: {
86 | model?: string;
87 | web?: boolean;
88 | attachReference?: boolean;
89 | }
90 | );
91 |
92 | addUserMessage(message: string): void;
93 | addAssistantMessage(message: string): void;
94 | }
95 |
96 | class GeminiInput {
97 | constructor(
98 | message: string,
99 | options?: {
100 | model?: string;
101 | attachReference?: boolean;
102 | }
103 | );
104 |
105 | addUserMessage(message: string): void;
106 | addAssistantMessage(message: string): void;
107 | }
108 |
109 | class MistralInput {
110 | constructor(
111 | message: string,
112 | options?: {
113 | model?: string;
114 | attachReference?: boolean;
115 | }
116 | );
117 |
118 | addUserMessage(message: string): void;
119 | addAssistantMessage(message: string): void;
120 | }
121 |
122 | class AnthropicInput {
123 | constructor(
124 | message: string,
125 | options?: {
126 | model?: string;
127 | attachReference?: boolean;
128 | }
129 | );
130 |
131 | addUserMessage(message: string): void;
132 | addAssistantMessage(message: string): void;
133 | }
134 |
135 | class VLLMInput {
136 | constructor(
137 | systemMessage: string,
138 | options?: {
139 | model?: string;
140 | // e.g. maxTokens, temperature, attachReference, etc.
141 | maxTokens?: number;
142 | temperature?: number;
143 | attachReference?: boolean;
144 | }
145 | );
146 | addUserMessage(message: string): void;
147 | addAssistantMessage(message: string): void;
148 | }
149 |
150 | class ChatContext {
151 | constructor(
152 | apiKey: string,
153 | provider?: SupportedChatModels,
154 | customProxy?: ProxyHelper | null
155 | );
156 | getRoleContext(
157 | userMessage: string,
158 | historyMessages: { role: 'user' | 'assistant'; content: string }[],
159 | n: number,
160 | embeddingName?: string | null
161 | );
162 | }
163 | class ProxyHelper {
164 | static getInstance(): ProxyHelper;
165 | setAzureOpenai(resourceName: string): void;
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/intellichat/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | typescript: {
4 | ignoreBuildErrors: true,
5 | },
6 | eslint: {
7 | ignoreDuringBuilds: true,
8 | },
9 | swcMinify: true,
10 | };
11 |
12 | module.exports = nextConfig;
13 |
--------------------------------------------------------------------------------
/intellichat/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "intellinext",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint",
10 | "typecheck": "tsc"
11 | },
12 | "dependencies": {
13 | "@hookform/resolvers": "^3.3.1",
14 | "@radix-ui/react-collapsible": "1.0.3",
15 | "@radix-ui/react-dialog": "1.0.4",
16 | "@radix-ui/react-label": "2.0.2",
17 | "@radix-ui/react-popover": "^1.0.7",
18 | "@radix-ui/react-scroll-area": "^1.0.4",
19 | "@radix-ui/react-select": "1.2.2",
20 | "@radix-ui/react-separator": "1.0.3",
21 | "@radix-ui/react-slot": "1.0.2",
22 | "@radix-ui/react-switch": "^1.0.3",
23 | "@radix-ui/react-toast": "1.1.4",
24 | "@radix-ui/react-tooltip": "^1.0.6",
25 | "@tanstack/react-query": "4.33.0",
26 | "@types/node": "20.5.9",
27 | "@types/react": "18.2.21",
28 | "@types/react-dom": "18.2.7",
29 | "autoprefixer": "10.4.15",
30 | "class-variance-authority": "0.7.0",
31 | "clsx": "2.0.0",
32 | "encoding": "^0.1.13",
33 | "eslint": "8.48.0",
34 | "eslint-config-next": "13.4.19",
35 | "intellinode": "^2.2.9",
36 | "lucide-react": "0.274.0",
37 | "nanoid": "4.0.2",
38 | "next": "13.4.19",
39 | "postcss": "8.4.29",
40 | "react": "18.2.0",
41 | "react-dom": "18.2.0",
42 | "react-github-btn": "^1.4.0",
43 | "react-hook-form": "^7.46.1",
44 | "react-markdown": "8.0.7",
45 | "tailwind-merge": "1.14.0",
46 | "tailwindcss": "3.3.3",
47 | "tailwindcss-animate": "1.0.7",
48 | "typescript": "5.2.2",
49 | "zod": "3.22.2",
50 | "zustand": "4.4.1"
51 | },
52 | "devDependencies": {
53 | "@tailwindcss/typography": "0.5.10",
54 | "eslint-config-prettier": "9.0.0",
55 | "eslint-plugin-jsx-a11y": "6.7.1",
56 | "eslint-plugin-react-hooks": "4.6.0",
57 | "prettier": "3.0.3",
58 | "prettier-plugin-tailwindcss": "0.5.4"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/intellichat/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/intellichat/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/intellichat/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/intellichat/src/app/api/chat/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server';
2 | import { chatbotValidator } from '@/lib/validators';
3 | import {
4 | getAzureChatResponse,
5 | getChatProviderKey,
6 | getChatResponse,
7 | getDefaultProviderKey,
8 | } from '@/lib/intellinode';
9 | import { serializeError } from '@/lib/helpers';
10 |
11 | const defaultSystemMessage =
12 | 'You are a helpful assistant. Format response in Markdown where needed.';
13 | const defaultProvider = 'openai';
14 |
15 | function extractJsonFromString(str: string): any[] {
16 | try {
17 | const jsonStart = str.indexOf('{');
18 | if (jsonStart !== -1) {
19 | const jsonPart = str.slice(jsonStart);
20 | return [JSON.parse(jsonPart)];
21 | }
22 | } catch {
23 | // Ignore JSON parsing errors
24 | }
25 | return [];
26 | }
27 |
28 | export async function POST(req: Request) {
29 | const json = await req.json();
30 | const parsedJson : any = chatbotValidator.safeParse(json);
31 |
32 | if (!parsedJson.success) {
33 | const { error } = parsedJson;
34 | return NextResponse.json({ error: error.message }, { status: 400 });
35 | }
36 |
37 | const {
38 | messages,
39 | providers,
40 | provider,
41 | systemMessage = defaultSystemMessage,
42 | n = 2,
43 | withContext,
44 | intellinodeData,
45 | oneKey,
46 | stream: streamResponse,
47 | } = parsedJson.data;
48 |
49 | const isVllm = (provider === 'vllm');
50 |
51 | const key = isVllm
52 | ? null
53 | : (provider && providers[provider]?.apiKey) ||
54 | getChatProviderKey(provider) ||
55 | getDefaultProviderKey(provider, oneKey);
56 |
57 | if (!isVllm && !key) {
58 | console.log('error');
59 | const missingKeyError = `no api key provided for ${provider} ...`;
60 | return NextResponse.json({ error: missingKeyError }, { status: 400 });
61 | }
62 |
63 | const contextKey = providers.openai?.apiKey || getChatProviderKey('openai');
64 |
65 | if (withContext && !contextKey) {
66 | const missingContextKey = `OpenAi key was not provided, either add it to your .env file or in the chat settings`;
67 | return NextResponse.json({ error: missingContextKey }, { status: 400 });
68 | }
69 |
70 | if (intellinodeData && !oneKey) {
71 | const missingOneKey = `oneKey is required when intellinodeData is enabled`;
72 | return NextResponse.json({ error: missingOneKey }, { status: 400 });
73 | }
74 |
75 | const chatSystemMessage =
76 | systemMessage.trim() !== '' ? systemMessage : defaultSystemMessage;
77 | const chatProvider = provider || defaultProvider;
78 | const chatProviderProps = providers[chatProvider];
79 |
80 | try {
81 | if (chatProvider === 'azure' && providers.azure) {
82 | const responses = await getAzureChatResponse({
83 | provider: { ...providers.azure, apiKey: key },
84 | systemMessage: chatSystemMessage,
85 | withContext,
86 | messages,
87 | n,
88 | oneKey: intellinodeData ? oneKey : undefined,
89 | });
90 | return NextResponse.json({ response: responses });
91 | } else if (chatProviderProps && chatProviderProps?.name !== 'azure') {
92 | let shouldStream = (chatProviderProps.name === 'openai' || chatProviderProps.name === 'cohere' || chatProviderProps.name === 'vllm') && req.headers.get('Accept') === 'text/event-stream';
93 |
94 | if (shouldStream) {
95 | const encoder = new TextEncoder();
96 | const stream = new TransformStream();
97 | const writer = stream.writable.getWriter();
98 |
99 | // Start the streaming response
100 | getChatResponse({
101 | provider: { ...chatProviderProps, apiKey: key },
102 | systemMessage: chatSystemMessage,
103 | withContext,
104 | contextKey,
105 | messages,
106 | n,
107 | stream : streamResponse,
108 | oneKey: intellinodeData ? oneKey : undefined,
109 | intellinodeData,
110 | intelliBase: intellinodeData ? process.env.CUSTOM_INTELLIBASE_URL : undefined,
111 | onChunk: async (chunk: string) => {
112 | try {
113 | // Ensure proper SSE format
114 | const data = `${chunk}`;
115 | await writer.write(encoder.encode(data));
116 | } catch (error) {
117 | console.error('Error writing chunk:', error);
118 | throw error;
119 | }
120 | },
121 | }).then(async () => {
122 | // Send end message and close the stream
123 | // await writer.write(encoder.encode('[DONE]\n\n'));
124 | await writer.close();
125 | }).catch(async (error) => {
126 | console.error('Streaming error:', error);
127 | // Safely serialize the error before sending to client
128 | try {
129 | const safeError = serializeError(error);
130 | let errMsg = (safeError as any).error?.message || safeError.message || '';
131 | const extracted = extractJsonFromString(errMsg);
132 | if (extracted.length && extracted[0]?.error?.message) {
133 | errMsg = extracted[0].error.message;
134 | }
135 | await writer.write(
136 | encoder.encode(errMsg || 'Something went wrong; unable to generate a response.')
137 | );
138 | } finally {
139 | await writer.close();
140 | }
141 | });;
142 |
143 | return new Response(stream.readable, {
144 | headers: {
145 | 'Content-Type': 'text/event-stream',
146 | 'Cache-Control': 'no-cache',
147 | 'Connection': 'keep-alive',
148 | },
149 | });
150 | } else {
151 | // Non-streaming response remains the same
152 | const responses = await getChatResponse({
153 | provider: { ...chatProviderProps, apiKey: key, baseUrl: chatProviderProps.baseUrl },
154 | systemMessage: chatSystemMessage,
155 | withContext,
156 | contextKey,
157 | messages,
158 | stream : false,
159 | n,
160 | oneKey: intellinodeData ? oneKey : undefined,
161 | intellinodeData,
162 | });
163 |
164 | if (Array.isArray(responses)) {
165 | // If the response is an array (as with vLLM), wrap it accordingly.
166 | return NextResponse.json({
167 | response: responses,
168 | references: null,
169 | });
170 | } else {
171 | return NextResponse.json({
172 | response: responses.result,
173 | references: responses.references,
174 | });
175 | }
176 | }
177 | }
178 | } catch (e) {
179 | console.error('Error:', e);
180 | const defaultErrorMsg = 'invalid api key or provider';
181 | try {
182 | const safeError = serializeError(e);
183 | let errMsg = (safeError as any).error?.message || safeError.message || '';
184 | const extracted = extractJsonFromString(errMsg);
185 | if (extracted.length && extracted[0]?.error?.message) {
186 | errMsg = extracted[0].error.message;
187 | }
188 | return NextResponse.json({ error: errMsg || defaultErrorMsg }, { status: 400 });
189 | } catch {
190 | return NextResponse.json({ error: defaultErrorMsg }, { status: 400 });
191 | }
192 | }
193 | }
194 |
195 | export const maxDuration = 180;
196 |
--------------------------------------------------------------------------------
/intellichat/src/app/api/route.ts:
--------------------------------------------------------------------------------
1 | import { getChatProviderKey } from '@/lib/intellinode';
2 | import { NextResponse } from 'next/server';
3 |
4 | // Check if the user has set up their API keys for OpenAI and Replicate in .env
5 | export async function GET() {
6 | const OpenAIKey = getChatProviderKey('openai');
7 | const ReplicateKey = getChatProviderKey('replicate');
8 | const CohereKey = getChatProviderKey('cohere');
9 | const GoogleKey = getChatProviderKey('google');
10 | const AzureKey = getChatProviderKey('azure');
11 | const MistralKey = getChatProviderKey('mistral');
12 | const anthropicKey = getChatProviderKey('anthropic');
13 |
14 | return NextResponse.json({
15 | openai: !!OpenAIKey,
16 | replicate: !!ReplicateKey,
17 | cohere: !!CohereKey,
18 | google: !!GoogleKey,
19 | azure: !!AzureKey,
20 | mistral: !!MistralKey,
21 | anthropic: !!anthropicKey,
22 | });
23 | }
24 |
--------------------------------------------------------------------------------
/intellichat/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/intelligentnode/IntelliChat/c741d1cfda9fd9120adc81a8e6c616541f25b7bc/intellichat/src/app/favicon.ico
--------------------------------------------------------------------------------
/intellichat/src/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | :root {
7 | --background: 240 5% 17%;
8 | --foreground: 0 0% 100%;
9 |
10 | --card: 0 0% 100%;
11 | --card-foreground: 240 10% 3.9%;
12 |
13 | --popover: 0 0% 100%;
14 | --popover-foreground: 240 10% 3.9%;
15 |
16 | --primary: 210 99% 58%;
17 | --primary-foreground: 0 0% 98%;
18 |
19 | --secondary: 257 81% 79%;
20 | --secondary-foreground: 240 5.9% 10%;
21 |
22 | --muted: 240 4.8% 95.9%;
23 | --muted-foreground: 240 3.8% 46.1%;
24 |
25 | --accent: 240 4.8% 95.9%;
26 | --accent-foreground: 240 5.9% 10%;
27 |
28 | --destructive: 0 84.2% 60.2%;
29 | --destructive-foreground: 0 0% 98%;
30 |
31 | --border: 240 5.9% 90%;
32 | --input: 0 0% 95%;
33 | --ring: 240 10% 3.9%;
34 |
35 | --radius: 0.5rem;
36 |
37 | --header-height: 104px;
38 | }
39 |
40 | .dark {
41 | --background: 240 10% 3.9%;
42 | --foreground: 0 0% 98%;
43 |
44 | --card: 240 10% 3.9%;
45 | --card-foreground: 0 0% 98%;
46 |
47 | --popover: 240 10% 3.9%;
48 | --popover-foreground: 0 0% 98%;
49 |
50 | --primary: 0 0% 98%;
51 | --primary-foreground: 240 5.9% 10%;
52 |
53 | --secondary: 240 3.7% 15.9%;
54 | --secondary-foreground: 0 0% 98%;
55 |
56 | --muted: 240 3.7% 15.9%;
57 | --muted-foreground: 240 5% 64.9%;
58 |
59 | --accent: 240 3.7% 15.9%;
60 | --accent-foreground: 0 0% 98%;
61 |
62 | --destructive: 0 62.8% 30.6%;
63 | --destructive-foreground: 0 0% 98%;
64 |
65 | --border: 240 3.7% 15.9%;
66 | --input: 240 3.7% 15.9%;
67 | --ring: 240 4.9% 83.9%;
68 | }
69 | }
70 |
71 | @layer base {
72 | * {
73 | @apply border-border;
74 | }
75 | body {
76 | @apply bg-background text-foreground;
77 | }
78 | }
79 |
80 | .rtl {
81 | direction: rtl;
82 | }
--------------------------------------------------------------------------------
/intellichat/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import './globals.css';
2 | import { Inter } from 'next/font/google';
3 |
4 | import Header from '@/components/shared/header';
5 | import { RQProvider } from '@/components/shared/providers';
6 |
7 | import type { Metadata } from 'next';
8 | import { Toaster } from '@/components/ui/toaster';
9 |
10 | const inter = Inter({ subsets: ['latin'] });
11 |
12 | export const metadata: Metadata = {
13 | title: 'IntelliChat',
14 | description:
15 | 'An open-source AI chatbot built with IntelliNode and Next.js. It is designed to accelerate the integration of multiple language models.',
16 | };
17 |
18 | export default function RootLayout({
19 | children,
20 | }: {
21 | children: React.ReactNode;
22 | }) {
23 | return (
24 |
25 |
28 |
29 |
30 |
31 | {children}
32 |
33 |
34 |
35 |
36 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/intellichat/src/app/page.tsx:
--------------------------------------------------------------------------------
1 | import Chat from '@/components/chat';
2 | import Image from 'next/image';
3 |
4 | export default function Home() {
5 | return ;
6 | }
7 |
--------------------------------------------------------------------------------
/intellichat/src/components/apikey-input.tsx:
--------------------------------------------------------------------------------
1 | import { useChatSettings } from '@/store/chat-settings';
2 | import { Input } from '@/components/ui/input';
3 | import {
4 | FormControl,
5 | FormDescription,
6 | FormField,
7 | FormItem,
8 | FormLabel,
9 | } from '@/components/ui/form';
10 |
11 | export default function ApiKeyInput({
12 | name,
13 | id,
14 | label,
15 | control,
16 | provider,
17 | withContext,
18 | }: {
19 | name: string;
20 | provider: 'openai' | 'replicate' | 'cohere' | 'google';
21 | id: string;
22 | label: string;
23 | control: any;
24 | withContext: boolean;
25 | }) {
26 | const envKeys = useChatSettings((s) => s.envKeys);
27 | const isVisible = provider === id || (withContext && id === 'openai');
28 | if (!isVisible) return null;
29 | const hasEnvKey = envKeys[id];
30 | return (
31 | (
35 |
36 | {label}
37 |
38 |
39 |
40 | {hasEnvKey && (
41 |
42 | API Key is set as an environment variable, but you can override it
43 | here.
44 |
45 | )}
46 |
47 | )}
48 | >
49 | );
50 | }
51 |
--------------------------------------------------------------------------------
/intellichat/src/components/chat-message.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ReactMarkdown } from 'react-markdown/lib/react-markdown';
3 | import { cn, isPrimarilyRtl } from '@/lib/utils';
4 | import { Message } from '@/lib/types';
5 | import { BookIcon } from 'lucide-react';
6 | import { Popover, PopoverContent, PopoverTrigger } from './ui/popover';
7 |
8 | type Props = Message & {
9 | last?: boolean;
10 | isStreaming?: boolean;
11 | };
12 |
13 | export const ChatMessage = ({
14 | role,
15 | content,
16 | last,
17 | references,
18 | id,
19 | isStreaming
20 | }: Props) => {
21 | const isUser = role === 'user';
22 | const isRtl = isPrimarilyRtl(content);
23 |
24 | return (
25 |
26 |
27 |
28 | {isUser ? (
29 |
30 | {content}
31 |
32 | ) : (
33 |
34 |
35 | {content}
36 |
37 | {last && references && references.length > 0 && !isStreaming && (
38 |
39 |
40 |
41 | Sources
42 |
43 |
44 | {references.map((ref, index) => (
45 |
46 | {ref}
47 |
48 | ))}
49 |
50 |
51 |
52 | )}
53 | {isStreaming && (
54 |
59 | )}
60 |
61 | )}
62 |
63 |
64 | );
65 | };
66 |
67 | const ChatAvatar = ({ isUser }: { isUser: boolean }) => {
68 | return (
69 |
75 | );
76 | };
--------------------------------------------------------------------------------
/intellichat/src/components/chat-panel.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { ChatMessage } from './chat-message';
3 | import { Message } from '@/lib/types';
4 |
5 | export const ChatPanel = ({
6 | chat,
7 | initialMessage,
8 | streamingMessage,
9 | isStreaming,
10 | }: {
11 | chat?: Message[];
12 | initialMessage?: Message | null;
13 | streamingMessage: Message | null;
14 | isStreaming: boolean;
15 | }) => {
16 | const endRef = React.useRef(null);
17 |
18 | useEffect(() => {
19 | requestAnimationFrame(() => {
20 | endRef.current?.scrollIntoView({ behavior: 'smooth' });
21 | });
22 | }, [chat?.length, streamingMessage]); // Add streamingMessage to dependencies
23 |
24 | if (chat) {
25 | return (
26 | <>
27 | {/* Render regular messages */}
28 | {chat.map((message, index) => {
29 | if (message.id !== streamingMessage?.id) {
30 | return (
31 |
36 | );
37 | }
38 | })}
39 |
40 | {/* Render streaming message if present */}
41 | {streamingMessage && (
42 |
47 | )}
48 |
49 |
50 | >
51 | );
52 | }
53 |
54 | if (initialMessage) {
55 | return ;
56 | }
57 |
58 | return null;
59 | };
60 |
--------------------------------------------------------------------------------
/intellichat/src/components/chat-prompt.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Textarea } from './ui/textarea';
3 | import { Button } from './ui/button';
4 | import { CornerDownLeft } from 'lucide-react';
5 |
6 | type Props = {
7 | isLoading: boolean;
8 | onSubmit: () => void;
9 | };
10 |
11 | export const ChatPrompt = React.forwardRef(
12 | function ChatPrompt(props, ref) {
13 | const { isLoading, onSubmit } = props;
14 |
15 | const onEnter = (event: React.KeyboardEvent) => {
16 | if (isLoading) return;
17 | if (event.key === 'Enter') {
18 | event.preventDefault();
19 | onSubmit();
20 | }
21 | };
22 |
23 | return (
24 |
25 | {isLoading && (
26 |
27 | Generating Response ...
28 |
29 | )}
30 |
31 |
38 |
47 |
48 |
49 | );
50 | }
51 | );
52 |
--------------------------------------------------------------------------------
/intellichat/src/components/chat-settings.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import React from 'react';
4 | import { z } from 'zod';
5 | import { useForm } from 'react-hook-form';
6 | import { zodResolver } from '@hookform/resolvers/zod';
7 |
8 | import { useChatSettings } from '@/store/chat-settings';
9 | import { AIProviders } from '@/lib/ai-providers';
10 | import { formSchema } from '@/lib/schema';
11 |
12 | import { ScrollArea } from '@/components/ui/scroll-area';
13 | import { Form } from '@/components/ui/form';
14 | import { Button } from '@/components/ui/button';
15 | import {
16 | FormInputField,
17 | FormSelectField,
18 | FormSwitchField,
19 | } from '@/components/form-ui';
20 | import ApiKeyInput from '@/components/apikey-input';
21 | import { SupportedProvidersNamesType } from '@/lib/validators';
22 |
23 | const providersOptions = Object.keys(AIProviders).map((key) => ({
24 | label: AIProviders[key as keyof typeof AIProviders].name,
25 | value: key,
26 | _key: key,
27 | }));
28 |
29 | export default function ChatSettings({ close }: { close: () => void }) {
30 | const systemMessage = useChatSettings((s) => s.systemMessage);
31 | const numberOfMessages = useChatSettings((s) => s.numberOfMessages);
32 | const provider = useChatSettings((s) => s.provider);
33 | const providers = useChatSettings((s) => s.providers);
34 | const withContext = useChatSettings((s) => s.withContext);
35 | const stream = useChatSettings((s) => s.stream);
36 | const intellinodeData = useChatSettings((s) => s.intellinodeData);
37 | const oneKey = useChatSettings((s) => s.oneKey);
38 | const envKeys = useChatSettings((s) => s.envKeys);
39 | const getModel = useChatSettings((s) => s.getModel);
40 | const updateChatSettings = useChatSettings((s) => s.updateChatSettings);
41 | const resetStore = useChatSettings((s) => s.resetState);
42 |
43 | // For vLLM, we want the top-level providerModel to be empty,
44 | // and we ensure providers.vllm exists with default empty fields.
45 | const defaultProviderModel = provider === 'vllm' ? '' : getModel();
46 | const defaultVllmProviders = {
47 | ...providers,
48 | vllm: providers.vllm || { name: 'vllm', model: '', baseUrl: '', apiKey: "" },
49 | };
50 |
51 | const defaultFormValues = {
52 | systemMessage,
53 | numberOfMessages,
54 | providerName: provider,
55 | providerModel: provider === 'vllm' ? '' : getModel(),
56 | providers: defaultVllmProviders,
57 | stream,
58 | withContext,
59 | intellinodeData,
60 | oneKey,
61 | envKeys,
62 | };
63 |
64 | const form = useForm>({
65 | values: defaultFormValues,
66 | defaultValues: defaultFormValues,
67 | resolver: zodResolver(formSchema),
68 | });
69 |
70 | function onSubmit({
71 | providerName,
72 | providerModel,
73 | withContext,
74 | stream,
75 | providers,
76 | ...values
77 | }: z.infer) {
78 | console.log("onSubmit called with:", {
79 | providerName,
80 | providerModel,
81 | withContext,
82 | stream,
83 | providers,
84 | ...values,
85 | });
86 | const selectedProvider = providerName;
87 | const updatedProviders = { ...providers };
88 |
89 | // Only update the model from the top-level providerModel if the provider is not azure or vllm.
90 | if (selectedProvider !== 'azure' && selectedProvider !== 'vllm') {
91 | updatedProviders[selectedProvider] = {
92 | ...updatedProviders[selectedProvider],
93 | model: providerModel,
94 | };
95 | console.log(`For provider ${selectedProvider}, setting model from dropdown:`, providerModel);
96 | } else {
97 | // For azure and vllm, we assume the user filled the text field.
98 | console.log(`For provider ${selectedProvider}, using text field value for model:`, updatedProviders[selectedProvider].model);
99 | }
100 |
101 | const payload = {
102 | provider: selectedProvider,
103 | withContext: selectedProvider === 'openai' ? withContext : false,
104 | stream: selectedProvider === 'openai' || selectedProvider === 'cohere' || provider === 'vllm' ? stream : false,
105 | providers: updatedProviders,
106 | ...values,
107 | };
108 |
109 | updateChatSettings(payload);
110 | close();
111 | }
112 |
113 | function onError(errors: any) {
114 | console.error("Form validation errors:", errors);
115 | }
116 |
117 | function onChangeProviderName(name: SupportedProvidersNamesType) {
118 | console.log("Provider changed to:", name);
119 | form.setValue('providerName', name);
120 | // For providers with a predefined list, set default providerModel; otherwise clear it.
121 | if (name !== 'azure' && name !== 'vllm' && AIProviders[name].models) {
122 | const defaultModel = AIProviders[name].models[0] as string;
123 | form.setValue('providerModel', defaultModel);
124 | console.log("Set providerModel from dropdown to:", defaultModel);
125 | } else {
126 | form.setValue('providerModel', '');
127 | console.log("Cleared providerModel for provider:", name);
128 | }
129 | }
130 |
131 | const watchProviderName = form.watch('providerName');
132 | const watchIntellinodeData = form.watch('intellinodeData');
133 |
134 | // Only show dropdown options if provider is not azure and not vllm.
135 | const modelsOptions =
136 | watchProviderName !== 'azure' && watchProviderName !== 'vllm'
137 | ? AIProviders[watchProviderName].models.map((model) => ({
138 | label: model,
139 | value: model,
140 | _key: model,
141 | }))
142 | : [];
143 |
144 | // Reset function: resets the form to default values.
145 | function handleReset() {
146 | console.log("Reset button clicked. Resetting form.");
147 | form.reset(defaultFormValues);
148 | resetStore();
149 | }
150 |
151 | return (
152 |
153 |
297 |
298 |
299 | );
300 | }
301 |
--------------------------------------------------------------------------------
/intellichat/src/components/chat.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import React, { useEffect, useState } from 'react';
4 | import { nanoid } from 'nanoid';
5 | import { ChatPanel } from './chat-panel';
6 | import { ChatPrompt } from './chat-prompt';
7 | import Container from '@/components/shared/container';
8 | import { useMutation, useQuery } from '@tanstack/react-query';
9 | import type { PostMessagePayload } from '@/lib/validators';
10 | import { Message } from '@/lib/types';
11 | import { useChatSettings } from '@/store/chat-settings';
12 | import { useToast } from './ui/use-toast';
13 | import { useSearchParams } from 'next/navigation';
14 |
15 | export default function Chat() {
16 | const messages = useChatSettings((s) => s.messages);
17 | const params = useSearchParams();
18 | const oneKey = params.get('one_key');
19 |
20 | const getSettings = useChatSettings((s) => s.getSettings);
21 | const setEnvKeys = useChatSettings((s) => s.setEnvKeys);
22 | const setMessage = useChatSettings((s) => s.setMessage);
23 | const setOneKey = useChatSettings((s) => s.setOneKey);
24 | const [streamingMessage, setStreamingMessage] = React.useState(null);
25 | const [isStreaming , setIsStreaming] = useState(false);
26 | // const [chatPanelMessage, setChatPanelMessage] = useState(messages);
27 |
28 |
29 |
30 | useEffect(() => {
31 | const ok = oneKey ?? getSettings().oneKey;
32 | if (ok) setOneKey(ok);
33 | }, []);
34 |
35 | const { toast } = useToast();
36 | const input = React.useRef(null);
37 |
38 | const handleStream = async (response: Response, messageId: string) => {
39 | const reader = response.body?.getReader();
40 | if (!reader) return;
41 |
42 | const decoder = new TextDecoder();
43 | let accumulatedContent = '';
44 | let frameId: number | null = null;
45 |
46 | const updateMessage = () => {
47 | setStreamingMessage({
48 | id: messageId,
49 | content: accumulatedContent,
50 | role: 'assistant',
51 | });
52 | frameId = null;
53 | };
54 |
55 | try {
56 | while (true) {
57 | const { done, value } = await reader.read();
58 | if (done) break;
59 |
60 | accumulatedContent += decoder.decode(value);
61 |
62 | // Reduce re-renders: Schedule the update if not already scheduled
63 | if (!frameId) {
64 | frameId = requestAnimationFrame(updateMessage);
65 | }
66 | }
67 |
68 | if (frameId) {
69 | cancelAnimationFrame(frameId);
70 | updateMessage(); // Final update before clearing streamingMessage
71 | }
72 |
73 | setIsStreaming(false);
74 |
75 | // Final update once stream ends
76 | setMessage({
77 | id: messageId,
78 | content: accumulatedContent,
79 | role: 'assistant',
80 | });
81 |
82 | setStreamingMessage(null);
83 | } catch (error) {
84 | console.error('Stream reading error:', error);
85 | reader.cancel();
86 | }
87 | };
88 |
89 |
90 | const { mutate, isLoading } = useMutation({
91 | mutationFn: async (payload: PostMessagePayload) => {
92 | const settings = getSettings();
93 | const supportsStreaming = settings.provider === 'openai' ||
94 | settings.provider === 'cohere' ||
95 | settings.provider === 'vllm';
96 | const isStreaming = supportsStreaming && settings.stream;
97 | setIsStreaming(isStreaming);
98 |
99 | const res = await fetch('/api/chat', {
100 | method: 'POST',
101 | body: JSON.stringify(payload),
102 | // Add headers to indicate streaming for OpenAI or Cohere
103 | headers: {
104 | 'Content-Type': 'application/json',
105 | 'Accept': isStreaming ? 'text/event-stream' : 'application/json',
106 | },
107 | });
108 |
109 | if (!res.ok) {
110 | const { error } = await res.json();
111 | throw new Error(`${error}`);
112 | }
113 |
114 | if (isStreaming) {
115 | // Handle streaming for OpenAI or Cohere
116 | const messageId = nanoid();
117 | handleStream(res, messageId);
118 | return null; // Return null as we're handling the response in handleStream
119 | } else {
120 | // Handle normal response for other providers
121 | const json: {
122 | response: string[];
123 | references: { [key: string]: string } | null;
124 | } = await res.json();
125 | return json;
126 | }
127 | },
128 | onSuccess: (data) => {
129 |
130 | if (!data) return; // Skip for streaming responses
131 |
132 | const { response, references } = data;
133 | if (!response) return;
134 | const refsSet = references ? new Set(Object.keys(references)) : null;
135 | const refs = references && refsSet ? Array.from(refsSet.keys()) : null;
136 |
137 | setMessage({
138 | id: nanoid(),
139 | content: response[0],
140 | references: refs,
141 | role: 'assistant',
142 | });
143 | },
144 | onError: (err: any) => {
145 | setStreamingMessage(null);
146 | toast({
147 | title: 'Error',
148 | variant: 'destructive',
149 | description: err.message,
150 | duration: 5000,
151 | });
152 | },
153 | });
154 |
155 | useQuery({
156 | queryKey: ['apiKeys'],
157 | staleTime: Infinity,
158 | refetchOnWindowFocus: false,
159 | queryFn: async () => {
160 | const res = await fetch('/api');
161 | if (res.ok) {
162 | const json = await res.json();
163 | return json as {
164 | openai: boolean;
165 | replicate: boolean;
166 | cohere: boolean;
167 | google: boolean;
168 | azure: boolean;
169 | };
170 | }
171 | const { error } = await res.json();
172 | throw new Error(`${error}`);
173 | },
174 | onSuccess: (data:any) => {
175 | setEnvKeys(data);
176 | },
177 | });
178 |
179 | const onSubmit = async () => {
180 | const inputText = input.current?.value;
181 | if (!inputText) return;
182 |
183 | const prompt = {
184 | id: nanoid(),
185 | content: inputText,
186 | role: 'user',
187 | } as Message;
188 | setMessage(prompt);
189 | const payload: PostMessagePayload = {
190 | messages: messages ? [...messages, prompt] : [prompt],
191 | ...getSettings(),
192 | };
193 |
194 | console.log("Payload", payload);
195 |
196 | mutate(payload);
197 | input.current!.value = '';
198 | };
199 |
200 | return (
201 |
202 |
203 |
208 |
209 |
210 |
211 | );
212 | }
--------------------------------------------------------------------------------
/intellichat/src/components/field-group.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from '@/lib/utils';
2 |
3 | export default function FieldGroup({
4 | children,
5 | withToolTip,
6 | className,
7 | }: {
8 | children?: React.ReactNode;
9 | className?: string;
10 | withToolTip?: boolean;
11 | }) {
12 | return (
13 |
20 | {children}
21 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/intellichat/src/components/field-tooltip.tsx:
--------------------------------------------------------------------------------
1 | import { InfoIcon } from 'lucide-react';
2 | import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip';
3 |
4 | export default function FieldTooltip({
5 | children,
6 | }: {
7 | children?: React.ReactNode;
8 | }) {
9 | return (
10 |
11 |
12 |
13 |
14 | {children}
15 |
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/intellichat/src/components/form-ui.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | FormControl,
3 | FormField,
4 | FormItem,
5 | FormLabel,
6 | FormMessage,
7 | } from '@/components/ui/form';
8 |
9 | import {
10 | Select,
11 | SelectContent,
12 | SelectItem,
13 | SelectTrigger,
14 | SelectValue,
15 | } from '@/components/ui/select';
16 |
17 | import { Input, InputProps } from '@/components/ui/input';
18 | import FieldTooltip from './field-tooltip';
19 | import { cn } from '@/lib/utils';
20 | import { Switch } from './ui/switch';
21 |
22 | type FormSelectProps = {
23 | placeholder: string;
24 | label?: string;
25 | name: string;
26 | options: Array<{
27 | label: string;
28 | value: string;
29 | _key: string;
30 | }>;
31 | control: any;
32 | className?: string;
33 | onChange?: (e: any) => void;
34 | };
35 |
36 | export function FormSelectField({
37 | placeholder,
38 | label,
39 | control,
40 | name,
41 | options,
42 | className,
43 | onChange,
44 | }: FormSelectProps) {
45 | return (
46 | (
50 |
51 | {label && {label}}
52 |
53 |
73 |
74 |
75 |
76 | )}
77 | >
78 | );
79 | }
80 |
81 | type FormInputProps = {
82 | name: string;
83 | label?: string;
84 | type?: string;
85 | className?: string;
86 | inputClassName?: string;
87 | control: any;
88 | withTooltip?: boolean;
89 | tooltipText?: string;
90 | } & InputProps;
91 |
92 | export function FormInputField({
93 | name,
94 | label,
95 | type = 'text',
96 | className,
97 | inputClassName,
98 | control,
99 | withTooltip,
100 | tooltipText,
101 | ...props
102 | }: FormInputProps) {
103 | return (
104 | (
108 |
109 |
114 | {label && {label}}
115 | {withTooltip && {tooltipText}}
116 |
117 |
118 |
124 |
125 |
126 |
127 | )}
128 | />
129 | );
130 | }
131 |
132 | export function FormSwitchField({
133 | control,
134 | name,
135 | label,
136 | withTooltip,
137 | tooltipText,
138 | onChange,
139 | }: {
140 | control: any;
141 | name: string;
142 | label: string;
143 | withTooltip?: boolean;
144 | tooltipText?: string;
145 | onChange?: (e: any) => void;
146 | }) {
147 | return (
148 | (
152 |
153 |
154 | {label}
155 |
156 | {
159 | field.onChange(e);
160 | if (onChange) onChange(e);
161 | }}
162 | />
163 |
164 |
165 | {withTooltip && {tooltipText}}
166 |
167 | )}
168 | />
169 | );
170 | }
171 |
--------------------------------------------------------------------------------
/intellichat/src/components/shared/container.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from '@/lib/utils';
2 | import React, { PropsWithChildren } from 'react';
3 |
4 | type Props = {
5 | as?: 'div' | 'section' | 'article' | 'main' | 'header' | 'footer';
6 | className?: string;
7 | };
8 |
9 | export default function Container({
10 | as = 'div',
11 | className,
12 | children,
13 | }: PropsWithChildren) {
14 | const Component = as;
15 | return (
16 |
19 | {children}
20 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/intellichat/src/components/shared/header.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import React from 'react';
4 | import Logo from './logo';
5 | import Container from './container';
6 | import SideBar from './sidebar';
7 | import { Button } from '@/components/ui/button';
8 | import { useChatSettings } from '@/store/chat-settings';
9 | import GitHubButton from 'react-github-btn';
10 | type Props = {};
11 |
12 | export default function Header({}: Props) {
13 | const clearMessages = useChatSettings((s) => s.clearMessages);
14 | return (
15 |
16 |
17 |
20 |
33 |
34 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/intellichat/src/components/shared/logo.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | type Props = {};
4 |
5 | export default function Logo({}: Props) {
6 | return (
7 | <>
8 | {/* full */}
9 |
115 |
116 | {/* mobile */}
117 |
178 | >
179 | );
180 | }
181 |
--------------------------------------------------------------------------------
/intellichat/src/components/shared/providers.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import React from 'react';
4 | import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
5 |
6 | export function RQProvider({ children }: { children: React.ReactNode }) {
7 | const [client] = React.useState(new QueryClient());
8 |
9 | return {children};
10 | }
11 |
--------------------------------------------------------------------------------
/intellichat/src/components/shared/sidebar.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import React, { useEffect } from 'react';
4 | import { usePathname } from 'next/navigation';
5 | import { PanelLeftOpen, PanelLeftClose } from 'lucide-react';
6 |
7 | import { useChatSettings } from '@/store/chat-settings';
8 |
9 | import {
10 | Sheet,
11 | SheetContent,
12 | SheetHeader,
13 | SheetTitle,
14 | SheetTrigger,
15 | } from '@/components/ui/sheet';
16 | import { Button } from '@/components/ui/button';
17 | import ChatSettings from '@/components/chat-settings';
18 | import { TooltipProvider } from '@/components/ui/tooltip';
19 | import { SupportedProvidersNamesType } from '@/lib/validators';
20 |
21 | export default function SideBar({ title }: { title?: string }) {
22 | const [isOpen, setIsOpen] = React.useState(false);
23 | const pathname = usePathname();
24 | const getProvider = useChatSettings((s) => s.getProvider);
25 | const intellinodeData = useChatSettings((s) => s.intellinodeData);
26 | const oneKey = useChatSettings((s) => s.oneKey);
27 | const envKeys = useChatSettings((s) => s.envKeys);
28 |
29 | // Open the settings sheet if the user has not set the API keys
30 | useEffect(() => {
31 | const provider = getProvider();
32 | const providerkey = provider?.apiKey;
33 | const keyInState = providerkey ? providerkey.trim() : undefined;
34 | const keyInEnv = provider
35 | ? envKeys[provider.name as SupportedProvidersNamesType]
36 | : false;
37 | const keyExists = keyInState || keyInEnv;
38 | const oneKeyInState = oneKey.trim();
39 | const oneKeyIsEnabled = intellinodeData;
40 |
41 | if (!keyExists && oneKeyIsEnabled && !oneKeyInState) {
42 | setIsOpen(true);
43 | }
44 | }, [getProvider, oneKey]);
45 |
46 | return (
47 | setIsOpen(!isOpen)}>
48 |
49 |
57 |
58 |
62 | {pathname === '/' && (
63 | <>
64 |
65 | {title}
66 |
67 |
68 | setIsOpen(false)} />
69 |
70 | >
71 | )}
72 |
73 |
74 | );
75 | }
76 |
--------------------------------------------------------------------------------
/intellichat/src/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
9 | {
10 | variants: {
11 | variant: {
12 | default: "bg-primary text-primary-foreground hover:bg-primary/90",
13 | destructive:
14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15 | outline:
16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17 | secondary:
18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19 | ghost: "hover:bg-accent hover:text-accent-foreground",
20 | link: "text-primary underline-offset-4 hover:underline",
21 | },
22 | size: {
23 | default: "h-10 px-4 py-2",
24 | sm: "h-9 rounded-md px-3",
25 | lg: "h-11 rounded-md px-8",
26 | icon: "h-10 w-10",
27 | },
28 | },
29 | defaultVariants: {
30 | variant: "default",
31 | size: "default",
32 | },
33 | }
34 | )
35 |
36 | export interface ButtonProps
37 | extends React.ButtonHTMLAttributes,
38 | VariantProps {
39 | asChild?: boolean
40 | }
41 |
42 | const Button = React.forwardRef(
43 | ({ className, variant, size, asChild = false, ...props }, ref) => {
44 | const Comp = asChild ? Slot : "button"
45 | return (
46 |
51 | )
52 | }
53 | )
54 | Button.displayName = "Button"
55 |
56 | export { Button, buttonVariants }
57 |
--------------------------------------------------------------------------------
/intellichat/src/components/ui/collapsible.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
4 |
5 | const Collapsible = CollapsiblePrimitive.Root
6 |
7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
8 |
9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
10 |
11 | export { Collapsible, CollapsibleTrigger, CollapsibleContent }
12 |
--------------------------------------------------------------------------------
/intellichat/src/components/ui/form.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as LabelPrimitive from "@radix-ui/react-label"
3 | import { Slot } from "@radix-ui/react-slot"
4 | import {
5 | Controller,
6 | ControllerProps,
7 | FieldPath,
8 | FieldValues,
9 | FormProvider,
10 | useFormContext,
11 | } from "react-hook-form"
12 |
13 | import { cn } from "@/lib/utils"
14 | import { Label } from "@/components/ui/label"
15 |
16 | const Form = FormProvider
17 |
18 | type FormFieldContextValue<
19 | TFieldValues extends FieldValues = FieldValues,
20 | TName extends FieldPath = FieldPath
21 | > = {
22 | name: TName
23 | }
24 |
25 | const FormFieldContext = React.createContext(
26 | {} as FormFieldContextValue
27 | )
28 |
29 | const FormField = <
30 | TFieldValues extends FieldValues = FieldValues,
31 | TName extends FieldPath = FieldPath
32 | >({
33 | ...props
34 | }: ControllerProps) => {
35 | return (
36 |
37 |
38 |
39 | )
40 | }
41 |
42 | const useFormField = () => {
43 | const fieldContext = React.useContext(FormFieldContext)
44 | const itemContext = React.useContext(FormItemContext)
45 | const { getFieldState, formState } = useFormContext()
46 |
47 | const fieldState = getFieldState(fieldContext.name, formState)
48 |
49 | if (!fieldContext) {
50 | throw new Error("useFormField should be used within ")
51 | }
52 |
53 | const { id } = itemContext
54 |
55 | return {
56 | id,
57 | name: fieldContext.name,
58 | formItemId: `${id}-form-item`,
59 | formDescriptionId: `${id}-form-item-description`,
60 | formMessageId: `${id}-form-item-message`,
61 | ...fieldState,
62 | }
63 | }
64 |
65 | type FormItemContextValue = {
66 | id: string
67 | }
68 |
69 | const FormItemContext = React.createContext(
70 | {} as FormItemContextValue
71 | )
72 |
73 | const FormItem = React.forwardRef<
74 | HTMLDivElement,
75 | React.HTMLAttributes
76 | >(({ className, ...props }, ref) => {
77 | const id = React.useId()
78 |
79 | return (
80 |
81 |
82 |
83 | )
84 | })
85 | FormItem.displayName = "FormItem"
86 |
87 | const FormLabel = React.forwardRef<
88 | React.ElementRef,
89 | React.ComponentPropsWithoutRef
90 | >(({ className, ...props }, ref) => {
91 | const { error, formItemId } = useFormField()
92 |
93 | return (
94 |
100 | )
101 | })
102 | FormLabel.displayName = "FormLabel"
103 |
104 | const FormControl = React.forwardRef<
105 | React.ElementRef,
106 | React.ComponentPropsWithoutRef
107 | >(({ ...props }, ref) => {
108 | const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
109 |
110 | return (
111 |
122 | )
123 | })
124 | FormControl.displayName = "FormControl"
125 |
126 | const FormDescription = React.forwardRef<
127 | HTMLParagraphElement,
128 | React.HTMLAttributes
129 | >(({ className, ...props }, ref) => {
130 | const { formDescriptionId } = useFormField()
131 |
132 | return (
133 |
139 | )
140 | })
141 | FormDescription.displayName = "FormDescription"
142 |
143 | const FormMessage = React.forwardRef<
144 | HTMLParagraphElement,
145 | React.HTMLAttributes
146 | >(({ className, children, ...props }, ref) => {
147 | const { error, formMessageId } = useFormField()
148 | const body = error ? String(error?.message) : children
149 |
150 | if (!body) {
151 | return null
152 | }
153 |
154 | return (
155 |
161 | {body}
162 |
163 | )
164 | })
165 | FormMessage.displayName = "FormMessage"
166 |
167 | export {
168 | useFormField,
169 | Form,
170 | FormItem,
171 | FormLabel,
172 | FormControl,
173 | FormDescription,
174 | FormMessage,
175 | FormField,
176 | }
177 |
--------------------------------------------------------------------------------
/intellichat/src/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { cn } from '@/lib/utils';
4 |
5 | export interface InputProps
6 | extends React.InputHTMLAttributes {}
7 |
8 | const Input = React.forwardRef(
9 | ({ className, type, ...props }, ref) => {
10 | return (
11 |
20 | );
21 | }
22 | );
23 | Input.displayName = 'Input';
24 |
25 | export { Input };
26 |
--------------------------------------------------------------------------------
/intellichat/src/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as LabelPrimitive from "@radix-ui/react-label"
5 | import { cva, type VariantProps } from "class-variance-authority"
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 &
16 | VariantProps
17 | >(({ className, ...props }, ref) => (
18 |
23 | ))
24 | Label.displayName = LabelPrimitive.Root.displayName
25 |
26 | export { Label }
27 |
--------------------------------------------------------------------------------
/intellichat/src/components/ui/popover.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as PopoverPrimitive from "@radix-ui/react-popover"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Popover = PopoverPrimitive.Root
9 |
10 | const PopoverTrigger = PopoverPrimitive.Trigger
11 |
12 | const PopoverContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
16 |
17 |
27 |
28 | ))
29 | PopoverContent.displayName = PopoverPrimitive.Content.displayName
30 |
31 | export { Popover, PopoverTrigger, PopoverContent }
32 |
--------------------------------------------------------------------------------
/intellichat/src/components/ui/scroll-area.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const ScrollArea = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, children, ...props }, ref) => (
12 |
17 |
18 | {children}
19 |
20 |
21 |
22 |
23 | ))
24 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
25 |
26 | const ScrollBar = React.forwardRef<
27 | React.ElementRef,
28 | React.ComponentPropsWithoutRef
29 | >(({ className, orientation = "vertical", ...props }, ref) => (
30 |
43 |
44 |
45 | ))
46 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
47 |
48 | export { ScrollArea, ScrollBar }
49 |
--------------------------------------------------------------------------------
/intellichat/src/components/ui/select.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SelectPrimitive from "@radix-ui/react-select"
5 | import { Check, ChevronDown } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Select = SelectPrimitive.Root
10 |
11 | const SelectGroup = SelectPrimitive.Group
12 |
13 | const SelectValue = SelectPrimitive.Value
14 |
15 | const SelectTrigger = React.forwardRef<
16 | React.ElementRef,
17 | React.ComponentPropsWithoutRef
18 | >(({ className, children, ...props }, ref) => (
19 |
27 | {children}
28 |
29 |
30 |
31 |
32 | ))
33 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
34 |
35 | const SelectContent = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, children, position = "popper", ...props }, ref) => (
39 |
40 |
51 |
58 | {children}
59 |
60 |
61 |
62 | ))
63 | SelectContent.displayName = SelectPrimitive.Content.displayName
64 |
65 | const SelectLabel = React.forwardRef<
66 | React.ElementRef,
67 | React.ComponentPropsWithoutRef
68 | >(({ className, ...props }, ref) => (
69 |
74 | ))
75 | SelectLabel.displayName = SelectPrimitive.Label.displayName
76 |
77 | const SelectItem = React.forwardRef<
78 | React.ElementRef,
79 | React.ComponentPropsWithoutRef
80 | >(({ className, children, ...props }, ref) => (
81 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | {children}
96 |
97 | ))
98 | SelectItem.displayName = SelectPrimitive.Item.displayName
99 |
100 | const SelectSeparator = React.forwardRef<
101 | React.ElementRef,
102 | React.ComponentPropsWithoutRef
103 | >(({ className, ...props }, ref) => (
104 |
109 | ))
110 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName
111 |
112 | export {
113 | Select,
114 | SelectGroup,
115 | SelectValue,
116 | SelectTrigger,
117 | SelectContent,
118 | SelectLabel,
119 | SelectItem,
120 | SelectSeparator,
121 | }
122 |
--------------------------------------------------------------------------------
/intellichat/src/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SeparatorPrimitive from "@radix-ui/react-separator"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Separator = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(
12 | (
13 | { className, orientation = "horizontal", decorative = true, ...props },
14 | ref
15 | ) => (
16 |
27 | )
28 | )
29 | Separator.displayName = SeparatorPrimitive.Root.displayName
30 |
31 | export { Separator }
32 |
--------------------------------------------------------------------------------
/intellichat/src/components/ui/sheet.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SheetPrimitive from "@radix-ui/react-dialog"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 | import { X } from "lucide-react"
7 |
8 | import { cn } from "@/lib/utils"
9 |
10 | const Sheet = SheetPrimitive.Root
11 |
12 | const SheetTrigger = SheetPrimitive.Trigger
13 |
14 | const SheetClose = SheetPrimitive.Close
15 |
16 | const SheetPortal = ({
17 | className,
18 | ...props
19 | }: SheetPrimitive.DialogPortalProps) => (
20 |
21 | )
22 | SheetPortal.displayName = SheetPrimitive.Portal.displayName
23 |
24 | const SheetOverlay = React.forwardRef<
25 | React.ElementRef,
26 | React.ComponentPropsWithoutRef
27 | >(({ className, ...props }, ref) => (
28 |
36 | ))
37 | SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
38 |
39 | const sheetVariants = cva(
40 | "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
41 | {
42 | variants: {
43 | side: {
44 | top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
45 | bottom:
46 | "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
47 | left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
48 | right:
49 | "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
50 | },
51 | },
52 | defaultVariants: {
53 | side: "right",
54 | },
55 | }
56 | )
57 |
58 | interface SheetContentProps
59 | extends React.ComponentPropsWithoutRef,
60 | VariantProps {}
61 |
62 | const SheetContent = React.forwardRef<
63 | React.ElementRef,
64 | SheetContentProps
65 | >(({ side = "right", className, children, ...props }, ref) => (
66 |
67 |
68 |
73 | {children}
74 |
75 |
76 | Close
77 |
78 |
79 |
80 | ))
81 | SheetContent.displayName = SheetPrimitive.Content.displayName
82 |
83 | const SheetHeader = ({
84 | className,
85 | ...props
86 | }: React.HTMLAttributes) => (
87 |
94 | )
95 | SheetHeader.displayName = "SheetHeader"
96 |
97 | const SheetFooter = ({
98 | className,
99 | ...props
100 | }: React.HTMLAttributes) => (
101 |
108 | )
109 | SheetFooter.displayName = "SheetFooter"
110 |
111 | const SheetTitle = React.forwardRef<
112 | React.ElementRef,
113 | React.ComponentPropsWithoutRef
114 | >(({ className, ...props }, ref) => (
115 |
120 | ))
121 | SheetTitle.displayName = SheetPrimitive.Title.displayName
122 |
123 | const SheetDescription = React.forwardRef<
124 | React.ElementRef,
125 | React.ComponentPropsWithoutRef
126 | >(({ className, ...props }, ref) => (
127 |
132 | ))
133 | SheetDescription.displayName = SheetPrimitive.Description.displayName
134 |
135 | export {
136 | Sheet,
137 | SheetTrigger,
138 | SheetClose,
139 | SheetContent,
140 | SheetHeader,
141 | SheetFooter,
142 | SheetTitle,
143 | SheetDescription,
144 | }
145 |
--------------------------------------------------------------------------------
/intellichat/src/components/ui/switch.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SwitchPrimitives from "@radix-ui/react-switch"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Switch = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 |
25 |
26 | ))
27 | Switch.displayName = SwitchPrimitives.Root.displayName
28 |
29 | export { Switch }
30 |
--------------------------------------------------------------------------------
/intellichat/src/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface TextareaProps
6 | extends React.TextareaHTMLAttributes {}
7 |
8 | const Textarea = React.forwardRef(
9 | ({ className, ...props }, ref) => {
10 | return (
11 |
19 | )
20 | }
21 | )
22 | Textarea.displayName = "Textarea"
23 |
24 | export { Textarea }
25 |
--------------------------------------------------------------------------------
/intellichat/src/components/ui/toast.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as ToastPrimitives from "@radix-ui/react-toast"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 | import { X } from "lucide-react"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const ToastProvider = ToastPrimitives.Provider
9 |
10 | const ToastViewport = React.forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef
13 | >(({ className, ...props }, ref) => (
14 |
22 | ))
23 | ToastViewport.displayName = ToastPrimitives.Viewport.displayName
24 |
25 | const toastVariants = cva(
26 | "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
27 | {
28 | variants: {
29 | variant: {
30 | default: "border bg-background text-foreground",
31 | destructive:
32 | "destructive group border-destructive bg-destructive text-destructive-foreground",
33 | },
34 | },
35 | defaultVariants: {
36 | variant: "default",
37 | },
38 | }
39 | )
40 |
41 | const Toast = React.forwardRef<
42 | React.ElementRef,
43 | React.ComponentPropsWithoutRef &
44 | VariantProps
45 | >(({ className, variant, ...props }, ref) => {
46 | return (
47 |
52 | )
53 | })
54 | Toast.displayName = ToastPrimitives.Root.displayName
55 |
56 | const ToastAction = React.forwardRef<
57 | React.ElementRef,
58 | React.ComponentPropsWithoutRef
59 | >(({ className, ...props }, ref) => (
60 |
68 | ))
69 | ToastAction.displayName = ToastPrimitives.Action.displayName
70 |
71 | const ToastClose = React.forwardRef<
72 | React.ElementRef,
73 | React.ComponentPropsWithoutRef
74 | >(({ className, ...props }, ref) => (
75 |
84 |
85 |
86 | ))
87 | ToastClose.displayName = ToastPrimitives.Close.displayName
88 |
89 | const ToastTitle = React.forwardRef<
90 | React.ElementRef,
91 | React.ComponentPropsWithoutRef
92 | >(({ className, ...props }, ref) => (
93 |
98 | ))
99 | ToastTitle.displayName = ToastPrimitives.Title.displayName
100 |
101 | const ToastDescription = React.forwardRef<
102 | React.ElementRef,
103 | React.ComponentPropsWithoutRef
104 | >(({ className, ...props }, ref) => (
105 |
110 | ))
111 | ToastDescription.displayName = ToastPrimitives.Description.displayName
112 |
113 | type ToastProps = React.ComponentPropsWithoutRef
114 |
115 | type ToastActionElement = React.ReactElement
116 |
117 | export {
118 | type ToastProps,
119 | type ToastActionElement,
120 | ToastProvider,
121 | ToastViewport,
122 | Toast,
123 | ToastTitle,
124 | ToastDescription,
125 | ToastClose,
126 | ToastAction,
127 | }
128 |
--------------------------------------------------------------------------------
/intellichat/src/components/ui/toaster.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import {
4 | Toast,
5 | ToastClose,
6 | ToastDescription,
7 | ToastProvider,
8 | ToastTitle,
9 | ToastViewport,
10 | } from "@/components/ui/toast"
11 | import { useToast } from "@/components/ui/use-toast"
12 |
13 | export function Toaster() {
14 | const { toasts } = useToast()
15 |
16 | return (
17 |
18 | {toasts.map(function ({ id, title, description, action, ...props }) {
19 | return (
20 |
21 |
22 | {title && {title}}
23 | {description && (
24 | {description}
25 | )}
26 |
27 | {action}
28 |
29 |
30 | )
31 | })}
32 |
33 |
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/intellichat/src/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as TooltipPrimitive from "@radix-ui/react-tooltip"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const TooltipProvider = TooltipPrimitive.Provider
9 |
10 | const Tooltip = TooltipPrimitive.Root
11 |
12 | const TooltipTrigger = TooltipPrimitive.Trigger
13 |
14 | const TooltipContent = React.forwardRef<
15 | React.ElementRef,
16 | React.ComponentPropsWithoutRef
17 | >(({ className, sideOffset = 4, ...props }, ref) => (
18 |
27 | ))
28 | TooltipContent.displayName = TooltipPrimitive.Content.displayName
29 |
30 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
31 |
--------------------------------------------------------------------------------
/intellichat/src/components/ui/use-toast.ts:
--------------------------------------------------------------------------------
1 | // Inspired by react-hot-toast library
2 | import * as React from "react"
3 |
4 | import type {
5 | ToastActionElement,
6 | ToastProps,
7 | } from "@/components/ui/toast"
8 |
9 | const TOAST_LIMIT = 1
10 | const TOAST_REMOVE_DELAY = 1000000
11 |
12 | type ToasterToast = ToastProps & {
13 | id: string
14 | title?: React.ReactNode
15 | description?: React.ReactNode
16 | action?: ToastActionElement
17 | }
18 |
19 | const actionTypes = {
20 | ADD_TOAST: "ADD_TOAST",
21 | UPDATE_TOAST: "UPDATE_TOAST",
22 | DISMISS_TOAST: "DISMISS_TOAST",
23 | REMOVE_TOAST: "REMOVE_TOAST",
24 | } as const
25 |
26 | let count = 0
27 |
28 | function genId() {
29 | count = (count + 1) % Number.MAX_VALUE
30 | return count.toString()
31 | }
32 |
33 | type ActionType = typeof actionTypes
34 |
35 | type Action =
36 | | {
37 | type: ActionType["ADD_TOAST"]
38 | toast: ToasterToast
39 | }
40 | | {
41 | type: ActionType["UPDATE_TOAST"]
42 | toast: Partial
43 | }
44 | | {
45 | type: ActionType["DISMISS_TOAST"]
46 | toastId?: ToasterToast["id"]
47 | }
48 | | {
49 | type: ActionType["REMOVE_TOAST"]
50 | toastId?: ToasterToast["id"]
51 | }
52 |
53 | interface State {
54 | toasts: ToasterToast[]
55 | }
56 |
57 | const toastTimeouts = new Map>()
58 |
59 | const addToRemoveQueue = (toastId: string) => {
60 | if (toastTimeouts.has(toastId)) {
61 | return
62 | }
63 |
64 | const timeout = setTimeout(() => {
65 | toastTimeouts.delete(toastId)
66 | dispatch({
67 | type: "REMOVE_TOAST",
68 | toastId: toastId,
69 | })
70 | }, TOAST_REMOVE_DELAY)
71 |
72 | toastTimeouts.set(toastId, timeout)
73 | }
74 |
75 | export const reducer = (state: State, action: Action): State => {
76 | switch (action.type) {
77 | case "ADD_TOAST":
78 | return {
79 | ...state,
80 | toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
81 | }
82 |
83 | case "UPDATE_TOAST":
84 | return {
85 | ...state,
86 | toasts: state.toasts.map((t) =>
87 | t.id === action.toast.id ? { ...t, ...action.toast } : t
88 | ),
89 | }
90 |
91 | case "DISMISS_TOAST": {
92 | const { toastId } = action
93 |
94 | // ! Side effects ! - This could be extracted into a dismissToast() action,
95 | // but I'll keep it here for simplicity
96 | if (toastId) {
97 | addToRemoveQueue(toastId)
98 | } else {
99 | state.toasts.forEach((toast) => {
100 | addToRemoveQueue(toast.id)
101 | })
102 | }
103 |
104 | return {
105 | ...state,
106 | toasts: state.toasts.map((t) =>
107 | t.id === toastId || toastId === undefined
108 | ? {
109 | ...t,
110 | open: false,
111 | }
112 | : t
113 | ),
114 | }
115 | }
116 | case "REMOVE_TOAST":
117 | if (action.toastId === undefined) {
118 | return {
119 | ...state,
120 | toasts: [],
121 | }
122 | }
123 | return {
124 | ...state,
125 | toasts: state.toasts.filter((t) => t.id !== action.toastId),
126 | }
127 | }
128 | }
129 |
130 | const listeners: Array<(state: State) => void> = []
131 |
132 | let memoryState: State = { toasts: [] }
133 |
134 | function dispatch(action: Action) {
135 | memoryState = reducer(memoryState, action)
136 | listeners.forEach((listener) => {
137 | listener(memoryState)
138 | })
139 | }
140 |
141 | type Toast = Omit
142 |
143 | function toast({ ...props }: Toast) {
144 | const id = genId()
145 |
146 | const update = (props: ToasterToast) =>
147 | dispatch({
148 | type: "UPDATE_TOAST",
149 | toast: { ...props, id },
150 | })
151 | const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
152 |
153 | dispatch({
154 | type: "ADD_TOAST",
155 | toast: {
156 | ...props,
157 | id,
158 | open: true,
159 | onOpenChange: (open) => {
160 | if (!open) dismiss()
161 | },
162 | },
163 | })
164 |
165 | return {
166 | id: id,
167 | dismiss,
168 | update,
169 | }
170 | }
171 |
172 | function useToast() {
173 | const [state, setState] = React.useState(memoryState)
174 |
175 | React.useEffect(() => {
176 | listeners.push(setState)
177 | return () => {
178 | const index = listeners.indexOf(setState)
179 | if (index > -1) {
180 | listeners.splice(index, 1)
181 | }
182 | }
183 | }, [state])
184 |
185 | return {
186 | ...state,
187 | toast,
188 | dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
189 | }
190 | }
191 |
192 | export { useToast, toast }
193 |
--------------------------------------------------------------------------------
/intellichat/src/lib/ai-providers.ts:
--------------------------------------------------------------------------------
1 | // Description: Chat providers and their models.
2 |
3 | import { SupportedProvidersNamesType } from './validators';
4 |
5 | const OpenAIModels = ['gpt-4o', 'gpt-4o-mini', 'o1', 'o1-mini', 'o3-mini', 'gpt-4', 'gpt-3.5-turbo'] as const;
6 | const ReplicateModels = [
7 | '70b-chat',
8 | '13b-chat',
9 | '34b-code',
10 | '34b-python',
11 | '13b-code-instruct',
12 | ] as const;
13 | const CohereModels = ['command-r-plus', 'command-r', 'command'] as const;
14 | const GoogleModels = ['gemini'] as const;
15 | const MistralModels = ['mistral-tiny', 'mistral-medium'] as const;
16 | const AnthropicModels = ['claude-3-sonnet-20240229', 'claude-3-opus-20240229'] as const;
17 | const VLLMModels = [] as const;
18 |
19 | export const AIProviders = {
20 | openai: {
21 | name: 'openai' as const,
22 | models: OpenAIModels,
23 | },
24 | replicate: {
25 | name: 'replicate' as const,
26 | models: ReplicateModels,
27 | },
28 | cohere: {
29 | name: 'cohere' as const,
30 | models: CohereModels,
31 | },
32 | google: {
33 | name: 'google' as const,
34 | models: GoogleModels,
35 | },
36 | mistral: {
37 | name: 'mistral' as const,
38 | models: MistralModels,
39 | },
40 | anthropic: {
41 | name: 'anthropic' as const,
42 | models: AnthropicModels,
43 | },
44 | // azure is a special case, it has a different validator
45 | // and the model names are entered manually instead of being a list
46 | azure: {
47 | name: 'azure' as const,
48 | },
49 | vllm: {
50 | name: 'vllm' as const
51 | },
52 | };
53 |
54 | // Create a record of all providers with false values, this will be used to
55 | // check if a provider api key is defined in the environment variables.
56 | export const envKeys = Object.fromEntries(
57 | Object.keys(AIProviders).map((key) => [key, false])
58 | ) as Record;
59 |
--------------------------------------------------------------------------------
/intellichat/src/lib/helpers.ts:
--------------------------------------------------------------------------------
1 | export const serializeError = (error: any) => {
2 | return {
3 | message: error.message || 'Unknown error',
4 | name: error.name,
5 | // Add any other relevant error properties you want to capture
6 | stack: process.env.NODE_ENV === 'development' ? error.stack : undefined
7 | };
8 | };
--------------------------------------------------------------------------------
/intellichat/src/lib/intellinode.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ChatContext,
3 | ChatGPTInput,
4 | Chatbot,
5 | CohereInput,
6 | GeminiInput,
7 | LLamaReplicateInput,
8 | MistralInput,
9 | AnthropicInput,
10 | ProxyHelper,
11 | VLLMInput,
12 | SupportedChatModels,
13 | } from 'intellinode';
14 | import { ChatProvider } from './types';
15 | import {
16 | azureType,
17 | azureValidator,
18 | cohereType,
19 | cohereValidator,
20 | googleType,
21 | googleValidator,
22 | mistralValidator,
23 | mistralType,
24 | openAIType,
25 | openAIValidator,
26 | replicateType,
27 | replicateValidator,
28 | anthropicType,
29 | anthropicValidator,
30 | vllmValidator
31 | } from './validators';
32 |
33 | // We can use this function to get the default provider key if onekey is provided and starts with 'in'
34 | export function getDefaultProviderKey(provider: ChatProvider, oneKey?: string) {
35 | if (!oneKey || (oneKey && !oneKey.startsWith('in'))) {
36 | return null;
37 | }
38 |
39 | switch (provider) {
40 | case 'openai':
41 | return process.env.INTELLI_OPENAI_API_KEY;
42 | case 'replicate':
43 | return process.env.INTELLI_REPLICATE_API_KEY;
44 | case 'azure':
45 | return process.env.INTELLI_AZURE_API_KEY;
46 | case 'cohere':
47 | return process.env.INTELLI_COHERE_API_KEY;
48 | case 'google':
49 | return process.env.INTELLI_GOOGLE_API_KEY;
50 | case 'anthropic':
51 | return process.env.INTELLI_Anthropic_API_KEY;
52 | default:
53 | return null;
54 | }
55 | }
56 |
57 | export function getChatProviderKey(provider: ChatProvider) {
58 | switch (provider) {
59 | case 'openai':
60 | return process.env.OPENAI_API_KEY;
61 | case 'replicate':
62 | return process.env.REPLICATE_API_KEY;
63 | case 'azure':
64 | return process.env.AZURE_API_KEY;
65 | case 'cohere':
66 | return process.env.COHERE_API_KEY;
67 | case 'google':
68 | return process.env.GOOGLE_API_KEY;
69 | case 'mistral':
70 | return process.env.MISTRAL_API_KEY;
71 | case 'anthropic':
72 | return process.env.Anthropic_API_KEY;
73 | case 'vllm':
74 | return null;
75 | default:
76 | return null;
77 | }
78 | }
79 |
80 | type getAzureChatResponseParams = {
81 | systemMessage: string;
82 | messages: {
83 | role: 'user' | 'assistant';
84 | content: string;
85 | }[];
86 | provider?: azureType;
87 | withContext: boolean;
88 | n: number;
89 | oneKey?: string;
90 | };
91 |
92 | export async function getAzureChatResponse({
93 | systemMessage,
94 | messages,
95 | provider,
96 | withContext,
97 | oneKey,
98 | n,
99 | }: getAzureChatResponseParams) {
100 | const parsed = azureValidator.safeParse(provider);
101 |
102 | if (!parsed.success) {
103 | const { error } = parsed;
104 | throw new Error(error.message);
105 | }
106 |
107 | const { apiKey, resourceName, model, embeddingName, name } = parsed.data;
108 | const proxy = createProxy(resourceName);
109 |
110 | const chatbot = new Chatbot(
111 | apiKey,
112 | 'openai',
113 | proxy,
114 | ...(oneKey ? [{ oneKey, intelliBase: process.env.CUSTOM_INTELLIBASE_URL }] : [])
115 | );
116 |
117 | const input = getChatInput(name, model, systemMessage);
118 |
119 | if (withContext) {
120 | const contextResponse = await getContextResponse({
121 | apiKey,
122 | proxy,
123 | messages,
124 | model: embeddingName,
125 | n,
126 | });
127 | addMessages(input, contextResponse);
128 | } else {
129 | addMessages(input, messages);
130 | }
131 |
132 | const responses = await chatbot.chat(input);
133 | return responses[0];
134 | }
135 |
136 | type getChatResponseParams = {
137 | systemMessage: string;
138 | messages: {
139 | role: 'user' | 'assistant';
140 | content: string;
141 | }[];
142 | provider?: openAIType | replicateType | cohereType | googleType | mistralType | anthropicType;
143 | withContext: boolean;
144 | stream?: boolean;
145 | n: number;
146 | contextKey?: string | null;
147 | oneKey?: string;
148 | intellinodeData?: boolean;
149 | onChunk?: (chunk: string) => Promise;
150 | intelliBase?: string;
151 | };
152 |
153 | const validateProvider = (name: string) => {
154 | switch (name) {
155 | case 'openai':
156 | return openAIValidator;
157 | case 'replicate':
158 | return replicateValidator;
159 | case 'cohere':
160 | return cohereValidator;
161 | case 'google':
162 | return googleValidator;
163 | case 'mistral':
164 | return mistralValidator;
165 | case 'anthropic':
166 | return anthropicValidator;
167 | case 'vllm':
168 | return vllmValidator;
169 | default:
170 | throw new Error('provider is not supported');
171 | }
172 | };
173 |
174 | export async function getChatResponse({
175 | systemMessage,
176 | messages,
177 | provider,
178 | withContext,
179 | stream,
180 | n,
181 | contextKey,
182 | oneKey,
183 | intellinodeData,
184 | onChunk,
185 | intelliBase,
186 | }: getChatResponseParams) {
187 | if (!provider) {
188 | throw new Error('provider is required');
189 | }
190 | const parsed = validateProvider(provider.name).safeParse(provider);
191 |
192 | if (!parsed.success) {
193 | throw new Error(parsed.error.message);
194 | }
195 |
196 | const { apiKey, model, name, baseUrl } = parsed.data;
197 | const finalApiKey = name === 'vllm' ? "" : apiKey;
198 | const chatbot = new Chatbot(
199 | finalApiKey,
200 | name === 'google' ? 'gemini' : name,
201 | null,
202 | oneKey && intellinodeData
203 | ? { baseUrl, oneKey, intelliBase: intelliBase || process.env.CUSTOM_INTELLIBASE_URL }
204 | : { baseUrl }
205 | );
206 |
207 |
208 | const input = getChatInput(name, model, systemMessage);
209 |
210 | if (withContext) {
211 | if (!contextKey) {
212 | throw new Error('contextKey is required');
213 | }
214 |
215 | const contextResponse = await getContextResponse({
216 | apiKey: contextKey,
217 | messages,
218 | n,
219 | });
220 | addMessages(input, contextResponse);
221 | } else {
222 | addMessages(input, messages);
223 | }
224 |
225 | if ((name === 'openai' || name === 'cohere' || name === 'vllm') && stream && onChunk) {
226 | const streamData = await chatbot.stream(input);
227 | let fullResponse = '';
228 |
229 | try {
230 | for await (const chunk of streamData) {
231 | fullResponse += chunk;
232 | // If Response is available as a string
233 | const textChunk = typeof chunk === 'string' ? chunk : JSON.stringify(chunk);
234 |
235 | await onChunk(textChunk);
236 | }
237 |
238 | console.log('fullResponse', fullResponse);
239 |
240 | return {
241 | result: [fullResponse],
242 | references: null
243 | };
244 | } catch (error) {
245 | console.error('Streaming error:', error);
246 | throw error;
247 | }
248 | } else {
249 | // Handle non-streaming response
250 | const responses = await chatbot.chat(input);
251 | console.log("responses", responses);
252 | return responses;
253 | }
254 | }
255 |
256 | function getChatInput(provider: string, model: string, systemMessage: string) {
257 | switch (provider) {
258 | case 'openai':
259 | case 'azure':
260 | return new ChatGPTInput(systemMessage, { model, attachReference: true });
261 | case 'replicate':
262 | return new LLamaReplicateInput(systemMessage, {
263 | model,
264 | attachReference: true,
265 | });
266 | case 'cohere':
267 | return new CohereInput(systemMessage, { model, attachReference: true });
268 | case 'google':
269 | return new GeminiInput(systemMessage, { model, attachReference: true });
270 | case 'mistral':
271 | return new MistralInput(systemMessage, { model, attachReference: true });
272 | case 'anthropic':
273 | return new AnthropicInput(systemMessage, { model, attachReference: true });
274 | case 'vllm':
275 | return new VLLMInput(systemMessage, { model, attachReference: true, });
276 | default:
277 | throw new Error('provider is not supported');
278 | }
279 | }
280 |
281 | function addMessages(
282 | chatInput: ChatGPTInput | LLamaReplicateInput | CohereInput | GeminiInput,
283 | messages: {
284 | role: 'user' | 'assistant';
285 | content: string;
286 | }[]
287 | ) {
288 | messages.forEach((m) => {
289 | if (m.role === 'user') {
290 | chatInput.addUserMessage(m.content);
291 | } else {
292 | chatInput.addAssistantMessage(m.content);
293 | }
294 | });
295 | }
296 |
297 | function createProxy(resourceName: string) {
298 | const proxy = new ProxyHelper();
299 | proxy.setAzureOpenai(resourceName);
300 | return proxy;
301 | }
302 |
303 | type ContextResponseParams = {
304 | apiKey: string;
305 | proxy?: ProxyHelper | null;
306 | messages: { role: 'user' | 'assistant'; content: string }[];
307 | model?: string | null;
308 | n?: number;
309 | };
310 |
311 | async function getContextResponse({
312 | apiKey,
313 | proxy = null,
314 | messages,
315 | model,
316 | n = 2,
317 | }: ContextResponseParams) {
318 | const context = new ChatContext(apiKey, 'openai', proxy);
319 | // extract the last message from the array; this is the user's message
320 | const userMessage = messages[messages.length - 1].content;
321 |
322 | // get the closest context to the user's message
323 | const contextResponse = await context.getRoleContext(
324 | userMessage,
325 | messages,
326 | n,
327 | model
328 | );
329 | return contextResponse;
330 | }
331 |
--------------------------------------------------------------------------------
/intellichat/src/lib/schema.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod';
2 | import { ProvidersValidator, providerNames } from './validators';
3 |
4 | export const formSchema = z
5 | .object({
6 | systemMessage: z.string(),
7 | numberOfMessages: z.number().min(2).max(6),
8 | providerName: z.enum(providerNames),
9 | providerModel: z.string().optional(),
10 | providers: ProvidersValidator,
11 | withContext: z.boolean(),
12 | stream: z.boolean(),
13 | intellinodeData: z.boolean(),
14 | oneKey: z.string().optional(),
15 | envKeys: z.record(z.boolean()),
16 | })
17 | .superRefine((data, ctx) => {
18 | const name = data.providerName;
19 | if (name === 'vllm') {
20 | // For vLLM, require that the nested model and baseUrl are non-empty.
21 | if (!data.providers.vllm?.model || data.providers.vllm.model.trim() === "") {
22 | ctx.addIssue({
23 | code: z.ZodIssueCode.custom,
24 | message: "vLLM model is required",
25 | path: ["providers", "vllm", "model"],
26 | });
27 | }
28 | if (!data.providers.vllm?.baseUrl || data.providers.vllm.baseUrl.trim() === "") {
29 | ctx.addIssue({
30 | code: z.ZodIssueCode.custom,
31 | message: "vLLM Base URL is required",
32 | path: ["providers", "vllm", "baseUrl"],
33 | });
34 | }
35 | } else {
36 | // For non-vllm providers, ensure that an API key exists.
37 | const keyValue = data.providers[name]?.apiKey;
38 | const envKey = data.envKeys[name];
39 | const keyExists = keyValue || envKey;
40 | if ((!data.intellinodeData || !data.oneKey) && !keyExists) {
41 | ctx.addIssue({
42 | code: z.ZodIssueCode.custom,
43 | message: `${name} API Key is required`,
44 | path: ["providers", name, "apiKey"],
45 | });
46 | }
47 | }
48 | // If withContext is enabled, enforce that OpenAI has a key.
49 | if (data.withContext && !data.envKeys.openAi && !data.providers.openai?.apiKey) {
50 | ctx.addIssue({
51 | code: z.ZodIssueCode.custom,
52 | message: 'OpenAI API Key is required.',
53 | path: ['openaiKey'],
54 | });
55 | }
56 | });
57 |
--------------------------------------------------------------------------------
/intellichat/src/lib/types.ts:
--------------------------------------------------------------------------------
1 | export type Message = {
2 | id: string;
3 | content: string;
4 | role: 'user' | 'assistant';
5 | references?: string[] | null;
6 | isStreaming?: boolean;
7 | };
8 |
9 | export type ChatProvider =
10 | | 'openai'
11 | | 'replicate'
12 | | 'azure'
13 | | 'cohere'
14 | | 'google'
15 | | 'mistral'
16 | | 'anthropic'
17 | | 'vllm';
--------------------------------------------------------------------------------
/intellichat/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
8 | /**
9 | * Determines if a string is AR (more than 50% characters)
10 | * @param text The text to analyze
11 | * @returns boolean indicating if text is primarily Arabic
12 | */
13 | export function isPrimarilyRtl(text: string): boolean {
14 | if (!text || text.length === 0) return false;
15 |
16 | // Arabic Unicode range is U+0600 to U+06FF
17 | const arabicRegex = /[\u0600-\u06FF]/g;
18 | const arabicMatches = text.match(arabicRegex);
19 |
20 | const arabicCount = arabicMatches ? arabicMatches.length : 0;
21 | const percentage = (arabicCount / text.length) * 100;
22 |
23 | return percentage > 50;
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/intellichat/src/lib/validators.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod';
2 | import { AIProviders } from './ai-providers';
3 |
4 | // List of supported provider names (e.g. 'openai', 'replicate', etc.)
5 | export const providerNames = Object.keys(AIProviders) as [
6 | keyof typeof AIProviders,
7 | ...Array,
8 | ];
9 |
10 | // Create a validator for a given provider
11 | const createProviderValidator = (
12 | provider: (typeof AIProviders)[keyof typeof AIProviders]
13 | ) => {
14 | return z.object({
15 | name: z.literal(provider.name),
16 | model: provider.name !== 'azure' ? z.enum(provider.models) : z.string(),
17 | apiKey: z.string(),
18 | });
19 | };
20 |
21 | export const openAIValidator = createProviderValidator(AIProviders.openai);
22 | export type openAIType = z.infer;
23 |
24 | export const replicateValidator = createProviderValidator(
25 | AIProviders.replicate
26 | );
27 | export type replicateType = z.infer;
28 |
29 | export const cohereValidator = createProviderValidator(AIProviders.cohere);
30 | export type cohereType = z.infer;
31 |
32 | export const googleValidator = createProviderValidator(AIProviders.google);
33 | export type googleType = z.infer;
34 |
35 | export const azureValidator = createProviderValidator(AIProviders.azure).extend(
36 | { resourceName: z.string(), embeddingName: z.string() }
37 | );
38 | export type azureType = z.infer;
39 |
40 | export const mistralValidator = createProviderValidator(AIProviders.mistral);
41 | export type mistralType = z.infer;
42 |
43 | export const anthropicValidator = createProviderValidator(AIProviders.anthropic);
44 | export type anthropicType = z.infer;
45 |
46 | export const vllmValidator = z.object({
47 | name: z.literal('vllm'),
48 | model: z.string().min(1, { message: "Model is required" }).or(z.literal("")),
49 | apiKey: z.preprocess(
50 | (val) => (val === null ? undefined : val),
51 | z.string().optional().default("")
52 | ),
53 | baseUrl: z.string().min(1, { message: "Base URL is required" }).or(z.literal("")),
54 | });
55 |
56 | export const ProvidersValidator = z.object({
57 | openai: openAIValidator.optional(),
58 | replicate: replicateValidator.optional(),
59 | cohere: cohereValidator.optional(),
60 | google: googleValidator.optional(),
61 | azure: azureValidator.optional(),
62 | mistral: mistralValidator.optional(),
63 | anthropic: anthropicValidator.optional(),
64 | vllm: vllmValidator.optional(),
65 | });
66 |
67 | export type vllmType = z.infer;
68 |
69 | export type SupportedProvidersType = z.infer;
70 | export type SupportedProvidersNamesType = keyof SupportedProvidersType;
71 |
72 | // Create a validator for the chatbot payload
73 | export const chatbotValidator = z.object({
74 | messages: z.array(
75 | z.object({
76 | content: z.string(),
77 | role: z.enum(['user', 'assistant']),
78 | })
79 | ),
80 | provider: z.enum(providerNames),
81 | providers: ProvidersValidator,
82 | systemMessage: z.string().optional(),
83 | withContext: z.boolean(),
84 | stream: z.boolean(),
85 | intellinodeData: z.boolean(),
86 | oneKey: z.string().optional(),
87 | n: z.number().optional(),
88 | });
89 | export type PostMessagePayload = z.infer;
90 |
--------------------------------------------------------------------------------
/intellichat/src/store/chat-settings.ts:
--------------------------------------------------------------------------------
1 | import { envKeys } from '@/lib/ai-providers';
2 | import type { Message } from '@/lib/types';
3 | import type {
4 | PostMessagePayload,
5 | SupportedProvidersNamesType,
6 | SupportedProvidersType,
7 | } from '@/lib/validators';
8 | import { create } from 'zustand';
9 | import { persist } from 'zustand/middleware';
10 |
11 | type ChatSettingsState = {
12 | messages: Message[];
13 | isSidebarOpen: boolean;
14 | systemMessage: string;
15 | provider: SupportedProvidersNamesType;
16 | numberOfMessages: number;
17 | providers: SupportedProvidersType;
18 | withContext: boolean;
19 | stream : boolean;
20 | intellinodeData: boolean;
21 | oneKey: string;
22 | envKeys: Record;
23 | getModel: () => string | undefined;
24 | getSettings: () => Omit;
25 | getProvider: () => SupportedProvidersType[SupportedProvidersNamesType];
26 | updateChatSettings: (settings: Partial) => void;
27 | toggleSidebar: () => void;
28 | setMessage: (message: Message) => void;
29 | setOneKey: (key: string | null) => void;
30 | setEnvKeys: (envKeys: Record) => void;
31 | clearMessages: () => void;
32 | resetState: () => void;
33 | };
34 |
35 | const initialProviders: ChatSettingsState['providers'] = {
36 | cohere: { name: 'cohere', model: 'command', apiKey: '' },
37 | openai: { name: 'openai', model: 'gpt-4o', apiKey: '' },
38 | replicate: {
39 | name: 'replicate',
40 | model: '70b-chat',
41 | apiKey: '',
42 | },
43 | google: { name: 'google', model: 'gemini', apiKey: '' },
44 |
45 | azure: {
46 | name: 'azure',
47 | model: '',
48 | apiKey: '',
49 | resourceName: '',
50 | embeddingName: '',
51 | },
52 | mistral: { name: 'mistral', model: 'mistral-tiny', apiKey: '' },
53 | anthropic: { name: 'anthropic', model: 'claude-3-sonnet-20240229', apiKey: '' },
54 | vllm: {
55 | name: 'vllm',
56 | model: '',
57 | baseUrl: '',
58 | apiKey: '',
59 | },
60 | };
61 |
62 | const initialState = {
63 | intellinodeData: false,
64 | oneKey: '',
65 | withContext: false,
66 | stream: false,
67 | systemMessage: '',
68 | provider: 'openai' as SupportedProvidersNamesType,
69 | numberOfMessages: 4,
70 | messages: [],
71 | isSidebarOpen: false,
72 | providers: initialProviders,
73 | };
74 |
75 | export const useChatSettings = create()(
76 | persist(
77 | (set, get) => ({
78 | ...initialState,
79 | envKeys,
80 | clearMessages: () => set((state) => ({ ...state, messages: [] })),
81 | setMessage: (message: Message) => {
82 | set((state) => ({ ...state, messages: [...state.messages, message] }));
83 | },
84 | resetState: () => {
85 | const { messages, ...rest } = initialState;
86 | set((state) => ({ ...state, ...rest }));
87 | },
88 | getSettings: () => {
89 | let settings: Omit = {
90 | provider: get().provider,
91 | providers: get().providers,
92 | systemMessage: get().systemMessage,
93 | n: get().numberOfMessages,
94 | withContext: get().withContext,
95 | oneKey: get().oneKey,
96 | stream :get().stream,
97 | intellinodeData: get().intellinodeData,
98 | };
99 | return settings;
100 | },
101 | getProvider: () => {
102 | const provider = get().provider;
103 | const providers = get().providers;
104 | return provider ? providers[provider] : providers.openai;
105 | },
106 | getModel: () => {
107 | const provider = get().provider;
108 | const providers = get().providers;
109 | return provider ? providers[provider]?.model : providers.openai?.model;
110 | },
111 | setEnvKeys: (envKeys: Record) => {
112 | set((state) => ({ ...state, envKeys }));
113 | },
114 | setOneKey: (key: string | null) => {
115 | set((state) => ({
116 | ...state,
117 | oneKey: key ?? '',
118 | intellinodeData: key !== null,
119 | }));
120 | },
121 | updateChatSettings: (settings: Partial) => {
122 | set((state) => ({ ...state, ...settings }));
123 | },
124 | toggleSidebar: () => {
125 | set((state) => ({ isSidebarOpen: !state.isSidebarOpen }));
126 | },
127 | }),
128 | {
129 | partialize: (state) =>
130 | Object.fromEntries(
131 | Object.entries(state).filter(
132 | ([key]) => !['messages', 'intellinodeData'].includes(key)
133 | )
134 | ),
135 | name: 'chat-settings',
136 | }
137 | )
138 | );
139 |
--------------------------------------------------------------------------------
/intellichat/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | darkMode: ['class'],
4 | content: [
5 | './pages/**/*.{ts,tsx}',
6 | './components/**/*.{ts,tsx}',
7 | './app/**/*.{ts,tsx}',
8 | './src/**/*.{ts,tsx}',
9 | ],
10 | theme: {
11 | container: {
12 | center: true,
13 | padding: '2rem',
14 | screens: {
15 | '2xl': '1400px',
16 | },
17 | },
18 | extend: {
19 | maxWidth: {
20 | max: '1040px',
21 | },
22 | margin: {
23 | base: '2.5rem',
24 | md: '3.5rem',
25 | },
26 | padding: {
27 | base: '2.5rem',
28 | md: '3.5rem',
29 | },
30 | colors: {
31 | border: 'hsl(var(--border))',
32 | input: 'hsl(var(--input))',
33 | ring: 'hsl(var(--ring))',
34 | background: 'hsl(var(--background))',
35 | foreground: 'hsl(var(--foreground))',
36 | primary: {
37 | DEFAULT: 'hsl(var(--primary))',
38 | foreground: 'hsl(var(--primary-foreground))',
39 | },
40 | secondary: {
41 | DEFAULT: 'hsl(var(--secondary))',
42 | foreground: 'hsl(var(--secondary-foreground))',
43 | },
44 | destructive: {
45 | DEFAULT: 'hsl(var(--destructive))',
46 | foreground: 'hsl(var(--destructive-foreground))',
47 | },
48 | muted: {
49 | DEFAULT: 'hsl(var(--muted))',
50 | foreground: 'hsl(var(--muted-foreground))',
51 | },
52 | accent: {
53 | DEFAULT: 'hsl(var(--accent))',
54 | foreground: 'hsl(var(--accent-foreground))',
55 | },
56 | popover: {
57 | DEFAULT: 'hsl(var(--popover))',
58 | foreground: 'hsl(var(--popover-foreground))',
59 | },
60 | card: {
61 | DEFAULT: 'hsl(var(--card))',
62 | foreground: 'hsl(var(--card-foreground))',
63 | },
64 | },
65 | borderRadius: {
66 | lg: 'var(--radius)',
67 | md: 'calc(var(--radius) - 2px)',
68 | sm: 'calc(var(--radius) - 4px)',
69 | },
70 | keyframes: {
71 | 'accordion-down': {
72 | from: { height: 0 },
73 | to: { height: 'var(--radix-accordion-content-height)' },
74 | },
75 | 'accordion-up': {
76 | from: { height: 'var(--radix-accordion-content-height)' },
77 | to: { height: 0 },
78 | },
79 | },
80 | animation: {
81 | 'accordion-down': 'accordion-down 0.2s ease-out',
82 | 'accordion-up': 'accordion-up 0.2s ease-out',
83 | },
84 | },
85 | },
86 | plugins: [require('tailwindcss-animate'), require('@tailwindcss/typography')],
87 | };
88 |
--------------------------------------------------------------------------------
/intellichat/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "node",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./src/*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------