├── .dockerignore
├── .env.example
├── .eslintignore
├── .eslintrc.json
├── .github
└── workflows
│ └── docker-image.yml
├── .gitignore
├── .vscode
└── settings.json
├── Dockerfile
├── LICENSE.txt
├── README.ja.md
├── README.md
├── README.zh_CN.md
├── README.zh_HK.md
├── app
├── [locale]
│ ├── (home)
│ │ ├── (chat)
│ │ │ └── page.tsx
│ │ ├── action.tsx
│ │ ├── layout.tsx
│ │ ├── provider.tsx
│ │ ├── search
│ │ │ └── page.tsx
│ │ └── sidebar.tsx
│ ├── layout.tsx
│ └── provider.tsx
├── api
│ ├── app
│ │ └── latest
│ │ │ └── route.ts
│ ├── chat
│ │ └── messages
│ │ │ ├── amazon
│ │ │ └── route.ts
│ │ │ ├── anthropic
│ │ │ └── route.ts
│ │ │ ├── azure
│ │ │ └── route.ts
│ │ │ ├── cohere
│ │ │ └── route.ts
│ │ │ ├── fireworks
│ │ │ └── route.ts
│ │ │ ├── google
│ │ │ └── route.ts
│ │ │ ├── groq
│ │ │ └── route.ts
│ │ │ ├── huggingface
│ │ │ └── route.ts
│ │ │ ├── mistral
│ │ │ └── route.ts
│ │ │ ├── openai
│ │ │ └── route.ts
│ │ │ ├── perplexity
│ │ │ └── route.ts
│ │ │ └── route.ts
│ └── search
│ │ ├── google
│ │ └── route.ts
│ │ └── route.ts
├── favicon.ico
├── layout.tsx
├── not-found.tsx
└── provider.tsx
├── components.json
├── components
├── layout
│ ├── add-button.tsx
│ ├── brand.tsx
│ ├── chat
│ │ ├── conversation-window.tsx
│ │ └── input-box.tsx
│ ├── history-list.tsx
│ ├── language-dropdown.tsx
│ ├── lightbox.tsx
│ ├── message.tsx
│ ├── model-select.tsx
│ ├── search-select.tsx
│ ├── search
│ │ ├── block
│ │ │ ├── additions.tsx
│ │ │ ├── answer.tsx
│ │ │ ├── ask-follow-up-question.tsx
│ │ │ ├── block-title.tsx
│ │ │ ├── clarifier.tsx
│ │ │ ├── error.tsx
│ │ │ ├── focus-point.tsx
│ │ │ ├── images.tsx
│ │ │ ├── question.tsx
│ │ │ ├── related.tsx
│ │ │ ├── searching.tsx
│ │ │ ├── sources.tsx
│ │ │ └── try-ask.tsx
│ │ ├── input-box.tsx
│ │ └── search-window.tsx
│ ├── settings-dialog.tsx
│ ├── settings-drawer.tsx
│ ├── settings
│ │ ├── general.tsx
│ │ ├── provider.tsx
│ │ ├── provider
│ │ │ ├── amazon.tsx
│ │ │ ├── anthropic.tsx
│ │ │ ├── azure.tsx
│ │ │ ├── cohere.tsx
│ │ │ ├── custom.tsx
│ │ │ ├── fireworks.tsx
│ │ │ ├── google.tsx
│ │ │ ├── groq.tsx
│ │ │ ├── huggingface.tsx
│ │ │ ├── mistral.tsx
│ │ │ ├── openai.tsx
│ │ │ └── perplexity.tsx
│ │ ├── search.tsx
│ │ └── searcher
│ │ │ └── tavily.tsx
│ ├── share-button.tsx
│ ├── theme-dropdown.tsx
│ ├── user-avatar.tsx
│ ├── version-badge.tsx
│ └── version-label.tsx
└── ui
│ ├── avatar.tsx
│ ├── badge.tsx
│ ├── button.tsx
│ ├── checkbox.tsx
│ ├── custom
│ ├── checkbox.tsx
│ ├── dialog.tsx
│ ├── drawer.tsx
│ ├── dropdown-menu.tsx
│ ├── input.tsx
│ ├── select.tsx
│ ├── switch.tsx
│ └── textarea.tsx
│ ├── dialog.tsx
│ ├── drawer.tsx
│ ├── dropdown-menu.tsx
│ ├── input.tsx
│ ├── label.tsx
│ ├── select.tsx
│ ├── switch.tsx
│ ├── tabs.tsx
│ └── textarea.tsx
├── config
├── i18n
│ └── index.ts
├── provider
│ ├── amazon.ts
│ ├── anthropic.ts
│ ├── azure.ts
│ ├── cohere.ts
│ ├── fireworks.ts
│ ├── google.ts
│ ├── groq.ts
│ ├── huggingface.ts
│ ├── index.ts
│ ├── mistral.ts
│ ├── openai.ts
│ └── perplexity.ts
├── search
│ ├── index.ts
│ └── question.ts
└── theme
│ └── index.ts
├── docker-compose.yml
├── hooks
├── storage.ts
├── store.ts
├── theme.tsx
└── window.ts
├── i18n.ts
├── lib
├── prompt
│ └── index.ts
├── provider
│ ├── Anthropic.ts
│ ├── Google.ts
│ └── OpenAI.ts
├── search
│ ├── challenger.tsx
│ ├── clarifier.tsx
│ ├── illustrator.tsx
│ └── searcher.tsx
└── ui
│ └── utils.ts
├── locales
├── de.json
├── en.json
├── es.json
├── fr.json
├── it.json
├── ja.json
├── ko.json
├── nl.json
├── pt.json
├── ru.json
├── zh-CN.json
├── zh-HK.json
└── zh-TW.json
├── middleware.ts
├── next.config.mjs
├── package.json
├── pnpm-lock.yaml
├── postcss.config.js
├── prettier.config.js
├── public
├── OpenAI.svg
├── favicon.ico
├── hero.png
├── icon.svg
├── icons
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── apple-touch-icon.png
│ ├── browserconfig.xml
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon.ico
│ ├── mstile-150x150.png
│ └── safari-pinned-tab.svg
├── img
│ ├── Amazon.png
│ ├── Anthropic.png
│ ├── Azure.png
│ ├── Cohere.png
│ ├── Custom.png
│ ├── Fireworks.png
│ ├── Google.png
│ ├── Groq.png
│ ├── HuggingFace.png
│ ├── Mistral.png
│ ├── OpenAI.png
│ ├── Perplexity.png
│ ├── Replicate.png
│ ├── Tavily.png
│ └── Team.png
└── manifest.json
├── renovate.json
├── styles
└── globals.css
├── tailwind.config.ts
├── tsconfig.json
├── types
├── app.ts
├── conversation.ts
├── i18n.ts
├── model.ts
├── search.ts
├── search
│ └── resources.ts
└── settings.ts
└── utils
├── app
├── time.ts
├── uuid.ts
└── version.ts
├── provider
├── cohere.tsx
└── google.tsx
└── search
├── engines
├── google.ts
├── tavily.ts
└── you.ts
└── image.ts
/.dockerignore:
--------------------------------------------------------------------------------
1 | **/.classpath
2 | **/.dockerignore
3 | **/.env
4 | **/.git
5 | **/.gitignore
6 | **/.project
7 | **/.settings
8 | **/.toolstarget
9 | **/.vs
10 | **/.vscode
11 | **/*.*proj.user
12 | **/*.dbmdl
13 | **/*.jfm
14 | **/charts
15 | **/docker-compose*
16 | **/compose*
17 | **/Dockerfile*
18 | **/node_modules
19 | **/npm-debug.log
20 | **/obj
21 | **/secrets.dev.yaml
22 | **/values.dev.yaml
23 | LICENSE
24 | README.md
25 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | # --------------- Providers -----------------
2 |
3 | ## Amazon (not supported so far)
4 | NEXT_PUBLIC_ACCESS_AWS=
5 | AWS_ACCESS_KEY=
6 | AWS_SECRET_KEY=
7 | AWS_REGION=
8 |
9 | ## Anthropic
10 | NEXT_PUBLIC_ACCESS_ANTHROPIC=
11 | ANTHROPIC_API_KEY=
12 |
13 | ## Azure (not supported so far)
14 | NEXT_PUBLIC_ACCESS_AZURE=
15 | AZURE_OPENAI_API_KEY=
16 | AZURE_OPENAI_ENDPOINT=
17 | AZURE_OPENAI_DEPLOY_INSTANCE_NAME=
18 |
19 | ## Cohere
20 | NEXT_PUBLIC_ACCESS_COHERE=
21 | COHERE_API_KEY=
22 |
23 | ## Fireworks
24 | NEXT_PUBLIC_ACCESS_FIREWORKS=
25 | FIREWORKS_API_KEY=
26 |
27 | ## Google
28 | NEXT_PUBLIC_ACCESS_GOOGLE=
29 | GOOGLE_API_KEY=
30 |
31 | ## Groq
32 | NEXT_PUBLIC_ACCESS_GROQ=
33 | GROQ_API_KEY=
34 |
35 | ## Hugging Face
36 | NEXT_PUBLIC_ACCESS_HUGGINGFACE=
37 | HUGGINGFACE_API_KEY=
38 |
39 | ## Mistral
40 | NEXT_PUBLIC_ACCESS_MISTRAL=
41 | MISTRAL_API_KEY=
42 |
43 | ## OpenAI
44 | NEXT_PUBLIC_ACCESS_OPENAI=
45 | OPENAI_API_KEY=
46 | OPENAI_API_ENDPOINT=
47 |
48 | ## Perplexity
49 | NEXT_PUBLIC_ACCESS_PERPLEXITY=
50 | PERPLEXITY_API_KEY=
51 | PERPLEXITY_ENDPOINT=
52 |
53 | # -------------- Search Engines --------------
54 |
55 | ## Google
56 | NEXT_PUBLIC_ACCESS_GOOGLE_SEARCH=
57 | GOOGLE_SEARCH_API_KEY=
58 | GOOGLE_SEARCH_ENGINE_ID=
59 |
60 | ## Tavily
61 | NEXT_PUBLIC_ACCESS_TAVILY_SEARCH=
62 | TAVILY_SEARCH_API_KEY=
63 |
64 | ## You
65 | NEXT_PUBLIC_ACCESS_YOU_SEARCH=
66 | YOU_SEARCH_API_KEY=
67 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | components/ui/**.tsx
2 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "extends": [
4 | "next",
5 | "next/core-web-vitals",
6 | "plugin:tailwindcss/recommended"
7 | ],
8 | "plugins": [
9 | "react",
10 | "simple-import-sort",
11 | "unused-imports"
12 | ],
13 | "parserOptions": {
14 | "sourceType": "module",
15 | "ecmaVersion": "latest"
16 | },
17 | "rules": {
18 | "simple-import-sort/imports": "error",
19 | "simple-import-sort/exports": "error",
20 | "unused-imports/no-unused-imports": "error",
21 | "unused-imports/no-unused-vars": [
22 | "warn",
23 | {
24 | "vars": "all",
25 | "varsIgnorePattern": "^_",
26 | "args": "after-used",
27 | "argsIgnorePattern": "^_"
28 | }
29 | ],
30 | "no-console": "warn",
31 | "react/no-unescaped-entities": "off"
32 | },
33 | "overrides": [
34 | {
35 | "files": [
36 | "*.ts",
37 | "*.tsx",
38 | "*.js"
39 | ],
40 | "parser": "@typescript-eslint/parser"
41 | },
42 | {
43 | "files": [
44 | "*.js",
45 | "*.jsx",
46 | "*.ts",
47 | "*.tsx"
48 | ],
49 | "rules": {
50 | "simple-import-sort/imports": [
51 | "error",
52 | {
53 | "groups": [
54 | [
55 | "^react",
56 | "^@?\\w"
57 | ],
58 | [
59 | "^(@|components)(/.*|$)"
60 | ],
61 | [
62 | "^\\u0000"
63 | ],
64 | [
65 | "^\\.\\.(?!/?$)",
66 | "^\\.\\./?$"
67 | ],
68 | [
69 | "^\\./(?=.*/)(?!/?$)",
70 | "^\\.(?!/?$)",
71 | "^\\./?$"
72 | ],
73 | [
74 | "^.+\\.?(css)$"
75 | ]
76 | ]
77 | }
78 | ]
79 | }
80 | }
81 | ]
82 | }
--------------------------------------------------------------------------------
/.github/workflows/docker-image.yml:
--------------------------------------------------------------------------------
1 | name: ChatChat Version Docker Image CI
2 |
3 | on:
4 | release:
5 | types: [created]
6 | workflow_dispatch:
7 |
8 | jobs:
9 | build-and-publish:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: Checkout code
14 | uses: actions/checkout@v4
15 |
16 | - name: Set up Docker Buildx
17 | uses: docker/setup-buildx-action@v3
18 |
19 | - name: Login to GitHub Container Registry
20 | uses: docker/login-action@v3
21 | with:
22 | registry: ghcr.io
23 | username: ${{ github.actor }}
24 | password: ${{ secrets.GHCR_TOKEN }}
25 |
26 | - name: Extract tag name
27 | id: extract_tag
28 | run: echo "##[set-output name=tag;]$(echo ${GITHUB_REF#refs/tags/})"
29 |
30 | - name: Build and Push Docker image to GHCR
31 | uses: docker/build-push-action@v5
32 | with:
33 | context: .
34 | file: ./Dockerfile
35 | push: true
36 | tags: |
37 | ghcr.io/okisdev/chatchat:${{ steps.extract_tag.outputs.tag }}
38 | ghcr.io/okisdev/chatchat:latest
39 | platforms: linux/amd64,linux/arm64
40 |
41 | - name: Login to Docker Hub
42 | uses: docker/login-action@v3
43 | with:
44 | username: ${{ secrets.DOCKERHUB_USERNAME }}
45 | password: ${{ secrets.DOCKERHUB_TOKEN }}
46 |
47 | - name: Tag and Push Docker image to Docker Hub
48 | uses: docker/build-push-action@v5
49 | with:
50 | context: .
51 | file: ./Dockerfile
52 | push: true
53 | tags: |
54 | okisdev/chatchat:${{ steps.extract_tag.outputs.tag }}
55 | okisdev/chatchat:latest
56 | platforms: linux/amd64,linux/arm64
--------------------------------------------------------------------------------
/.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 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env
30 | .env*.local
31 |
32 | # vercel
33 | .vercel
34 |
35 | # typescript
36 | *.tsbuildinfo
37 | next-env.d.ts
38 |
39 | # pwa
40 | */sw.js
41 | */sw.js.map
42 | */workbox-*.js
43 | */workbox-*.js.map
44 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": [
3 | "buildx",
4 | "DOCKERHUB",
5 | "fastapi",
6 | "Groq",
7 | "huggingface",
8 | "langchain",
9 | "langsmith",
10 | "Lightbox",
11 | "lucide",
12 | "markdownit",
13 | "mistralai",
14 | "mixtral",
15 | "onest",
16 | "rehype",
17 | "sonner",
18 | "Tavily",
19 | "tippyjs"
20 | ],
21 | "python.analysis.typeCheckingMode": "basic",
22 | "python.analysis.autoImportCompletions": true,
23 | "i18n-ally.localesPaths": [
24 | "locales",
25 | ],
26 | "i18n-ally.keystyle": "flat"
27 | }
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:lts-alpine AS base
2 |
3 | WORKDIR /app
4 |
5 | COPY package.json pnpm-lock.yaml ./
6 |
7 | RUN npm i -g pnpm@latest
8 | RUN pnpm install
9 |
10 | COPY . .
11 |
12 | RUN pnpm build
13 |
14 | FROM node:lts-alpine AS production
15 |
16 | WORKDIR /app
17 |
18 | COPY --from=base /app/package*.json ./
19 | COPY --from=base /app/.next ./.next
20 | COPY --from=base /app/public ./public
21 | COPY --from=base /app/node_modules ./node_modules
22 | COPY --from=base /app/next.config.mjs ./next.config.mjs
23 |
24 | RUN npm i -g pnpm@latest
25 |
26 | EXPOSE 3000
27 |
28 | ENV AWS_ACCESS_KEY="" \
29 | AWS_SECRET_KEY="" \
30 | AWS_REGION="" \
31 | ANTHROPIC_API_KEY="" \
32 | AZURE_OPENAI_API_KEY="" \
33 | AZURE_OPENAI_ENDPOINT="" \
34 | AZURE_OPENAI_DEPLOY_INSTANCE_NAME="" \
35 | COHERE_API_KEY="" \
36 | FIREWORKS_API_KEY="" \
37 | GOOGLE_API_KEY="" \
38 | GROQ_API_KEY="" \
39 | HUGGINGFACE_API_KEY="" \
40 | MISTRAL_API_KEY="" \
41 | OPENAI_API_KEY="" \
42 | OPENAI_API_ENDPOINT="" \
43 | PERPLEXITY_API_KEY="" \
44 | PERPLEXITY_ENDPOINT=""
45 |
46 | CMD ["pnpm", "start"]
47 |
--------------------------------------------------------------------------------
/README.ja.md:
--------------------------------------------------------------------------------
1 | # Chat Chat
2 |
3 | > シンプルで使いやすいインターフェイスを備えた、統合されたチャットとAIプラットフォーム。
4 |
5 |
6 | 🇺🇸 | 🇭🇰 | 🇨🇳 | 🇯🇵
7 |
8 |
9 |
10 |
11 | ドキュメント
12 |
13 |
14 |
15 | ## インターフェイス
16 |
17 | 
18 |
19 | 
20 |
21 | https://github.com/okisdev/ChatChat/assets/66008528/388023d5-b21a-40ee-a856-aab31de3d580
22 |
23 | https://github.com/okisdev/ChatChat/assets/66008528/f8d943d5-77c9-479b-9d5f-1e77eabd47b0
24 |
25 | ## 機能
26 |
27 | - 主要なAIプロバイダーに対応(Anthropic、OpenAI、Cohere、Google Geminiなど)
28 | - 自己ホストが容易
29 |
30 | ## 使用方法
31 |
32 | [ドキュメント](https://docs.okis.dev/docs/chat)
33 |
34 | ## デプロイメント
35 |
36 | [](https://vercel.com/import/project?template=https://github.com/okisdev/ChatChat)
37 |
38 | [](https://railway.app/template/-WWW5r)
39 |
40 | 詳細なデプロイ方法は[ドキュメント](https://docs.okis.dev/docs/chat)にて
41 |
42 | ## ライセンス
43 |
44 | [AGPL-3.0](./LICENSE)
45 |
46 | ## 技術スタック
47 |
48 | nextjs / tailwindcss / shadcn UI
49 |
50 | ## 注意
51 |
52 | - AIは不適切なコンテンツを生成する可能性がありますので、注意してご使用ください。
53 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Chat Chat
2 |
3 | > Your own unified chat and search to AI platform, with a simple and easy to use interface.
4 |
5 |
6 | 🇺🇸 | 🇭🇰 | 🇨🇳 | 🇯🇵
7 |
8 |
9 |
10 |
11 | Documentation
12 |
13 |
14 |
15 | ## Interface
16 |
17 | 
18 |
19 | 
20 |
21 | https://github.com/okisdev/ChatChat/assets/66008528/388023d5-b21a-40ee-a856-aab31de3d580
22 |
23 | https://github.com/okisdev/ChatChat/assets/66008528/f8d943d5-77c9-479b-9d5f-1e77eabd47b0
24 |
25 | ## Features
26 |
27 | - Support major AI Providers (Anthropic, OpenAI, Cohere, Google Gemini, etc.)
28 | - Ease self-hosted
29 |
30 | ## Usage
31 |
32 | [docs](https://docs.okis.dev/docs/chat)
33 |
34 | ## Deployment
35 |
36 | [](https://vercel.com/import/project?template=https://github.com/okisdev/ChatChat)
37 |
38 | [](https://railway.app/template/-WWW5r)
39 |
40 | more deployment methods in [docs](https://docs.okis.dev/docs/chat)
41 |
42 | ## LICENSE
43 |
44 | [AGPL-3.0](./LICENSE)
45 |
46 | ## Stack
47 |
48 | nextjs / tailwindcss / shadcn UI
49 |
50 | ## Note
51 |
52 | - AI may generate inappropriate content, please use it with caution.
53 |
--------------------------------------------------------------------------------
/README.zh_CN.md:
--------------------------------------------------------------------------------
1 | # Chat Chat
2 |
3 | > 您自己的统一聊天和搜索至AI平台,界面简单易用。
4 |
5 |
6 | 🇺🇸 | 🇭🇰 | 🇨🇳 | 🇯🇵
7 |
8 |
9 |
10 |
11 | 文档
12 |
13 |
14 |
15 | ## 界面
16 |
17 | 
18 |
19 | 
20 |
21 | https://github.com/okisdev/ChatChat/assets/66008528/388023d5-b21a-40ee-a856-aab31de3d580
22 |
23 | https://github.com/okisdev/ChatChat/assets/66008528/f8d943d5-77c9-479b-9d5f-1e77eabd47b0
24 |
25 | ## 特点
26 |
27 | - 支持主要的AI提供商(Anthropic、OpenAI、Cohere、Google Gemini等)
28 | - 方便自托管
29 |
30 | ## 使用方式
31 |
32 | [文档](https://docs.okis.dev/docs/chat)
33 |
34 | ## 部署
35 |
36 | [](https://vercel.com/import/project?template=https://github.com/okisdev/ChatChat)
37 |
38 | [](https://railway.app/template/-WWW5r)
39 |
40 | 更多部署方法见[文档](https://docs.okis.dev/docs/chat)
41 |
42 | ## 许可证
43 |
44 | [AGPL-3.0](./LICENSE)
45 |
46 | ## 技术栈
47 |
48 | nextjs / tailwindcss / shadcn UI
49 |
50 | ## 注意事项
51 |
52 | - AI可能会生成不适当的内容,请谨慎使用。
53 |
--------------------------------------------------------------------------------
/README.zh_HK.md:
--------------------------------------------------------------------------------
1 | # Chat Chat
2 |
3 | > 你的一體化聊天及搜索人工智能平台,界面簡單易用。
4 |
5 |
6 | 🇺🇸 | 🇭🇰 | 🇨🇳 | 🇯🇵
7 |
8 |
9 |
10 |
11 | 文件
12 |
13 |
14 |
15 | ## 介面
16 |
17 | 
18 |
19 | 
20 |
21 | https://github.com/okisdev/ChatChat/assets/66008528/388023d5-b21a-40ee-a856-aab31de3d580
22 |
23 | https://github.com/okisdev/ChatChat/assets/66008528/f8d943d5-77c9-479b-9d5f-1e77eabd47b0
24 |
25 | ## 功能
26 |
27 | - 支援主要人工智能供應商(Anthropic、OpenAI、Cohere、Google Gemini 等)
28 | - 方便自行託管
29 |
30 | ## 使用方式
31 |
32 | [文件](https://docs.okis.dev/docs/chat)
33 |
34 | ## 部署
35 |
36 | [](https://vercel.com/import/project?template=https://github.com/okisdev/ChatChat)
37 |
38 | [](https://railway.app/template/-WWW5r)
39 |
40 | 更多部署方法在[文件](https://docs.okis.dev/docs/chat)
41 |
42 | ## 許可證
43 |
44 | [AGPL-3.0](./LICENSE)
45 |
46 | ## 技術棧
47 |
48 | nextjs / tailwindcss / shadcn UI
49 |
50 | ## 注意事項
51 |
52 | - 人工智能可能會生成不當內容,請小心使用。
53 |
--------------------------------------------------------------------------------
/app/[locale]/(home)/layout.tsx:
--------------------------------------------------------------------------------
1 | import HomeProvider from '@/app/[locale]/(home)/provider';
2 | import AppSidebar from '@/app/[locale]/(home)/sidebar';
3 |
4 | export default async function AppLayout({
5 | children,
6 | }: Readonly<{
7 | children: React.ReactNode;
8 | }>) {
9 | return (
10 |
11 |
12 | {children}
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/app/[locale]/(home)/provider.tsx:
--------------------------------------------------------------------------------
1 | import { AI as AiProvider } from '@/app/[locale]/(home)/action';
2 |
3 | export default function HomeProvider({ children }: Readonly<{ children: React.ReactNode }>) {
4 | return {children};
5 | }
6 |
--------------------------------------------------------------------------------
/app/[locale]/(home)/search/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { AddButton } from '@/components/layout/add-button';
4 | import { ModelSelect } from '@/components/layout/model-select';
5 | import { SearchWindow } from '@/components/layout/search/search-window';
6 | import { SearchSelect } from '@/components/layout/search-select';
7 |
8 | export const runtime = 'edge';
9 |
10 | export const dynamic = 'force-dynamic';
11 |
12 | export default function Search() {
13 | return (
14 | <>
15 |
22 |
23 |
24 |
25 | >
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/app/[locale]/(home)/sidebar.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { Suspense } from 'react';
4 | import { useAtom } from 'jotai';
5 |
6 | import { Brand } from '@/components/layout/brand';
7 | import { HistoryList } from '@/components/layout/history-list';
8 | import { LanguageDropdown } from '@/components/layout/language-dropdown';
9 | import { SettingsDialog } from '@/components/layout/settings-dialog';
10 | import { SettingsDrawer } from '@/components/layout/settings-drawer';
11 | import { ThemeDropdown } from '@/components/layout/theme-dropdown';
12 | import store from '@/hooks/store';
13 | import { useMediaQuery } from '@/hooks/window';
14 |
15 | export default function AppSidebar() {
16 | const [conversations, setConversations] = useAtom(store.conversationsAtom);
17 |
18 | const isDesktop = useMediaQuery('(min-width: 768px)');
19 |
20 | return (
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
{isDesktop ? : }
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/app/[locale]/layout.tsx:
--------------------------------------------------------------------------------
1 | import LocaleProvider from '@/app/[locale]/provider';
2 |
3 | import '@/styles/globals.css';
4 |
5 | export default function LocaleLayout({
6 | children,
7 | }: Readonly<{
8 | children: React.ReactNode;
9 | }>) {
10 | return (
11 |
12 | {children}
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/app/[locale]/provider.tsx:
--------------------------------------------------------------------------------
1 | import { NextIntlClientProvider, useMessages } from 'next-intl';
2 |
3 | export default function LocaleProvider({ children }: Readonly<{ children: React.ReactNode }>) {
4 | const messages = useMessages();
5 |
6 | return {children};
7 | }
8 |
--------------------------------------------------------------------------------
/app/api/app/latest/route.ts:
--------------------------------------------------------------------------------
1 | import { getLatestVersion } from '@/utils/app/version';
2 |
3 | export async function GET(request: Request) {
4 | const latestVersion = await getLatestVersion({ owner: 'okisdev', repo: 'ChatChat' });
5 |
6 | return Response.json(
7 | {
8 | short: { version: latestVersion.tag_name, version_name: latestVersion.name },
9 | details: latestVersion,
10 | },
11 | { status: 200 }
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/app/api/chat/messages/amazon/route.ts:
--------------------------------------------------------------------------------
1 | // import { BedrockRuntimeClient, InvokeModelWithResponseStreamCommand } from '@aws-sdk/client-bedrock-runtime';
2 | // import { AWSBedrockAnthropicStream, StreamingTextResponse } from 'ai';
3 | // import { experimental_buildAnthropicPrompt } from 'ai/prompts';
4 |
5 | // import { ApiConfig } from '@/types/app';
6 |
7 | // export const runtime = 'edge';
8 |
9 | // export const dynamic = 'force-dynamic';
10 |
11 | // const amazon = new BedrockRuntimeClient({
12 | // region: process.env.AWS_REGION ?? 'us-east-1',
13 | // credentials: {
14 | // accessKeyId: process.env.AWS_ACCESS_KEY ?? '',
15 | // secretAccessKey: process.env.AWS_SECRET_KEY ?? '',
16 | // },
17 | // });
18 |
19 | // export async function POST(req: Request) {
20 | // const {
21 | // messages,
22 | // config,
23 | // stream,
24 | // }: {
25 | // messages: any[];
26 | // config: ApiConfig;
27 | // stream: boolean;
28 | // } = await req.json();
29 |
30 | // const response = await amazon.send(
31 | // new InvokeModelWithResponseStreamCommand({
32 | // modelId: config.model.model_id,
33 | // contentType: 'application/json',
34 | // accept: 'application/json',
35 | // body: JSON.stringify({
36 | // prompt: experimental_buildAnthropicPrompt(messages),
37 | // max_tokens_to_sample: 300,
38 | // stream: stream,
39 | // }),
40 | // })
41 | // );
42 |
43 | // const output = AWSBedrockAnthropicStream(response);
44 |
45 | // return new StreamingTextResponse(output);
46 | // }
47 |
48 |
49 | export async function GET(req: Request) {
50 | return Response.json({ error: 'Method Not Allowed' }, { status: 405 });
51 | }
52 |
--------------------------------------------------------------------------------
/app/api/chat/messages/anthropic/route.ts:
--------------------------------------------------------------------------------
1 | import Anthropic from '@anthropic-ai/sdk';
2 | import { AnthropicStream, StreamingTextResponse } from 'ai';
3 |
4 | import { ApiConfig } from '@/types/app';
5 |
6 | export const runtime = 'edge';
7 |
8 | export const dynamic = 'force-dynamic';
9 |
10 | export async function POST(req: Request) {
11 | const {
12 | messages,
13 | config,
14 | stream,
15 | }: {
16 | messages: any[];
17 | config: ApiConfig;
18 | stream: boolean;
19 | } = await req.json();
20 |
21 | const anthropic = new Anthropic({
22 | apiKey: config.provider?.apiKey ?? process.env.ANTHROPIC_API_KEY ?? '',
23 | });
24 |
25 | const response = await anthropic.messages.create({
26 | messages,
27 | model: config.model.model_id,
28 | stream: true,
29 | max_tokens: 4096,
30 | });
31 |
32 | const output = AnthropicStream(response);
33 |
34 | return new StreamingTextResponse(output);
35 | }
36 |
--------------------------------------------------------------------------------
/app/api/chat/messages/azure/route.ts:
--------------------------------------------------------------------------------
1 | // import { SimpleModel } from '@/types/model';
2 | // import { OpenAIClient, AzureKeyCredential } from '@azure/openai';
3 | // import { OpenAIStream, StreamingTextResponse } from 'ai';
4 |
5 | // const client = new OpenAIClient(process.env.AZURE_OPENAI_ENDPOINT ?? '', new AzureKeyCredential(process.env.AZURE_OPENAI_API_KEY ?? ''));
6 |
7 | export const runtime = 'edge';
8 |
9 | export const dynamic = 'force-dynamic';
10 |
11 | // export async function POST(req: Request) {
12 | // const {
13 | // messages,
14 | // model,
15 | // }: { stream
16 | // }: {
17 | // messages: any[];
18 | // model: SimpleModel;
19 | // stream: boolean;
20 | // } = await req.json();
21 |
22 | // const response = await client.streamChatCompletions(process.env.AZURE_OPENAI_DEPLOY_INSTANCE_NAME || '', messages);
23 |
24 | // const stream = OpenAIStream(response);
25 |
26 | // return new StreamingTextResponse(stream);
27 | // }
28 |
29 | export async function GET(req: Request) {
30 | return Response.json({ error: 'Method Not Allowed' }, { status: 405 });
31 | }
32 |
--------------------------------------------------------------------------------
/app/api/chat/messages/cohere/route.ts:
--------------------------------------------------------------------------------
1 | import { Message } from 'ai';
2 | import { CohereClient } from 'cohere-ai';
3 |
4 | import { ApiConfig } from '@/types/app';
5 | import { toCohereRole } from '@/utils/provider/cohere';
6 |
7 | export const runtime = 'edge';
8 |
9 | export const dynamic = 'force-dynamic';
10 |
11 | export async function POST(req: Request) {
12 | const {
13 | messages,
14 | config,
15 | stream,
16 | }: {
17 | messages: any[];
18 | config: ApiConfig;
19 | stream: boolean;
20 | } = await req.json();
21 |
22 | const chatHistory = messages.map((message: Message) => ({
23 | message: message.content,
24 | role: toCohereRole(message.role),
25 | }));
26 |
27 | const lastMessage = chatHistory.pop()!;
28 |
29 | const cohere = new CohereClient({
30 | token: config.provider?.apiKey ?? process.env.COHERE_API_KEY ?? '',
31 | });
32 |
33 | const response = await cohere.chatStream({
34 | message: lastMessage.message,
35 | chatHistory,
36 | model: config.model.model_id,
37 | });
38 |
39 | const output = new ReadableStream({
40 | async start(controller) {
41 | for await (const event of response) {
42 | if (event.eventType === 'text-generation') {
43 | controller.enqueue(event.text);
44 | }
45 | }
46 | controller.close();
47 | },
48 | });
49 |
50 | return new Response(output);
51 | }
52 |
--------------------------------------------------------------------------------
/app/api/chat/messages/fireworks/route.ts:
--------------------------------------------------------------------------------
1 | import { OpenAIStream, StreamingTextResponse } from 'ai';
2 | import OpenAI from 'openai';
3 |
4 | import { ApiConfig } from '@/types/app';
5 |
6 | export const runtime = 'edge';
7 |
8 | export const dynamic = 'force-dynamic';
9 |
10 | export async function POST(req: Request) {
11 | const {
12 | messages,
13 | config,
14 | stream,
15 | }: {
16 | messages: any[];
17 | config: ApiConfig;
18 | stream: boolean;
19 | } = await req.json();
20 |
21 | const fireworks = new OpenAI({
22 | apiKey: config.provider.apiKey ?? process.env.FIREWORKS_API_KEY ?? '',
23 | baseURL: 'https://api.fireworks.ai/inference/v1',
24 | });
25 |
26 | const response = await fireworks.chat.completions.create({
27 | model: config.model.model_id,
28 | stream: true,
29 | max_tokens: 1000,
30 | messages,
31 | });
32 |
33 | const output = OpenAIStream(response);
34 |
35 | return new StreamingTextResponse(output);
36 | }
37 |
--------------------------------------------------------------------------------
/app/api/chat/messages/google/route.ts:
--------------------------------------------------------------------------------
1 | import { GoogleGenerativeAI } from '@google/generative-ai';
2 | import { GoogleGenerativeAIStream, StreamingTextResponse } from 'ai';
3 |
4 | import { ApiConfig } from '@/types/app';
5 | import { toGoogleMessage } from '@/utils/provider/google';
6 |
7 | export const runtime = 'edge';
8 |
9 | export const dynamic = 'force-dynamic';
10 |
11 | export async function POST(req: Request) {
12 | const {
13 | messages,
14 | config,
15 | stream,
16 | }: {
17 | messages: any[];
18 | config: ApiConfig;
19 | stream: boolean;
20 | } = await req.json();
21 |
22 | const genAI = new GoogleGenerativeAI(config.provider?.apiKey ?? process.env.GOOGLE_API_KEY ?? '');
23 |
24 | const response = await genAI.getGenerativeModel({ model: config.model.model_id }).generateContentStream(toGoogleMessage(messages));
25 |
26 | const output = GoogleGenerativeAIStream(response);
27 |
28 | return new StreamingTextResponse(output);
29 | }
30 |
--------------------------------------------------------------------------------
/app/api/chat/messages/groq/route.ts:
--------------------------------------------------------------------------------
1 | import { OpenAIStream, StreamingTextResponse } from 'ai';
2 | import Groq from 'groq-sdk';
3 |
4 | import { ApiConfig } from '@/types/app';
5 |
6 | export const runtime = 'edge';
7 |
8 | export const dynamic = 'force-dynamic';
9 |
10 | export async function POST(req: Request) {
11 | const {
12 | messages,
13 | config,
14 | stream,
15 | }: {
16 | messages: any[];
17 | config: ApiConfig;
18 | stream: boolean;
19 | } = await req.json();
20 |
21 | const groq = new Groq({
22 | apiKey: config.provider?.apiKey ?? process.env.GROQ_API_KEY ?? '',
23 | });
24 |
25 | const response = await groq.chat.completions.create({
26 | model: config.model.model_id,
27 | stream: true,
28 | messages,
29 | });
30 |
31 | const output = OpenAIStream(response);
32 |
33 | return new StreamingTextResponse(output);
34 | }
35 |
--------------------------------------------------------------------------------
/app/api/chat/messages/huggingface/route.ts:
--------------------------------------------------------------------------------
1 | import { HfInference } from '@huggingface/inference';
2 | import { HuggingFaceStream, StreamingTextResponse } from 'ai';
3 | import { experimental_buildOpenAssistantPrompt } from 'ai/prompts';
4 |
5 | import { ApiConfig } from '@/types/app';
6 |
7 | export const runtime = 'edge';
8 |
9 | export const dynamic = 'force-dynamic';
10 |
11 | export async function POST(req: Request) {
12 | const {
13 | messages,
14 | config,
15 | stream,
16 | }: {
17 | messages: any[];
18 | config: ApiConfig;
19 | stream: boolean;
20 | } = await req.json();
21 |
22 | const huggingface = new HfInference(config.provider?.apiKey ?? process.env.HUGGINGFACE_API_KEY ?? '');
23 |
24 | const response = huggingface.textGenerationStream({
25 | model: config.model.model_id,
26 | inputs: experimental_buildOpenAssistantPrompt(messages),
27 | parameters: {
28 | max_new_tokens: 200,
29 | typical_p: 0.2,
30 | repetition_penalty: 1,
31 | truncate: 1000,
32 | return_full_text: false,
33 | },
34 | });
35 |
36 | const output = HuggingFaceStream(response);
37 |
38 | return new StreamingTextResponse(output);
39 | }
40 |
--------------------------------------------------------------------------------
/app/api/chat/messages/mistral/route.ts:
--------------------------------------------------------------------------------
1 | import MistralClient from '@mistralai/mistralai';
2 | import { MistralStream, StreamingTextResponse } from 'ai';
3 |
4 | import { ApiConfig } from '@/types/app';
5 |
6 | export const runtime = 'edge';
7 |
8 | export const dynamic = 'force-dynamic';
9 |
10 | export async function POST(req: Request) {
11 | const {
12 | messages,
13 | config,
14 | stream,
15 | }: {
16 | messages: any[];
17 | config: ApiConfig;
18 | stream: boolean;
19 | } = await req.json();
20 |
21 | const mistral = new MistralClient(config.provider?.apiKey ?? process.env.MISTRAL_API_KEY ?? '');
22 |
23 | const response = mistral.chatStream({
24 | model: config.model.model_id,
25 | maxTokens: 1000,
26 | messages,
27 | });
28 |
29 | const output = MistralStream(response);
30 |
31 | return new StreamingTextResponse(output);
32 | }
33 |
--------------------------------------------------------------------------------
/app/api/chat/messages/openai/route.ts:
--------------------------------------------------------------------------------
1 | import { OpenAIStream, StreamingTextResponse } from 'ai';
2 | import OpenAI from 'openai';
3 |
4 | import { ApiConfig } from '@/types/app';
5 |
6 | export const runtime = 'edge';
7 |
8 | export const dynamic = 'force-dynamic';
9 |
10 | export async function POST(req: Request) {
11 | const {
12 | messages,
13 | config,
14 | stream,
15 | }: {
16 | messages: any[];
17 | config: ApiConfig;
18 | stream: boolean;
19 | } = await req.json();
20 |
21 | const openai = new OpenAI({
22 | apiKey: config.provider?.apiKey ?? process.env.OPENAI_API_KEY ?? '',
23 | baseURL: config.provider?.endpoint ?? process.env.OPENAI_API_ENDPOINT ?? 'https://api.openai.com/v1',
24 | });
25 |
26 | const response = await openai.chat.completions.create({
27 | model: config.model.model_id,
28 | stream: true,
29 | messages,
30 | });
31 |
32 | const output = OpenAIStream(response);
33 |
34 | return new StreamingTextResponse(output);
35 | }
36 |
--------------------------------------------------------------------------------
/app/api/chat/messages/perplexity/route.ts:
--------------------------------------------------------------------------------
1 | import { OpenAIStream, StreamingTextResponse } from 'ai';
2 | import OpenAI from 'openai';
3 |
4 | import { ApiConfig } from '@/types/app';
5 |
6 | export const runtime = 'edge';
7 |
8 | export const dynamic = 'force-dynamic';
9 |
10 | export async function POST(req: Request) {
11 | const {
12 | messages,
13 | config,
14 | stream,
15 | }: {
16 | messages: any[];
17 | config: ApiConfig;
18 | stream: boolean;
19 | } = await req.json();
20 |
21 | const perplexity = new OpenAI({
22 | apiKey: config.provider?.apiKey ?? process.env.PERPLEXITY_API_KEY ?? '',
23 | baseURL: config.provider?.endpoint ?? process.env.PERPLEXITY_ENDPOINT ?? 'https://api.perplexity.ai/',
24 | });
25 |
26 | const response = await perplexity.chat.completions.create({
27 | model: config.model.model_id,
28 | stream: true,
29 | max_tokens: 4096,
30 | messages,
31 | });
32 |
33 | const output = OpenAIStream(response);
34 |
35 | return new StreamingTextResponse(output);
36 | }
37 |
--------------------------------------------------------------------------------
/app/api/search/google/route.ts:
--------------------------------------------------------------------------------
1 | import { createOpenAI } from '@ai-sdk/openai';
2 | import { CoreMessage, StreamingTextResponse, streamText as aiStreamText, ToolCallPart, ToolResultPart } from 'ai';
3 | import { createStreamableUI, createStreamableValue } from 'ai/rsc';
4 |
5 | import { searcherPrompt } from '@/lib/prompt';
6 | import { searcherSchema } from '@/lib/search/searcher';
7 | import { ApiConfig } from '@/types/app';
8 | import { withGoogleSearch } from '@/utils/search/engines/google';
9 |
10 | export const runtime = 'edge';
11 |
12 | export const dynamic = 'force-dynamic';
13 |
14 | export async function POST(req: Request) {
15 | const {
16 | messages,
17 | config,
18 | stream,
19 | }: {
20 | messages: CoreMessage[];
21 | config: ApiConfig;
22 | stream: boolean;
23 | } = await req.json();
24 |
25 | let fullResponse = '';
26 |
27 | const streamText = createStreamableValue();
28 |
29 | const uiStream = createStreamableUI();
30 |
31 | const openai = createOpenAI({
32 | apiKey: config.provider?.apiKey ?? process.env.OPENAI_API_KEY ?? '',
33 | baseUrl: config.provider?.endpoint ?? process.env.OPENAI_API_ENDPOINT ?? 'https://api.openai.com/v1',
34 | });
35 |
36 | const result = await aiStreamText({
37 | model: openai.chat('gpt-4'),
38 | system: searcherPrompt,
39 | messages,
40 | tools: {
41 | search: {
42 | description: 'Search the web for information.',
43 | parameters: searcherSchema,
44 | execute: async ({ query }: { query: string }) => {
45 | const searchResult = await withGoogleSearch(query);
46 |
47 | return searchResult;
48 | },
49 | },
50 | },
51 | });
52 |
53 | const toolCalls: ToolCallPart[] = [];
54 | const toolResponses: ToolResultPart[] = [];
55 | for await (const delta of result.fullStream) {
56 | switch (delta.type) {
57 | case 'text-delta':
58 | if (delta.textDelta) {
59 | if (fullResponse.length === 0 && delta.textDelta.length > 0) {
60 | }
61 |
62 | fullResponse += delta.textDelta;
63 | streamText.update(fullResponse);
64 | }
65 | break;
66 | case 'tool-call':
67 | toolCalls.push(delta);
68 | break;
69 | case 'tool-result':
70 | toolResponses.push(delta);
71 | break;
72 | case 'error':
73 | fullResponse += `\nError occurred while executing the tool`;
74 | break;
75 | }
76 | }
77 | messages.push({
78 | role: 'assistant',
79 | content: [{ type: 'text', text: fullResponse }, ...toolCalls],
80 | });
81 |
82 | if (toolResponses.length > 0) {
83 | messages.push({ role: 'tool', content: toolResponses });
84 | }
85 |
86 | return new StreamingTextResponse(result.toAIStream());
87 | }
88 |
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/okisdev/ChatChat/12674287a96b32ee4f04194b1e44944cf733a084/app/favicon.ico
--------------------------------------------------------------------------------
/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata, Viewport } from 'next';
2 | import { Onest } from 'next/font/google';
3 |
4 | import RootProvider from '@/app/provider';
5 |
6 | import '@/styles/globals.css';
7 | import 'tippy.js/dist/tippy.css';
8 |
9 | const onest = Onest({ subsets: ['latin'] });
10 |
11 | export const metadata: Metadata = {
12 | title: 'Chat Chat',
13 | description: 'Chat Chat - Unlock next-level conversations with AI',
14 |
15 | manifest: '/manifest.json',
16 |
17 | appleWebApp: {
18 | capable: true,
19 | statusBarStyle: 'default',
20 | title: 'Chat Chat',
21 | },
22 | };
23 |
24 | export const viewport: Viewport = {
25 | width: 'device-width',
26 | initialScale: 1,
27 | minimumScale: 1,
28 | userScalable: false,
29 | };
30 |
31 | export default function RootLayout({
32 | children,
33 | params: { locale },
34 | }: Readonly<{
35 | children: React.ReactNode;
36 | params: { locale: string };
37 | }>) {
38 | return (
39 |
40 | {children}
41 |
42 | );
43 | }
44 |
--------------------------------------------------------------------------------
/app/not-found.tsx:
--------------------------------------------------------------------------------
1 | export default function NotFound() {
2 | return (
3 |
4 |
... not found ...
5 |
6 | );
7 | }
8 |
--------------------------------------------------------------------------------
/app/provider.tsx:
--------------------------------------------------------------------------------
1 | import { Analytics } from '@vercel/analytics/react';
2 | import { SpeedInsights } from '@vercel/speed-insights/react';
3 | import { Provider as JotaiProvider } from 'jotai';
4 | import { Toaster } from 'sonner';
5 |
6 | import { ThemeProvider } from '@/hooks/theme';
7 |
8 | export default function RootProvider({ children }: Readonly<{ children: React.ReactNode }>) {
9 | return (
10 |
11 |
12 | {children}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/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": "styles/globals.css",
9 | "baseColor": "neutral",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/ui/utils"
16 | }
17 | }
--------------------------------------------------------------------------------
/components/layout/add-button.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { IoMdAdd } from 'react-icons/io';
4 | import { IoSearch } from 'react-icons/io5';
5 | import { RiChat1Line } from 'react-icons/ri';
6 | import Tippy from '@tippyjs/react';
7 | import { useUIState } from 'ai/rsc';
8 | import { usePathname, useRouter } from 'next/navigation';
9 | import { useTranslations } from 'next-intl';
10 |
11 | import { AI } from '@/app/[locale]/(home)/action';
12 | import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/custom/dropdown-menu';
13 |
14 | export const AddButton = () => {
15 | const router = useRouter();
16 |
17 | const t = useTranslations();
18 |
19 | const pathname = usePathname();
20 |
21 | const [messages, setMessages] = useUIState();
22 |
23 | return (
24 |
25 |
26 |
27 |
31 |
32 |
33 |
34 | {
36 | if (pathname === '/') {
37 | window.location.reload();
38 | } else {
39 | router.push('/');
40 | }
41 | }}
42 | className='flex cursor-pointer items-center justify-between'
43 | >
44 | {t('chat')}
45 |
46 |
47 | {
49 | if (pathname === '/search') {
50 | // router.refresh();
51 | // window.location.reload();
52 | setMessages([]);
53 | } else {
54 | router.push('/search');
55 | }
56 | }}
57 | className='flex cursor-pointer items-center justify-between'
58 | >
59 | {t('search')}
60 |
61 |
62 |
63 |
64 | );
65 | };
66 |
--------------------------------------------------------------------------------
/components/layout/brand.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 |
3 | import { VersionBadge } from '@/components/layout/version-badge';
4 |
5 | export const Brand = () => {
6 | return (
7 |
8 |
9 | Chat Chat
10 |
11 |
12 |
13 | );
14 | };
15 |
--------------------------------------------------------------------------------
/components/layout/chat/conversation-window.tsx:
--------------------------------------------------------------------------------
1 | import { FaUser } from 'react-icons/fa';
2 | import { LuClipboardCopy, LuLoader2 } from 'react-icons/lu';
3 | import { Message } from 'ai';
4 | import { useAtom } from 'jotai';
5 | import Image from 'next/image';
6 | import { useTranslations } from 'next-intl';
7 | import { toast } from 'sonner';
8 |
9 | import { renderMarkdownMessage } from '@/components/layout/message';
10 | import store from '@/hooks/store';
11 |
12 | export const ConversationWindow = ({
13 | messages,
14 | isLoading,
15 | }: Readonly<{
16 | messages: Message[];
17 | isLoading: boolean;
18 | }>) => {
19 | const t = useTranslations();
20 |
21 | const [currentUseModel] = useAtom(store.currentUseModelAtom);
22 |
23 | const onCopy = (context: string) => {
24 | navigator.clipboard.writeText(context);
25 |
26 | toast.success(t('copied'), {
27 | position: 'top-right',
28 | });
29 | };
30 |
31 | return (
32 |
33 | {messages.map((m) => (
34 |
35 |
36 | {m.role == 'user' ? (
37 |
38 | ) : (
39 |
40 | )}
41 | {!isLoading && (
42 |
43 |
50 |
51 | )}
52 |
53 | {renderMarkdownMessage(m.content)}
54 |
55 | ))}
56 | {isLoading &&
}
57 |
58 | );
59 | };
60 |
--------------------------------------------------------------------------------
/components/layout/language-dropdown.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { IoLanguage } from 'react-icons/io5';
4 | import { usePathname, useRouter } from 'next/navigation';
5 | import { useLocale } from 'next-intl';
6 |
7 | import { DropdownMenu, DropdownMenuContent, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuTrigger } from '@/components/ui/custom/dropdown-menu';
8 | import { LanguageList } from '@/config/i18n';
9 |
10 | export const LanguageDropdown = () => {
11 | const locale = useLocale();
12 |
13 | const router = useRouter();
14 |
15 | const pathname = usePathname();
16 |
17 | return (
18 |
19 |
20 |
21 | {LanguageList.find((l) => l.id === locale)?.flag}
22 |
23 |
24 | {
27 | router.push('/' + value + pathname);
28 | }}
29 | >
30 | {LanguageList.map((lang) => {
31 | return (
32 |
33 | {lang.flag + ' ' + lang.name}
34 |
35 | );
36 | })}
37 |
38 |
39 |
40 | );
41 | };
42 |
--------------------------------------------------------------------------------
/components/layout/lightbox.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import Lightbox from 'react-spring-lightbox';
4 | import { ImagesList } from 'react-spring-lightbox/dist/types/ImagesList';
5 |
6 | export const LightBox = ({
7 | images,
8 | open,
9 | setOpen,
10 | currentIndex,
11 | setCurrentIndex,
12 | }: {
13 | images: ImagesList;
14 | open: boolean;
15 | setOpen: (open: boolean) => void;
16 | currentIndex: number;
17 | setCurrentIndex: (index: number) => void;
18 | }) => {
19 | const gotoPrevious = () => currentIndex > 0 && setCurrentIndex(currentIndex - 1);
20 |
21 | const gotoNext = () => currentIndex + 1 < images.length && setCurrentIndex(currentIndex + 1);
22 |
23 | return setOpen(false)} />;
24 | };
25 |
--------------------------------------------------------------------------------
/components/layout/search-select.tsx:
--------------------------------------------------------------------------------
1 | import { IoChevronDown } from 'react-icons/io5';
2 | import { useAtom } from 'jotai';
3 | import Image from 'next/image';
4 | import Link from 'next/link';
5 | import { useTranslations } from 'next-intl';
6 |
7 | import {
8 | DropdownMenu,
9 | DropdownMenuContent,
10 | DropdownMenuItem,
11 | DropdownMenuLabel,
12 | DropdownMenuRadioGroup,
13 | DropdownMenuRadioItem,
14 | DropdownMenuSeparator,
15 | DropdownMenuTrigger,
16 | } from '@/components/ui/custom/dropdown-menu';
17 | import { SearchEngine } from '@/config/search';
18 | import store from '@/hooks/store';
19 | import { SearchEngineSetting } from '@/types/search';
20 |
21 | export const SearchSelect = () => {
22 | const t = useTranslations();
23 |
24 | const [currentSearchEngine, setCurrentSearchEngine] = useAtom(store.currentSearchEngineAtom);
25 | const [currentSearchEngineSettings] = useAtom(store.currentSearchEngineSettingsAtom);
26 |
27 | const hasKeyStored = process.env['NEXT_PUBLIC_ACCESS_TAVILY_SEARCH'] == 'true';
28 |
29 | const isTavilyConfigured = (currentSearchEngineSettings && currentSearchEngineSettings.Tavily !== null) || hasKeyStored;
30 |
31 | return (
32 |
33 |
34 |
35 | {currentSearchEngine}
36 |
37 |
38 |
39 | {
42 | setCurrentSearchEngine(value === 'Tavily' ? SearchEngine.Tavily : SearchEngine.Google);
43 | }}
44 | >
45 | {!isTavilyConfigured ? (
46 | <>
47 |
48 | {t('search_engine_not_configured')}
49 |
50 |
51 |
52 | {t('go_to_settings')}
53 |
54 |
55 |
56 | >
57 | ) : (
58 | hasKeyStored && (
59 | <>
60 |
61 | {t('search_engine_configured_globally')}
62 |
63 |
64 | >
65 | )
66 | )}
67 |
68 |
72 |
73 |
74 |
75 |
76 | );
77 | };
78 |
--------------------------------------------------------------------------------
/components/layout/search/block/additions.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { LuCheckCheck } from 'react-icons/lu';
4 | import { useTranslations } from 'next-intl';
5 |
6 | import { BlockTitle } from '@/components/layout/search/block/block-title';
7 |
8 | export const Additions = ({
9 | content,
10 | input,
11 | }: Readonly<{
12 | content: {
13 | [key: string]: boolean;
14 | };
15 | input: string;
16 | }>) => {
17 | const t = useTranslations();
18 |
19 | let options: string[] = Object.entries(content).reduce((acc: string[], [option, checked]) => {
20 | if (checked) acc.push(option);
21 | return acc;
22 | }, []);
23 |
24 | const additions = options.join(', ');
25 |
26 | return (
27 |
28 |
29 | {
{t('focus_on') + ': ' + input + t('and') + ' ' + additions}
}
30 |
31 | );
32 | };
33 |
--------------------------------------------------------------------------------
/components/layout/search/block/answer.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { AiOutlineSolution } from 'react-icons/ai';
4 | import { StreamableValue, useStreamableValue } from 'ai/rsc';
5 | import { useAtom } from 'jotai';
6 | import { useTranslations } from 'next-intl';
7 |
8 | import { renderMarkdownMessage } from '@/components/layout/message';
9 | import { BlockTitle } from '@/components/layout/search/block/block-title';
10 | import store from '@/hooks/store';
11 |
12 | export const Answer = ({
13 | content,
14 | }: Readonly<{
15 | content: string | StreamableValue;
16 | }>) => {
17 | const t = useTranslations();
18 |
19 | const [data, error, pending] = useStreamableValue(content);
20 |
21 | const [sameCitationId, setSameCitationId] = useAtom(store.sameCitationAtom);
22 |
23 | if (error) return {t('error')}
;
24 |
25 | return (
26 |
27 |
28 |
{renderMarkdownMessage(data ?? '', sameCitationId, (value) => setSameCitationId(value))}
29 |
30 | );
31 | };
32 |
--------------------------------------------------------------------------------
/components/layout/search/block/ask-follow-up-question.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { useRef, useState } from 'react';
4 | import { LiaReadme } from 'react-icons/lia';
5 | import { useActions, useUIState } from 'ai/rsc';
6 | import { useAtom } from 'jotai';
7 | import { useTranslations } from 'next-intl';
8 |
9 | import type { AI } from '@/app/[locale]/(home)/action';
10 | import { BlockTitle } from '@/components/layout/search/block/block-title';
11 | import { Question } from '@/components/layout/search/block/question';
12 | import { InputBox } from '@/components/layout/search/input-box';
13 | import store from '@/hooks/store';
14 |
15 | export const AskFollowUpQuestion = () => {
16 | const t = useTranslations();
17 |
18 | const { search } = useActions();
19 |
20 | const [isProSearch] = useAtom(store.isProSearchAtom);
21 | const [currentUseModel] = useAtom(store.currentUseModelAtom);
22 | const [currentProviderSettings] = useAtom(store.currentProviderSettingsAtom);
23 | const [currentSearchEngineSettings] = useAtom(store.currentSearchEngineSettingsAtom);
24 |
25 | const [input, setInput] = useState('');
26 | const inputRef = useRef(null);
27 |
28 | const [messages, setMessages] = useUIState();
29 |
30 | const handleSubmit = async (e: React.FormEvent) => {
31 | e.preventDefault();
32 |
33 | const formData = new FormData(e.currentTarget as HTMLFormElement);
34 |
35 | const userMessage = {
36 | id: Date.now(),
37 | isGenerating: false,
38 | component: ,
39 | };
40 |
41 | const searchResponse = await search(currentUseModel, currentProviderSettings, currentSearchEngineSettings, formData, isProSearch);
42 |
43 | setMessages((currentMessages) => [...currentMessages, userMessage, searchResponse]);
44 |
45 | setInput('');
46 | };
47 |
48 | return (
49 |
50 |
51 |
52 |
53 | );
54 | };
55 |
--------------------------------------------------------------------------------
/components/layout/search/block/block-title.tsx:
--------------------------------------------------------------------------------
1 | import { IconType } from 'react-icons/lib';
2 |
3 | interface BlockTitleProps {
4 | title: string;
5 | icon: IconType;
6 | }
7 |
8 | export const BlockTitle = (props: BlockTitleProps) => {
9 | return (
10 |
11 |
12 |
{props.title}
13 |
14 | );
15 | };
16 |
--------------------------------------------------------------------------------
/components/layout/search/block/error.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { BiError } from 'react-icons/bi';
4 | import { useTranslations } from 'next-intl';
5 |
6 | import { renderMarkdownMessage } from '@/components/layout/message';
7 | import { BlockTitle } from '@/components/layout/search/block/block-title';
8 |
9 | type ErrorType = 'not_supported' | 'provider_not_configured' | 'search_engine_not_configured' | 'error';
10 |
11 | export const BlockError = ({
12 | content,
13 | type = 'error',
14 | }: Readonly<{
15 | content: any;
16 | type: ErrorType;
17 | }>) => {
18 | const t = useTranslations();
19 |
20 | const RenderErrorMessage = () => {
21 | switch (type) {
22 | case 'not_supported':
23 | return {t('provider_not_supported') ?? content}
;
24 | case 'search_engine_not_configured':
25 | return {t('search_engine_not_configured') ?? content}
;
26 | case 'provider_not_configured':
27 | return {t('provider_not_configured') ?? content}
;
28 | case 'error':
29 | default:
30 | return {renderMarkdownMessage(content ?? '')}
;
31 | }
32 | };
33 |
34 | return (
35 |
36 |
37 |
{RenderErrorMessage()}
38 |
39 | );
40 | };
41 |
--------------------------------------------------------------------------------
/components/layout/search/block/focus-point.tsx:
--------------------------------------------------------------------------------
1 | import { LuFocus } from 'react-icons/lu';
2 | import { useTranslations } from 'next-intl';
3 |
4 | import { BlockTitle } from '@/components/layout/search/block/block-title';
5 |
6 | export const FocusPoint = ({ query }: { query: string }) => {
7 | const t = useTranslations();
8 |
9 | return (
10 |
14 | );
15 | };
16 |
--------------------------------------------------------------------------------
/components/layout/search/block/question.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { FaQuestion } from 'react-icons/fa6';
4 | import { useTranslations } from 'next-intl';
5 |
6 | import { RenderSimpleMessage } from '@/components/layout/message';
7 | import { BlockTitle } from '@/components/layout/search/block/block-title';
8 |
9 | export const Question = ({ content }: Readonly<{ content: string }>) => {
10 | const t = useTranslations();
11 |
12 | return (
13 |
14 |
15 |
16 |
17 | );
18 | };
19 |
--------------------------------------------------------------------------------
/components/layout/search/block/related.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import React from 'react';
4 | import { LuPlus } from 'react-icons/lu';
5 | import { PiPlugsConnectedDuotone } from 'react-icons/pi';
6 | import { useActions, useStreamableValue, useUIState } from 'ai/rsc';
7 | import { useAtom } from 'jotai';
8 | import { useTranslations } from 'next-intl';
9 |
10 | import { AI } from '@/app/[locale]/(home)/action';
11 | import { BlockTitle } from '@/components/layout/search/block/block-title';
12 | import { BlockError } from '@/components/layout/search/block/error';
13 | import { Question } from '@/components/layout/search/block/question';
14 | import store from '@/hooks/store';
15 | import { TIllustrator } from '@/types/search';
16 |
17 | export const Related = ({ relatedQueries }: { relatedQueries: TIllustrator }) => {
18 | const t = useTranslations();
19 |
20 | const { search } = useActions();
21 |
22 | const [isProSearch] = useAtom(store.isProSearchAtom);
23 | const [currentUseModel] = useAtom(store.currentUseModelAtom);
24 | const [currentProviderSettings] = useAtom(store.currentProviderSettingsAtom);
25 | const [currentSearchEngineSettings] = useAtom(store.currentSearchEngineSettingsAtom);
26 |
27 | const [messages, setMessages] = useUIState();
28 |
29 | const [data, error, pending] = useStreamableValue(relatedQueries);
30 |
31 | const handleSubmit = async (e: React.FormEvent) => {
32 | e.preventDefault();
33 |
34 | const formData = new FormData(e.currentTarget as HTMLFormElement);
35 |
36 | const submitter = (e.nativeEvent as SubmitEvent).submitter as HTMLTextAreaElement;
37 |
38 | let query = '';
39 |
40 | if (submitter) {
41 | formData.append(submitter.name, submitter.value);
42 | query = submitter.value;
43 | }
44 |
45 | const userMessage = {
46 | id: Date.now(),
47 | isGenerating: false,
48 | component: ,
49 | };
50 |
51 | const searchResponse = await search(currentUseModel, currentProviderSettings, currentSearchEngineSettings, formData, isProSearch);
52 |
53 | setMessages((currentMessages) => [...currentMessages, userMessage, searchResponse]);
54 | };
55 |
56 | if (error) {
57 | return ;
58 | }
59 |
60 | return (
61 |
79 | );
80 | };
81 |
--------------------------------------------------------------------------------
/components/layout/search/block/searching.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { AiOutlineSolution } from 'react-icons/ai';
4 | import { useTranslations } from 'next-intl';
5 |
6 | import { BlockTitle } from '@/components/layout/search/block/block-title';
7 |
8 | export const Searching = () => {
9 | const t = useTranslations();
10 |
11 | return (
12 |
13 |
14 |
{t('searching_slogan')}
15 |
16 | );
17 | };
18 |
--------------------------------------------------------------------------------
/components/layout/search/block/try-ask.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { memo } from 'react';
4 | import { PiStarFourBold } from 'react-icons/pi';
5 | import { useLocale, useTranslations } from 'next-intl';
6 |
7 | import { BlockTitle } from '@/components/layout/search/block/block-title';
8 | import { questions } from '@/config/search/question';
9 |
10 | export const TryAsk = memo(function TryAsk({
11 | setInput,
12 | }: Readonly<{
13 | setInput: (value: string) => void;
14 | }>) {
15 | const t = useTranslations();
16 |
17 | const locale = useLocale();
18 |
19 | const localeQuestions = questions[locale] ?? questions.en;
20 | const randomQuestions = localeQuestions.sort(() => Math.random() - 0.5).slice(0, 5);
21 |
22 | return (
23 |
24 |
25 |
26 | {randomQuestions.map((question) => (
27 |
34 | ))}
35 |
36 |
37 | );
38 | });
39 |
--------------------------------------------------------------------------------
/components/layout/search/input-box.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { FaArrowUp } from 'react-icons/fa';
4 | import Tippy from '@tippyjs/react';
5 | import { useAtom } from 'jotai';
6 | import { useRouter } from 'next/navigation';
7 | import { useTranslations } from 'next-intl';
8 |
9 | import { Switch } from '@/components/ui/custom/switch';
10 | import { Textarea } from '@/components/ui/custom/textarea';
11 | import store from '@/hooks/store';
12 |
13 | export const InputBox = ({
14 | input,
15 | inputRef,
16 | setInput,
17 | handleSubmit,
18 | }: Readonly<{
19 | input: string;
20 | inputRef: React.RefObject;
21 | setInput: (value: string) => void;
22 | handleSubmit: (e: React.FormEvent) => void;
23 | }>) => {
24 | const router = useRouter();
25 |
26 | const t = useTranslations();
27 |
28 | const [preferences] = useAtom(store.preferencesAtom);
29 | const [conversationSettings] = useAtom(store.conversationSettingsAtom);
30 |
31 | const [currentUseModel] = useAtom(store.currentUseModelAtom);
32 |
33 | const [isProSearch, setIsProSearch] = useAtom(store.isProSearchAtom);
34 |
35 | const handleKeyDown = (e: React.KeyboardEvent) => {
36 | if (preferences.enterSend) {
37 | if (e.key === 'Enter') {
38 | e.preventDefault();
39 | handleSubmit(e as React.FormEvent);
40 | }
41 | } else if (e.key === 'Enter' && e.shiftKey) {
42 | e.preventDefault();
43 | handleSubmit(e as React.FormEvent);
44 | }
45 | };
46 |
47 | return (
48 |
79 | );
80 | };
81 |
--------------------------------------------------------------------------------
/components/layout/search/search-window.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect, useRef, useState } from 'react';
2 | import { useActions, useUIState } from 'ai/rsc';
3 | import { useAtom } from 'jotai';
4 | import { useTranslations } from 'next-intl';
5 |
6 | import type { AI } from '@/app/[locale]/(home)/action';
7 | import { Question } from '@/components/layout/search/block/question';
8 | import { TryAsk } from '@/components/layout/search/block/try-ask';
9 | import { InputBox } from '@/components/layout/search/input-box';
10 | import store from '@/hooks/store';
11 |
12 | export const SearchWindow = () => {
13 | const t = useTranslations();
14 |
15 | const { search } = useActions();
16 |
17 | const [isProSearch] = useAtom(store.isProSearchAtom);
18 | const [currentUseModel] = useAtom(store.currentUseModelAtom);
19 | const [currentProviderSettings] = useAtom(store.currentProviderSettingsAtom);
20 | const [currentSearchEngineSettings] = useAtom(store.currentSearchEngineSettingsAtom);
21 |
22 | const [input, setInput] = useState('');
23 | const inputRef = useRef(null);
24 |
25 | const [messages, setMessages] = useUIState();
26 |
27 | const [isSend, setIsSend] = useState(false);
28 |
29 | useEffect(() => {
30 | if (isSend) {
31 | inputRef.current?.focus();
32 | setIsSend(false);
33 | }
34 | }, [isSend]);
35 |
36 | const handleSubmit = async (e: React.FormEvent) => {
37 | e.preventDefault();
38 |
39 | if (isSend) {
40 | handleClear();
41 | setIsSend(false);
42 | }
43 |
44 | setMessages((currentMessages) => [
45 | ...currentMessages,
46 | {
47 | id: Date.now(),
48 | isGenerating: false,
49 | component: ,
50 | },
51 | ]);
52 |
53 | const formData = new FormData(e.currentTarget);
54 |
55 | const searchResponse = await search(currentUseModel, currentProviderSettings, currentSearchEngineSettings, formData, isProSearch);
56 |
57 | setMessages((currentMessages) => [...currentMessages, searchResponse]);
58 |
59 | setInput('');
60 | };
61 |
62 | const handleClear = () => {
63 | setIsSend(true);
64 | setMessages([]);
65 | };
66 |
67 | useEffect(() => {
68 | inputRef.current?.focus();
69 | }, []);
70 |
71 | const setInputStable = useCallback((value: string) => {
72 | setInput(value);
73 | }, []);
74 |
75 | if (messages.length > 0) {
76 | return (
77 |
78 | {messages.map((message: { id: number; component: React.ReactNode }) => (
79 |
80 | {message.component}
81 |
82 | ))}
83 |
84 | );
85 | }
86 |
87 | return (
88 |
89 |
{t('search_slogan')}
90 |
91 |
92 |
93 | );
94 | };
95 |
--------------------------------------------------------------------------------
/components/layout/settings/provider/amazon.tsx:
--------------------------------------------------------------------------------
1 | import { Input } from '@/components/ui/custom/input';
2 | import { ProviderSetting } from '@/types/settings';
3 |
4 | export const AmazonProvider = ({ amazon, setAmazon }: { amazon: ProviderSetting['Amazon'] | null; setAmazon: (value: ProviderSetting['Amazon'] | null) => void }) => {
5 | return (
6 |
7 |
8 |
Amazon API Key
9 |
{
14 | setAmazon({
15 | apiKey: e.target.value,
16 | });
17 | }}
18 | />
19 |
20 |
21 |
Amazon Secret Key
22 |
{
27 | setAmazon({
28 | secretKey: e.target.value,
29 | });
30 | }}
31 | />
32 |
33 |
34 |
Amazon Region
35 |
{
40 | setAmazon({
41 | region: e.target.value,
42 | });
43 | }}
44 | />
45 |
46 |
47 | );
48 | };
49 |
--------------------------------------------------------------------------------
/components/layout/settings/provider/anthropic.tsx:
--------------------------------------------------------------------------------
1 | import { Input } from '@/components/ui/custom/input';
2 | import { ProviderSetting } from '@/types/settings';
3 |
4 | export const AnthropicProvider = ({ anthropic, setAnthropic }: { anthropic: ProviderSetting['Anthropic'] | null; setAnthropic: (value: ProviderSetting['Anthropic'] | null) => void }) => {
5 | return (
6 |
7 |
8 |
Anthropic API Key
9 |
{
14 | setAnthropic({
15 | apiKey: e.target.value,
16 | });
17 | }}
18 | />
19 |
20 |
21 | );
22 | };
23 |
--------------------------------------------------------------------------------
/components/layout/settings/provider/azure.tsx:
--------------------------------------------------------------------------------
1 | import { Input } from '@/components/ui/custom/input';
2 | import { ProviderSetting } from '@/types/settings';
3 |
4 | export const AzureProvider = ({ azure, setAzure }: { azure: ProviderSetting['Azure'] | null; setAzure: (value: ProviderSetting['Azure'] | null) => void }) => {
5 | return (
6 |
7 |
8 |
Azure API Key
9 |
{
14 | setAzure({
15 | apiKey: e.target.value,
16 | });
17 | }}
18 | />
19 |
20 |
21 |
Azure Endpoint
22 |
{
27 | setAzure({
28 | endpoint: e.target.value,
29 | });
30 | }}
31 | />
32 |
33 |
34 |
Azure Deployment Name
35 |
{
40 | setAzure({
41 | deploymentName: e.target.value,
42 | });
43 | }}
44 | />
45 |
46 |
47 | );
48 | };
49 |
--------------------------------------------------------------------------------
/components/layout/settings/provider/cohere.tsx:
--------------------------------------------------------------------------------
1 | import { Input } from '@/components/ui/custom/input';
2 | import { ProviderSetting } from '@/types/settings';
3 |
4 | export const CohereProvider = ({ cohere, setCohere }: { cohere: ProviderSetting['Cohere'] | null; setCohere: (value: ProviderSetting['Cohere'] | null) => void }) => {
5 | return (
6 |
7 |
8 |
Cohere API Key
9 |
{
14 | setCohere({
15 | apiKey: e.target.value,
16 | });
17 | }}
18 | />
19 |
20 |
21 | );
22 | };
23 |
--------------------------------------------------------------------------------
/components/layout/settings/provider/fireworks.tsx:
--------------------------------------------------------------------------------
1 | import { Input } from '@/components/ui/custom/input';
2 | import { ProviderSetting } from '@/types/settings';
3 |
4 | export const FireworksProvider = ({ fireworks, setFireworks }: { fireworks: ProviderSetting['Fireworks'] | null; setFireworks: (value: ProviderSetting['Fireworks'] | null) => void }) => {
5 | return (
6 |
7 |
8 |
Fireworks API Key
9 |
{
14 | setFireworks({
15 | apiKey: e.target.value,
16 | });
17 | }}
18 | />
19 |
20 |
21 | );
22 | };
23 |
--------------------------------------------------------------------------------
/components/layout/settings/provider/google.tsx:
--------------------------------------------------------------------------------
1 | import { Input } from '@/components/ui/custom/input';
2 | import { ProviderSetting } from '@/types/settings';
3 |
4 | export const GoogleProvider = ({ google, setGoogle }: { google: ProviderSetting['Google'] | null; setGoogle: (value: ProviderSetting['Google'] | null) => void }) => {
5 | return (
6 |
7 |
8 |
Google AI Studio API Key
9 |
{
14 | setGoogle({
15 | apiKey: e.target.value,
16 | });
17 | }}
18 | />
19 |
20 |
21 | );
22 | };
23 |
--------------------------------------------------------------------------------
/components/layout/settings/provider/groq.tsx:
--------------------------------------------------------------------------------
1 | import { Input } from '@/components/ui/custom/input';
2 | import { ProviderSetting } from '@/types/settings';
3 |
4 | export const GroqProvider = ({ groq, setGroq }: { groq: ProviderSetting['Groq'] | null; setGroq: (value: ProviderSetting['Groq'] | null) => void }) => {
5 | return (
6 |
7 |
8 |
Groq API Key
9 |
{
14 | setGroq({
15 | apiKey: e.target.value,
16 | });
17 | }}
18 | />
19 |
20 |
21 | );
22 | };
23 |
--------------------------------------------------------------------------------
/components/layout/settings/provider/huggingface.tsx:
--------------------------------------------------------------------------------
1 | import { Input } from '@/components/ui/custom/input';
2 | import { ProviderSetting } from '@/types/settings';
3 |
4 | export const HuggingFaceProvider = ({
5 | huggingFace,
6 | setHuggingFace,
7 | }: {
8 | huggingFace: ProviderSetting['HuggingFace'] | null;
9 | setHuggingFace: (value: ProviderSetting['HuggingFace'] | null) => void;
10 | }) => {
11 | return (
12 |
13 |
14 |
HuggingFace API Key
15 |
{
20 | setHuggingFace({
21 | apiKey: e.target.value,
22 | });
23 | }}
24 | />
25 |
26 |
27 | );
28 | };
29 |
--------------------------------------------------------------------------------
/components/layout/settings/provider/mistral.tsx:
--------------------------------------------------------------------------------
1 | import { Input } from '@/components/ui/custom/input';
2 | import { ProviderSetting } from '@/types/settings';
3 |
4 | export const MistralProvider = ({ mistral, setMistral }: { mistral: ProviderSetting['Mistral'] | null; setMistral: (value: ProviderSetting['Mistral'] | null) => void }) => {
5 | return (
6 |
7 |
8 |
Mistral API Key
9 |
{
14 | setMistral({
15 | apiKey: e.target.value,
16 | });
17 | }}
18 | />
19 |
20 |
21 | );
22 | };
23 |
--------------------------------------------------------------------------------
/components/layout/settings/provider/openai.tsx:
--------------------------------------------------------------------------------
1 | import { Input } from '@/components/ui/custom/input';
2 | import { ProviderSetting } from '@/types/settings';
3 |
4 | export const OpenAIProvider = ({ openAI, setOpenAI }: { openAI: ProviderSetting['OpenAI'] | null; setOpenAI: (value: ProviderSetting['OpenAI'] | null) => void }) => {
5 | return (
6 |
7 |
8 |
OpenAI API Key
9 |
{
14 | setOpenAI({
15 | ...openAI,
16 | apiKey: e.target.value,
17 | });
18 | }}
19 | />
20 |
21 |
22 |
OpenAI Endpoint
23 |
{
28 | setOpenAI({
29 | ...openAI,
30 | endpoint: e.target.value,
31 | });
32 | }}
33 | />
34 |
35 |
36 | );
37 | };
38 |
--------------------------------------------------------------------------------
/components/layout/settings/provider/perplexity.tsx:
--------------------------------------------------------------------------------
1 | import { Input } from '@/components/ui/custom/input';
2 | import { ProviderSetting } from '@/types/settings';
3 |
4 | export const PerplexityProvider = ({ perplexity, setPerplexity }: { perplexity: ProviderSetting['Perplexity'] | null; setPerplexity: (value: ProviderSetting['Perplexity'] | null) => void }) => {
5 | return (
6 |
7 |
8 |
Perplexity API Key
9 |
{
14 | setPerplexity({
15 | apiKey: e.target.value,
16 | });
17 | }}
18 | />
19 |
20 |
21 |
Perplexity Endpoint
22 |
{
27 | setPerplexity({
28 | endpoint: e.target.value,
29 | });
30 | }}
31 | />
32 |
33 |
34 | );
35 | };
36 |
--------------------------------------------------------------------------------
/components/layout/settings/search.tsx:
--------------------------------------------------------------------------------
1 | import Image from 'next/image';
2 | import { useRouter } from 'next/navigation';
3 | import { useTranslations } from 'next-intl';
4 |
5 | import { TavilySearchEngine } from '@/components/layout/settings/searcher/tavily';
6 | import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/custom/select';
7 | import { SearchEngine, SearchEngines } from '@/config/search';
8 | import { SearchEngineSetting } from '@/types/search';
9 |
10 | export const SearchSettings = ({
11 | currentSearchEngine,
12 | setCurrentSearchEngine,
13 | tavily,
14 | setTavily,
15 | }: {
16 | currentSearchEngine: SearchEngine;
17 | setCurrentSearchEngine: (value: SearchEngine) => void;
18 | tavily: SearchEngineSetting['Tavily'] | null;
19 | setTavily: (value: SearchEngineSetting['Tavily'] | null) => void;
20 | }) => {
21 | const t = useTranslations();
22 |
23 | const router = useRouter();
24 |
25 | const RenderProviderSettings = () => {
26 | switch (currentSearchEngine) {
27 | case SearchEngine.Tavily:
28 | return ;
29 | case SearchEngine.Google:
30 | default:
31 | return null;
32 | }
33 | };
34 |
35 | return (
36 |
37 |
38 |
{t('provider')}
39 |
52 | }
53 | />
54 |
55 |
56 | {SearchEngines.toSorted((a, b) => a.name.localeCompare(b.name)).map((searcher) => (
57 |
58 |
59 |
60 |
{searcher.name}
61 |
62 |
63 | ))}
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | );
72 | };
73 |
--------------------------------------------------------------------------------
/components/layout/settings/searcher/tavily.tsx:
--------------------------------------------------------------------------------
1 | import { Input } from '@/components/ui/custom/input';
2 | import { SearchEngineSetting } from '@/types/search';
3 |
4 | export const TavilySearchEngine = ({ tavily, setTavily }: { tavily: SearchEngineSetting['Tavily'] | null; setTavily: (value: SearchEngineSetting['Tavily'] | null) => void }) => {
5 | return (
6 |
7 |
8 |
Tavily API Key
9 |
{
14 | setTavily({
15 | apiKey: e.target.value,
16 | });
17 | }}
18 | />
19 |
20 |
21 | );
22 | };
23 |
--------------------------------------------------------------------------------
/components/layout/share-button.tsx:
--------------------------------------------------------------------------------
1 | import { LuShare } from 'react-icons/lu';
2 | import Tippy from '@tippyjs/react';
3 | import { saveAs } from 'file-saver';
4 | import { useAtom } from 'jotai';
5 | import { useSearchParams } from 'next/navigation';
6 | import { useTranslations } from 'next-intl';
7 | import { toast } from 'sonner';
8 |
9 | import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/custom/dropdown-menu';
10 | import store from '@/hooks/store';
11 | import { Conversation } from '@/types/conversation';
12 |
13 | export const ShareButton = () => {
14 | const t = useTranslations();
15 |
16 | const chat = useSearchParams().get('chat');
17 |
18 | const [conversations] = useAtom(store.conversationsAtom);
19 |
20 | const formatAsMarkdown = (conversation: Conversation) => {
21 | return conversation.conversation.map((message) => `**${message.role}:** ${message.content}`).join('\n\n');
22 | };
23 |
24 | const convertTextToImage = (text: string) => {
25 | const canvas = document.createElement('canvas');
26 | const context = canvas.getContext('2d');
27 |
28 | canvas.width = 800;
29 | canvas.height = 600;
30 |
31 | if (context) {
32 | context.fillStyle = '#000';
33 | context.font = '16px Arial';
34 |
35 | const lines = text.split('\\n');
36 | let y = 20;
37 | lines.forEach((line) => {
38 | context.fillText(line, 10, y);
39 | y += 30;
40 | });
41 | }
42 |
43 | return canvas.toDataURL('image/png');
44 | };
45 |
46 | const onShareAsMarkdown = () => {
47 | const conversation = conversations?.find((conversation) => conversation.id === chat);
48 |
49 | if (!conversation) {
50 | toast.error(t('conversation_not_found'), {
51 | position: 'top-right',
52 | });
53 | return;
54 | }
55 |
56 | const markdownContent = formatAsMarkdown(conversation);
57 | const blob = new Blob([markdownContent], { type: 'text/markdown;charset=utf-8' });
58 | saveAs(blob, `conversation-${chat}.md`);
59 | };
60 |
61 | const onShareAsImage = () => {
62 | const conversation = conversations?.find((conversation) => conversation.id === chat);
63 |
64 | if (!conversation) {
65 | toast.error(t('conversation_not_found'), {
66 | position: 'top-right',
67 | });
68 | return;
69 | }
70 |
71 | const markdownContent = formatAsMarkdown(conversation);
72 | const imageData = convertTextToImage(markdownContent);
73 |
74 | const link = document.createElement('a');
75 | link.download = `conversation-${chat}.png`;
76 | link.href = imageData;
77 | link.click();
78 | };
79 |
80 | return (
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | Markdown
90 |
91 |
92 | {t('image')}
93 |
94 |
95 |
96 | );
97 | };
98 |
--------------------------------------------------------------------------------
/components/layout/theme-dropdown.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { useEffect, useState } from 'react';
4 | import { IoInvertMode } from 'react-icons/io5';
5 | import { useTranslations } from 'next-intl';
6 | import { useTheme } from 'next-themes';
7 |
8 | import { DropdownMenu, DropdownMenuContent, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuTrigger } from '@/components/ui/custom/dropdown-menu';
9 | import { ThemeList } from '@/config/theme';
10 |
11 | export const ThemeDropdown = () => {
12 | const t = useTranslations();
13 |
14 | const [mounted, setMounted] = useState(false);
15 | const { theme, setTheme } = useTheme();
16 |
17 | useEffect(() => {
18 | setMounted(true);
19 | }, []);
20 |
21 | if (!mounted) {
22 | return null;
23 | }
24 |
25 | return (
26 |
27 |
28 |
29 | 🌞
30 | 🌚
31 |
32 |
33 | {
36 | setTheme(value);
37 | }}
38 | >
39 | {ThemeList.map((theme) => {
40 | return (
41 |
42 | {theme.icon + ' ' + t(`${theme.name.toString().toLowerCase()}`)}
43 |
44 | );
45 | })}
46 |
47 |
48 |
49 | );
50 | };
51 |
--------------------------------------------------------------------------------
/components/layout/user-avatar.tsx:
--------------------------------------------------------------------------------
1 | import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
2 |
3 | export const UserAvatar = ({
4 | avatarUrl,
5 | avatarFallback,
6 | className,
7 | }: Readonly<{
8 | avatarUrl?: string;
9 | avatarFallback?: string;
10 | className?: string;
11 | }>) => {
12 | return (
13 |
14 |
15 | {avatarFallback ?? 'NO'}
16 |
17 | );
18 | };
19 |
--------------------------------------------------------------------------------
/components/layout/version-label.tsx:
--------------------------------------------------------------------------------
1 | import packageInfo from '@/package.json';
2 |
3 | export const VersionLabel = () => {
4 | return v{packageInfo.version}
;
5 | };
6 |
--------------------------------------------------------------------------------
/components/ui/avatar.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import * as React from 'react';
4 | import * as AvatarPrimitive from '@radix-ui/react-avatar';
5 |
6 | import { cn } from '@/lib/ui/utils';
7 |
8 | const Avatar = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, ...props }, ref) => (
9 |
10 | ));
11 | Avatar.displayName = AvatarPrimitive.Root.displayName;
12 |
13 | const AvatarImage = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, ...props }, ref) => (
14 |
15 | ));
16 | AvatarImage.displayName = AvatarPrimitive.Image.displayName;
17 |
18 | const AvatarFallback = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, ...props }, ref) => (
19 |
20 | ));
21 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
22 |
23 | export { Avatar, AvatarImage, AvatarFallback };
24 |
--------------------------------------------------------------------------------
/components/ui/badge.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { cva, type VariantProps } from 'class-variance-authority';
3 |
4 | import { cn } from '@/lib/ui/utils';
5 |
6 | const badgeVariants = cva('inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', {
7 | variants: {
8 | variant: {
9 | default: 'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
10 | secondary: 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
11 | destructive: 'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
12 | outline: 'text-foreground',
13 | },
14 | },
15 | defaultVariants: {
16 | variant: 'default',
17 | },
18 | });
19 |
20 | export interface BadgeProps extends React.HTMLAttributes, VariantProps {}
21 |
22 | function Badge({ className, variant, ...props }: BadgeProps) {
23 | return ;
24 | }
25 |
26 | export { Badge, badgeVariants };
27 |
--------------------------------------------------------------------------------
/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/ui/utils';
6 |
7 | const buttonVariants = cva(
8 | 'inline-flex items-center justify-center whitespace-nowrap 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: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
14 | outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
15 | secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
16 | ghost: 'hover:bg-accent hover:text-accent-foreground',
17 | link: 'text-primary underline-offset-4 hover:underline',
18 | },
19 | size: {
20 | default: 'h-10 px-4 py-2',
21 | sm: 'h-9 rounded-md px-3',
22 | lg: 'h-11 rounded-md px-8',
23 | icon: 'h-10 w-10',
24 | },
25 | },
26 | defaultVariants: {
27 | variant: 'default',
28 | size: 'default',
29 | },
30 | }
31 | );
32 |
33 | export interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps {
34 | asChild?: boolean;
35 | }
36 |
37 | const Button = React.forwardRef(({ className, variant, size, asChild = false, ...props }, ref) => {
38 | const Comp = asChild ? Slot : 'button';
39 | return ;
40 | });
41 | Button.displayName = 'Button';
42 |
43 | export { Button, buttonVariants };
44 |
--------------------------------------------------------------------------------
/components/ui/checkbox.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import * as React from 'react';
4 | import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
5 | import { Check } from 'lucide-react';
6 |
7 | import { cn } from '@/lib/ui/utils';
8 |
9 | const Checkbox = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, ...props }, ref) => (
10 |
18 |
19 |
20 |
21 |
22 | ));
23 | Checkbox.displayName = CheckboxPrimitive.Root.displayName;
24 |
25 | export { Checkbox };
26 |
--------------------------------------------------------------------------------
/components/ui/custom/checkbox.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import * as React from 'react';
4 | import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
5 | import { Check } from 'lucide-react';
6 |
7 | import { cn } from '@/lib/ui/utils';
8 |
9 | const Checkbox = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, ...props }, ref) => (
10 |
18 |
19 |
20 |
21 |
22 | ));
23 | Checkbox.displayName = CheckboxPrimitive.Root.displayName;
24 |
25 | export { Checkbox };
26 |
--------------------------------------------------------------------------------
/components/ui/custom/dialog.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import * as React from 'react';
4 | import * as DialogPrimitive from '@radix-ui/react-dialog';
5 |
6 | import { cn } from '@/lib/ui/utils';
7 |
8 | const Dialog = DialogPrimitive.Root;
9 |
10 | const DialogTrigger = DialogPrimitive.Trigger;
11 |
12 | const DialogPortal = DialogPrimitive.Portal;
13 |
14 | const DialogClose = DialogPrimitive.Close;
15 |
16 | const DialogOverlay = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, ...props }, ref) => (
17 |
22 | ));
23 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
24 |
25 | const DialogContent = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, children, ...props }, ref) => (
26 |
27 |
28 |
36 | {children}
37 |
38 |
39 | ));
40 | DialogContent.displayName = DialogPrimitive.Content.displayName;
41 |
42 | const DialogHeader = ({ className, ...props }: React.HTMLAttributes) => ;
43 | DialogHeader.displayName = 'DialogHeader';
44 |
45 | const DialogFooter = ({ className, ...props }: React.HTMLAttributes) => ;
46 | DialogFooter.displayName = 'DialogFooter';
47 |
48 | const DialogTitle = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, ...props }, ref) => (
49 |
50 | ));
51 | DialogTitle.displayName = DialogPrimitive.Title.displayName;
52 |
53 | const DialogDescription = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, ...props }, ref) => (
54 |
55 | ));
56 | DialogDescription.displayName = DialogPrimitive.Description.displayName;
57 |
58 | export { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger };
59 |
--------------------------------------------------------------------------------
/components/ui/custom/drawer.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import * as React from 'react';
4 | import { Drawer as DrawerPrimitive } from 'vaul';
5 |
6 | import { cn } from '@/lib/ui/utils';
7 |
8 | const Drawer = ({ shouldScaleBackground = true, ...props }: React.ComponentProps) => ;
9 | Drawer.displayName = 'Drawer';
10 |
11 | const DrawerTrigger = DrawerPrimitive.Trigger;
12 |
13 | const DrawerPortal = DrawerPrimitive.Portal;
14 |
15 | const DrawerClose = DrawerPrimitive.Close;
16 |
17 | const DrawerOverlay = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, ...props }, ref) => (
18 |
19 | ));
20 | DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
21 |
22 | const DrawerContent = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, children, ...props }, ref) => (
23 |
24 |
25 |
30 |
31 | {children}
32 |
33 |
34 | ));
35 | DrawerContent.displayName = 'DrawerContent';
36 |
37 | const DrawerHeader = ({ className, ...props }: React.HTMLAttributes) => ;
38 | DrawerHeader.displayName = 'DrawerHeader';
39 |
40 | const DrawerFooter = ({ className, ...props }: React.HTMLAttributes) => ;
41 | DrawerFooter.displayName = 'DrawerFooter';
42 |
43 | const DrawerTitle = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, ...props }, ref) => (
44 |
45 | ));
46 | DrawerTitle.displayName = DrawerPrimitive.Title.displayName;
47 |
48 | const DrawerDescription = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, ...props }, ref) => (
49 |
50 | ));
51 | DrawerDescription.displayName = DrawerPrimitive.Description.displayName;
52 |
53 | export { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger };
54 |
--------------------------------------------------------------------------------
/components/ui/custom/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { cn } from '@/lib/ui/utils';
4 |
5 | export interface InputProps extends React.InputHTMLAttributes {}
6 |
7 | const Input = React.forwardRef(({ className, type, ...props }, ref) => {
8 | return (
9 |
19 | );
20 | });
21 | Input.displayName = 'Input';
22 |
23 | export { Input };
24 |
--------------------------------------------------------------------------------
/components/ui/custom/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/ui/utils';
7 |
8 | const Switch = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, ...props }, ref) => (
9 |
17 |
20 |
21 | ));
22 | Switch.displayName = SwitchPrimitives.Root.displayName;
23 |
24 | export { Switch };
25 |
--------------------------------------------------------------------------------
/components/ui/custom/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { cn } from '@/lib/ui/utils';
4 |
5 | export interface TextareaProps extends React.TextareaHTMLAttributes {}
6 |
7 | const Textarea = React.forwardRef(({ className, ...props }, ref) => {
8 | return (
9 |
17 | );
18 | });
19 | Textarea.displayName = 'Textarea';
20 |
21 | export { Textarea };
22 |
--------------------------------------------------------------------------------
/components/ui/dialog.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import * as React from 'react';
4 | import * as DialogPrimitive from '@radix-ui/react-dialog';
5 | import { X } from 'lucide-react';
6 |
7 | import { cn } from '@/lib/ui/utils';
8 |
9 | const Dialog = DialogPrimitive.Root;
10 |
11 | const DialogTrigger = DialogPrimitive.Trigger;
12 |
13 | const DialogPortal = DialogPrimitive.Portal;
14 |
15 | const DialogClose = DialogPrimitive.Close;
16 |
17 | const DialogOverlay = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, ...props }, ref) => (
18 |
23 | ));
24 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
25 |
26 | const DialogContent = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, children, ...props }, ref) => (
27 |
28 |
29 |
37 | {children}
38 |
39 |
40 | Close
41 |
42 |
43 |
44 | ));
45 | DialogContent.displayName = DialogPrimitive.Content.displayName;
46 |
47 | const DialogHeader = ({ className, ...props }: React.HTMLAttributes) => ;
48 | DialogHeader.displayName = 'DialogHeader';
49 |
50 | const DialogFooter = ({ className, ...props }: React.HTMLAttributes) => ;
51 | DialogFooter.displayName = 'DialogFooter';
52 |
53 | const DialogTitle = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, ...props }, ref) => (
54 |
55 | ));
56 | DialogTitle.displayName = DialogPrimitive.Title.displayName;
57 |
58 | const DialogDescription = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, ...props }, ref) => (
59 |
60 | ));
61 | DialogDescription.displayName = DialogPrimitive.Description.displayName;
62 |
63 | export { Dialog, DialogPortal, DialogOverlay, DialogClose, DialogTrigger, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription };
64 |
--------------------------------------------------------------------------------
/components/ui/drawer.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import * as React from 'react';
4 | import { Drawer as DrawerPrimitive } from 'vaul';
5 |
6 | import { cn } from '@/lib/ui/utils';
7 |
8 | const Drawer = ({ shouldScaleBackground = true, ...props }: React.ComponentProps) => ;
9 | Drawer.displayName = 'Drawer';
10 |
11 | const DrawerTrigger = DrawerPrimitive.Trigger;
12 |
13 | const DrawerPortal = DrawerPrimitive.Portal;
14 |
15 | const DrawerClose = DrawerPrimitive.Close;
16 |
17 | const DrawerOverlay = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, ...props }, ref) => (
18 |
19 | ));
20 | DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
21 |
22 | const DrawerContent = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, children, ...props }, ref) => (
23 |
24 |
25 |
26 |
27 | {children}
28 |
29 |
30 | ));
31 | DrawerContent.displayName = 'DrawerContent';
32 |
33 | const DrawerHeader = ({ className, ...props }: React.HTMLAttributes) => ;
34 | DrawerHeader.displayName = 'DrawerHeader';
35 |
36 | const DrawerFooter = ({ className, ...props }: React.HTMLAttributes) => ;
37 | DrawerFooter.displayName = 'DrawerFooter';
38 |
39 | const DrawerTitle = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, ...props }, ref) => (
40 |
41 | ));
42 | DrawerTitle.displayName = DrawerPrimitive.Title.displayName;
43 |
44 | const DrawerDescription = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, ...props }, ref) => (
45 |
46 | ));
47 | DrawerDescription.displayName = DrawerPrimitive.Description.displayName;
48 |
49 | export { Drawer, DrawerPortal, DrawerOverlay, DrawerTrigger, DrawerClose, DrawerContent, DrawerHeader, DrawerFooter, DrawerTitle, DrawerDescription };
50 |
--------------------------------------------------------------------------------
/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { cn } from '@/lib/ui/utils';
4 |
5 | export interface InputProps extends React.InputHTMLAttributes {}
6 |
7 | const Input = React.forwardRef(({ className, type, ...props }, ref) => {
8 | return (
9 |
18 | );
19 | });
20 | Input.displayName = 'Input';
21 |
22 | export { Input };
23 |
--------------------------------------------------------------------------------
/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/ui/utils';
8 |
9 | const labelVariants = cva('text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70');
10 |
11 | const Label = React.forwardRef, React.ComponentPropsWithoutRef & VariantProps>(
12 | ({ className, ...props }, ref) =>
13 | );
14 | Label.displayName = LabelPrimitive.Root.displayName;
15 |
16 | export { Label };
17 |
--------------------------------------------------------------------------------
/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/ui/utils';
7 |
8 | const Switch = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, ...props }, ref) => (
9 |
17 |
20 |
21 | ));
22 | Switch.displayName = SwitchPrimitives.Root.displayName;
23 |
24 | export { Switch };
25 |
--------------------------------------------------------------------------------
/components/ui/tabs.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import * as React from 'react';
4 | import * as TabsPrimitive from '@radix-ui/react-tabs';
5 |
6 | import { cn } from '@/lib/ui/utils';
7 |
8 | const Tabs = TabsPrimitive.Root;
9 |
10 | const TabsList = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, ...props }, ref) => (
11 |
12 | ));
13 | TabsList.displayName = TabsPrimitive.List.displayName;
14 |
15 | const TabsTrigger = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, ...props }, ref) => (
16 |
24 | ));
25 | TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
26 |
27 | const TabsContent = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, ...props }, ref) => (
28 |
33 | ));
34 | TabsContent.displayName = TabsPrimitive.Content.displayName;
35 |
36 | export { Tabs, TabsList, TabsTrigger, TabsContent };
37 |
--------------------------------------------------------------------------------
/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { cn } from '@/lib/ui/utils';
4 |
5 | export interface TextareaProps extends React.TextareaHTMLAttributes {}
6 |
7 | const Textarea = React.forwardRef(({ className, ...props }, ref) => {
8 | return (
9 |
17 | );
18 | });
19 | Textarea.displayName = 'Textarea';
20 |
21 | export { Textarea };
22 |
--------------------------------------------------------------------------------
/config/i18n/index.ts:
--------------------------------------------------------------------------------
1 | export const LanguageList = [
2 | {
3 | id: 'de',
4 | name: 'Deutsch',
5 | flag: '🇩🇪',
6 | },
7 | {
8 | id: 'en',
9 | name: 'English',
10 | flag: '🇺🇸',
11 | },
12 | {
13 | id: 'es',
14 | name: 'Español',
15 | flag: '🇪🇸',
16 | },
17 | {
18 | id: 'fr',
19 | name: 'Français',
20 | flag: '🇫🇷',
21 | },
22 | {
23 | id: 'it',
24 | name: 'Italiano',
25 | flag: '🇮🇹',
26 | },
27 | {
28 | id: 'ja',
29 | name: '日本語',
30 | flag: '🇯🇵',
31 | },
32 | {
33 | id: 'ko',
34 | name: '한국어',
35 | flag: '🇰🇷',
36 | },
37 | {
38 | id: 'nl',
39 | name: 'Nederlands',
40 | flag: '🇳🇱',
41 | },
42 | {
43 | id: 'pt',
44 | name: 'Português',
45 | flag: '🇵🇹',
46 | },
47 | {
48 | id: 'ru',
49 | name: 'Русский',
50 | flag: '🇷🇺',
51 | },
52 | {
53 | id: 'zh-CN',
54 | name: '简体中文',
55 | flag: '🇨🇳',
56 | },
57 | {
58 | id: 'zh-HK',
59 | name: '繁体中文(香港)',
60 | flag: '🇭🇰',
61 | },
62 | {
63 | id: 'zh-TW',
64 | name: '繁體中文(台湾)',
65 | flag: '🇹🇼',
66 | },
67 | ];
68 |
69 | export const languageId = ['de', 'en', 'es', 'fr', 'it', 'ja', 'ko', 'nl', 'pt', 'ru', 'zh-CN', 'zh-HK', 'zh-TW'];
70 |
--------------------------------------------------------------------------------
/config/provider/anthropic.ts:
--------------------------------------------------------------------------------
1 | import { Model } from '@/types/model';
2 |
3 | export type AnthropicModelId = 'claude-3-opus-20240229' | 'claude-3-sonnet-20240229' | 'claude-3-haiku-20240307' | 'claude-2.1' | 'claude-2.0' | 'claude-instant-1.2';
4 | export type AnthropicModelName = 'Claude 3 Sonnet' | 'Claude 3 Haiku' | 'Claude 3 Opus' | 'Claude 2.1' | 'Claude 2.0' | 'Claude Instant 1.2';
5 |
6 | export const model: Model[] = [
7 | {
8 | id: 'claude-3-sonnet-20240229',
9 | name: 'Claude 3 Sonnet',
10 | maxInputTokens: null,
11 | maxOutputTokens: 4096,
12 | maxTokens: 200000,
13 | price: 18.0,
14 | vision: true,
15 | },
16 | {
17 | id: 'claude-3-haiku-20240307',
18 | name: 'Claude 3 Haiku',
19 | maxInputTokens: null,
20 | maxOutputTokens: 4096,
21 | maxTokens: 200000,
22 | price: 1.5,
23 | vision: true,
24 | },
25 | {
26 | id: 'claude-3-opus-20240229',
27 | name: 'Claude 3 Opus',
28 | maxInputTokens: null,
29 | maxOutputTokens: 4096,
30 | maxTokens: 200000,
31 | price: 90.0,
32 | vision: true,
33 | },
34 | {
35 | id: 'claude-2.1',
36 | name: 'Claude 2.1',
37 | maxInputTokens: null,
38 | maxOutputTokens: 4096,
39 | maxTokens: 200000,
40 | price: 18.0,
41 | vision: false,
42 | },
43 | {
44 | id: 'claude-2.0',
45 | name: 'Claude 2.0',
46 | maxInputTokens: null,
47 | maxOutputTokens: 4096,
48 | maxTokens: 200000,
49 | price: 18.0,
50 | vision: false,
51 | },
52 | {
53 | id: 'claude-instant-1.2',
54 | name: 'Claude Instant 1.2',
55 | maxInputTokens: null,
56 | maxOutputTokens: 4096,
57 | maxTokens: 200000,
58 | price: 18.0,
59 | vision: false,
60 | },
61 | ];
62 |
--------------------------------------------------------------------------------
/config/provider/azure.ts:
--------------------------------------------------------------------------------
1 | import { Model } from '@/types/model';
2 |
3 | export type AzureOpenAIModelId =
4 | | 'gpt-4'
5 | | 'gpt-4-0613'
6 | | 'gpt-4-1106-preview'
7 | | 'gpt-4-0125-preview'
8 | | 'gpt-4-vision-preview'
9 | | 'gpt-4-32k-0613'
10 | | 'gpt-35-turbo-0301'
11 | | 'gpt-35-turbo-0613'
12 | | 'gpt-35-turbo-1106'
13 | | 'gpt-35-turbo-0125'
14 | | 'gpt-35-turbo-16k-0613';
15 | export type AzureOpenAIModelName =
16 | | 'GPT-4'
17 | | 'GPT-4 0613'
18 | | 'GPT-4 1106 Preview'
19 | | 'GPT-4 0125 Preview'
20 | | 'GPT-4 Vision Preview'
21 | | 'GPT-4 32K 0613'
22 | | 'GPT-3.5 Turbo 0301'
23 | | 'GPT-3.5 Turbo 0613'
24 | | 'GPT-3.5 Turbo 1106'
25 | | 'GPT-3.5 Turbo 0125'
26 | | 'GPT-3.5 Turbo 16K 0613';
27 |
28 | export const model: Model[] = [
29 | {
30 | id: 'gpt-4',
31 | name: 'GPT-4',
32 | maxInputTokens: null,
33 | maxOutputTokens: 4096,
34 | maxTokens: 200000,
35 | price: 18.0,
36 | },
37 | {
38 | id: 'gpt-4-0613',
39 | name: 'GPT-4 0613',
40 | maxInputTokens: null,
41 | maxOutputTokens: 4096,
42 | maxTokens: 200000,
43 | price: 18.0,
44 | },
45 | {
46 | id: 'gpt-4-1106-preview',
47 | name: 'GPT-4 1106 Preview',
48 | maxInputTokens: null,
49 | maxOutputTokens: 4096,
50 | maxTokens: 200000,
51 | price: 18.0,
52 | },
53 | {
54 | id: 'gpt-4-0125-preview',
55 | name: 'GPT-4 0125 Preview',
56 | maxInputTokens: null,
57 | maxOutputTokens: 4096,
58 | maxTokens: 200000,
59 | price: 18.0,
60 | },
61 | {
62 | id: 'gpt-4-vision-preview',
63 | name: 'GPT-4 Vision Preview',
64 | maxInputTokens: null,
65 | maxOutputTokens: 4096,
66 | maxTokens: 200000,
67 | price: 18.0,
68 | },
69 | {
70 | id: 'gpt-4-32k-0613',
71 | name: 'GPT-4 32K 0613',
72 | maxInputTokens: null,
73 | maxOutputTokens: 4096,
74 | maxTokens: 200000,
75 | price: 18.0,
76 | },
77 | {
78 | id: 'gpt-35-turbo-0301',
79 | name: 'GPT-3.5 Turbo 0301',
80 | maxInputTokens: null,
81 | maxOutputTokens: 4096,
82 | maxTokens: 200000,
83 | price: 18.0,
84 | },
85 | {
86 | id: 'gpt-35-turbo-0613',
87 | name: 'GPT-3.5 Turbo 0613',
88 | maxInputTokens: null,
89 | maxOutputTokens: 4096,
90 | maxTokens: 200000,
91 | price: 18.0,
92 | },
93 | {
94 | id: 'gpt-35-turbo-1106',
95 | name: 'GPT-3.5 Turbo 1106',
96 | maxInputTokens: null,
97 | maxOutputTokens: 4096,
98 | maxTokens: 200000,
99 | price: 18,
100 | },
101 | ];
102 |
--------------------------------------------------------------------------------
/config/provider/cohere.ts:
--------------------------------------------------------------------------------
1 | import { Model } from '@/types/model';
2 |
3 | export type CohereModelId = 'command-light' | 'command-light-nightly' | 'command' | 'command-nightly' | 'command-r' | 'command-r-plus';
4 | export type CohereModelName = 'Command Light' | 'Command Light Nightly' | 'Command' | 'Command Nightly' | 'Command R' | 'Command R +';
5 |
6 | export const model: Model[] = [
7 | {
8 | id: 'command-light',
9 | name: 'Command Light',
10 | maxInputTokens: 4096,
11 | maxOutputTokens: 4096,
12 | maxTokens: 4096,
13 | price: 18.0,
14 | },
15 | {
16 | id: 'command-light-nightly',
17 | name: 'Command Light Nightly',
18 | maxInputTokens: 8192,
19 | maxOutputTokens: 8192,
20 | maxTokens: 8192,
21 | price: 1.5,
22 | },
23 | {
24 | id: 'command',
25 | name: 'Command',
26 | maxInputTokens: 4096,
27 | maxOutputTokens: 4096,
28 | maxTokens: 4096,
29 | price: 90.0,
30 | },
31 | {
32 | id: 'command-nightly',
33 | name: 'Command Nightly',
34 | maxInputTokens: 8192,
35 | maxOutputTokens: 8192,
36 | maxTokens: 8192,
37 | price: 90.0,
38 | },
39 | {
40 | id: 'command-r',
41 | name: 'Command R',
42 | maxInputTokens: 128000,
43 | maxOutputTokens: 4096,
44 | maxTokens: 128000,
45 | price: 90.0,
46 | },
47 | {
48 | id: 'command-r-plus',
49 | name: 'Command R +',
50 | maxInputTokens: 128000,
51 | maxOutputTokens: 4096,
52 | maxTokens: 128000,
53 | price: 90.0,
54 | },
55 | ];
56 |
--------------------------------------------------------------------------------
/config/provider/fireworks.ts:
--------------------------------------------------------------------------------
1 | import { Model } from '@/types/model';
2 |
3 | export type FireworksModelId =
4 | | 'accounts/fireworks/models/llama-v2-7b'
5 | | 'accounts/fireworks/models/llama-v2-7b-chat'
6 | | 'accounts/fireworks/models/llama-v2-13b'
7 | | 'accounts/fireworks/models/llama-v2-13b-chat'
8 | | 'accounts/fireworks/models/llama-v2-34b-code-w8a16'
9 | | 'accounts/fireworks/models/llama-v2-70b-chat'
10 | | 'accounts/fireworks/models/mistral-7b'
11 | | 'accounts/fireworks/models/mistral-7b-instruct-4k'
12 | | 'accounts/fireworks/models/zephyr-7b-beta'
13 | | 'accounts/fireworks/models/mixtral-8x7b'
14 | | 'accounts/fireworks/models/mixtral-8x7b-instruct';
15 | export type FireworksModelName =
16 | | 'LLAMA V2 7B'
17 | | 'LLAMA V2 7B Chat'
18 | | 'LLAMA V2 13B'
19 | | 'LLAMA V2 13B Chat'
20 | | 'LLAMA V2 34B Code W8A16'
21 | | 'LLAMA V2 70B Chat'
22 | | 'Mistral 7B'
23 | | 'Mistral 7B Instruct 4K'
24 | | 'Zephyr 7B Beta'
25 | | 'Mixtral 8x7B'
26 | | 'Mixtral 8x7B Instruct';
27 |
28 | export const model: Model[] = [
29 | {
30 | id: 'accounts/fireworks/models/llama-v2-7b',
31 | name: 'LLAMA V2 7B',
32 | maxInputTokens: null,
33 | maxOutputTokens: 4096,
34 | maxTokens: 200000,
35 | price: 18.0,
36 | },
37 | {
38 | id: 'accounts/fireworks/models/llama-v2-7b-chat',
39 | name: 'LLAMA V2 7B Chat',
40 | maxInputTokens: null,
41 | maxOutputTokens: 4096,
42 | maxTokens: 200000,
43 | price: 18.0,
44 | },
45 | {
46 | id: 'accounts/fireworks/models/llama-v2-13b',
47 | name: 'LLAMA V2 13B',
48 | maxInputTokens: null,
49 | maxOutputTokens: 4096,
50 | maxTokens: 200000,
51 | price: 18.0,
52 | },
53 | {
54 | id: 'accounts/fireworks/models/llama-v2-13b-chat',
55 | name: 'LLAMA V2 13B Chat',
56 | maxInputTokens: null,
57 | maxOutputTokens: 4096,
58 | maxTokens: 200000,
59 | price: 18.0,
60 | },
61 | {
62 | id: 'accounts/fireworks/models/llama-v2-34b-code-w8a16',
63 | name: 'LLAMA V2 34B Code W8A16',
64 | maxInputTokens: null,
65 | maxOutputTokens: 4096,
66 | maxTokens: 200000,
67 | price: 18.0,
68 | },
69 | {
70 | id: 'accounts/fireworks/models/llama-v2-70b-chat',
71 | name: 'LLAMA V2 70B Chat',
72 | maxInputTokens: null,
73 | maxOutputTokens: 4096,
74 | maxTokens: 200000,
75 | price: 18.0,
76 | },
77 | {
78 | id: 'accounts/fireworks/models/mistral-7b',
79 | name: 'Mistral 7B',
80 | maxInputTokens: null,
81 | maxOutputTokens: 4096,
82 | maxTokens: 200000,
83 | price: 18.0,
84 | },
85 | {
86 | id: 'accounts/fireworks/models/mistral-7b-instruct-4k',
87 | name: 'Mistral 7B Instruct 4K',
88 | maxInputTokens: null,
89 | maxOutputTokens: 4096,
90 | maxTokens: 200000,
91 | price: 18.0,
92 | },
93 | {
94 | id: 'accounts/fireworks/models/zephyr-7b-beta',
95 | name: 'Zephyr 7B Beta',
96 | maxInputTokens: null,
97 | maxOutputTokens: 4096,
98 | maxTokens: 200000,
99 | price: 18.0,
100 | },
101 | {
102 | id: 'accounts/fireworks/models/mixtral-8x7b',
103 | name: 'Mixtral 8x7B',
104 | maxInputTokens: null,
105 | maxOutputTokens: 4096,
106 | maxTokens: 200000,
107 | price: 18.0,
108 | },
109 | {
110 | id: 'accounts/fireworks/models/mixtral-8x7b-instruct',
111 | name: 'Mixtral 8x7B Instruct',
112 | maxInputTokens: null,
113 | maxOutputTokens: 4096,
114 | maxTokens: 200000,
115 | price: 18.0,
116 | },
117 | ];
118 |
--------------------------------------------------------------------------------
/config/provider/google.ts:
--------------------------------------------------------------------------------
1 | import { Model } from '@/types/model';
2 |
3 | export type GoogleModelId = 'gemini-1.5-pro-latest' | 'gemini-pro';
4 | export type GoogleModelName = 'Gemini 1.5 Pro' | 'Gemini Pro';
5 |
6 | export const model: Model[] = [
7 | {
8 | id: 'gemini-pro',
9 | name: 'Gemini Pro',
10 | maxInputTokens: 30720,
11 | maxOutputTokens: 2048,
12 | maxTokens: 1000,
13 | price: 0.06,
14 | },
15 | {
16 | id: 'gemini-1.5-pro-latest',
17 | name: 'Gemini 1.5 Pro',
18 | maxInputTokens: 1048576,
19 | maxOutputTokens: 8192,
20 | maxTokens: 1000,
21 | price: 0.06,
22 | },
23 | ];
24 |
--------------------------------------------------------------------------------
/config/provider/groq.ts:
--------------------------------------------------------------------------------
1 | import { Model } from '@/types/model';
2 |
3 | export type GroqModelId = 'llama2-70b-4096' | 'mixtral-8x7b-32768' | 'gemma-7b-it';
4 | export type GroqModelName = 'LLaMA2 70b Chat' | 'Mixtral 8x7b Instruct v0.1' | 'Gemma 7b it';
5 |
6 | export const model: Model[] = [
7 | {
8 | id: 'llama2-70b-4096',
9 | name: 'LLaMA2 70b Chat',
10 | maxInputTokens: null,
11 | maxOutputTokens: null,
12 | maxTokens: 4096,
13 | price: 0.06,
14 | },
15 | {
16 | id: 'mixtral-8x7b-32768',
17 | name: 'Mixtral 8x7b Instruct v0.1',
18 | maxInputTokens: null,
19 | maxOutputTokens: null,
20 | maxTokens: 32768,
21 | price: 0.06,
22 | },
23 | {
24 | id: 'gemma-7b-it',
25 | name: 'Gemma 7b it',
26 | maxInputTokens: null,
27 | maxOutputTokens: null,
28 | maxTokens: 8192,
29 | price: 0.06,
30 | },
31 | ];
32 |
--------------------------------------------------------------------------------
/config/provider/huggingface.ts:
--------------------------------------------------------------------------------
1 | import { Model } from '@/types/model';
2 |
3 | export type HuggingFaceModelId =
4 | | 'OpenAssistant/oasst-sft-4-pythia-12b-epoch-3.5'
5 | | 'CohereForAl/c4ai-command-r-plus'
6 | | 'meta-llama/Meta-Llama-3-70B-Instruct'
7 | | 'HuggingFaceH4/zephyr-orpo-141b-A35b-v0.1'
8 | | 'mistralai/Mixtral-8x7B-Instruct-v0.1'
9 | | 'google/gemma-1.1-7b-it'
10 | | 'NousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO'
11 | | 'mistralai/Mistral-7B-Instruct-v0.2';
12 | export type HuggingFaceModelName =
13 | | 'OpenAssistant/oasst-sft-4-pythia-12b-epoch-3.5'
14 | | 'CohereForAl/c4ai-command-r-plus'
15 | | 'meta-llama/Meta-Llama-3-70B-Instruct'
16 | | 'HuggingFaceH4/zephyr-orpo-141b-A35b-v0.1'
17 | | 'mistralai/Mixtral-8x7B-Instruct-v0.1'
18 | | 'google/gemma-1.1-7b-it'
19 | | 'NousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO'
20 | | 'mistralai/Mistral-7B-Instruct-v0.2';
21 |
22 | export const model: Model[] = [
23 | {
24 | id: 'OpenAssistant/oasst-sft-4-pythia-12b-epoch-3.5',
25 | name: 'OpenAssistant/oasst-sft-4-pythia-12b-epoch-3.5',
26 | maxInputTokens: null,
27 | maxOutputTokens: null,
28 | maxTokens: null,
29 | price: null,
30 | },
31 | {
32 | id: 'CohereForAl/c4ai-command-r-plus',
33 | name: 'CohereForAl/c4ai-command-r-plus',
34 | maxInputTokens: null,
35 | maxOutputTokens: null,
36 | maxTokens: null,
37 | price: null,
38 | },
39 | {
40 | id: 'meta-llama/Meta-Llama-3-70B-Instruct',
41 | name: 'meta-llama/Meta-Llama-3-70B-Instruct',
42 | maxInputTokens: null,
43 | maxOutputTokens: null,
44 | maxTokens: null,
45 | price: null,
46 | },
47 | {
48 | id: 'HuggingFaceH4/zephyr-orpo-141b-A35b-v0.1',
49 | name: 'HuggingFaceH4/zephyr-orpo-141b-A35b-v0.1',
50 | maxInputTokens: null,
51 | maxOutputTokens: null,
52 | maxTokens: null,
53 | price: null,
54 | },
55 | {
56 | id: 'mistralai/Mixtral-8x7B-Instruct-v0.1',
57 | name: 'mistralai/Mixtral-8x7B-Instruct-v0.1',
58 | maxInputTokens: null,
59 | maxOutputTokens: null,
60 | maxTokens: null,
61 | price: null,
62 | },
63 | {
64 | id: 'google/gemma-1.1-7b-it',
65 | name: 'google/gemma-1.1-7b-it',
66 | maxInputTokens: null,
67 | maxOutputTokens: null,
68 | maxTokens: null,
69 | price: null,
70 | },
71 | {
72 | id: 'NousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO',
73 | name: 'NousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO',
74 | maxInputTokens: null,
75 | maxOutputTokens: null,
76 | maxTokens: null,
77 | price: null,
78 | },
79 | {
80 | id: 'mistralai/Mistral-7B-Instruct-v0.2',
81 | name: 'mistralai/Mistral-7B-Instruct-v0.2',
82 | maxInputTokens: null,
83 | maxOutputTokens: null,
84 | maxTokens: null,
85 | price: null,
86 | },
87 | ];
88 |
--------------------------------------------------------------------------------
/config/provider/index.ts:
--------------------------------------------------------------------------------
1 | import { AmazonModelId, AmazonModelName } from '@/config/provider/amazon';
2 | import { AnthropicModelId, AnthropicModelName } from '@/config/provider/anthropic';
3 | import { AzureOpenAIModelId, AzureOpenAIModelName } from '@/config/provider/azure';
4 | import { CohereModelId, CohereModelName } from '@/config/provider/cohere';
5 | import { FireworksModelId, FireworksModelName } from '@/config/provider/fireworks';
6 | import { GoogleModelId, GoogleModelName } from '@/config/provider/google';
7 | import { GroqModelId, GroqModelName } from '@/config/provider/groq';
8 | import { HuggingFaceModelId, HuggingFaceModelName } from '@/config/provider/huggingface';
9 | import { MistralModelId, MistralModelName } from '@/config/provider/mistral';
10 | import { OpenAIModelId, OpenAIModelName } from '@/config/provider/openai';
11 | import { PerplexityModelId, PerplexityModelName } from '@/config/provider/perplexity';
12 |
13 | export type AllModelId =
14 | | AmazonModelId
15 | | AnthropicModelId
16 | | AzureOpenAIModelId
17 | | CohereModelId
18 | | FireworksModelId
19 | | GroqModelId
20 | | HuggingFaceModelId
21 | | MistralModelId
22 | | GoogleModelId
23 | | OpenAIModelId
24 | | PerplexityModelId;
25 |
26 | export type AllModelName =
27 | | AmazonModelName
28 | | AnthropicModelName
29 | | AzureOpenAIModelName
30 | | CohereModelName
31 | | FireworksModelName
32 | | GroqModelName
33 | | HuggingFaceModelName
34 | | MistralModelName
35 | | GoogleModelName
36 | | OpenAIModelName
37 | | PerplexityModelName;
38 |
39 | export enum Provider {
40 | Amazon = 'Amazon',
41 | OpenAI = 'OpenAI',
42 | Google = 'Google',
43 | Anthropic = 'Anthropic',
44 | Azure = 'Azure',
45 | Cohere = 'Cohere',
46 | Fireworks = 'Fireworks',
47 | Groq = 'Groq',
48 | HuggingFace = 'HuggingFace',
49 | Mistral = 'Mistral',
50 | Perplexity = 'Perplexity',
51 |
52 | Custom = 'Custom',
53 | }
54 |
55 | export const Providers: {
56 | id: string;
57 | name: Provider;
58 | }[] = [
59 | {
60 | id: 'amazon',
61 | name: Provider.Amazon,
62 | },
63 | {
64 | id: 'openai',
65 | name: Provider.OpenAI,
66 | },
67 | {
68 | id: 'google',
69 | name: Provider.Google,
70 | },
71 | {
72 | id: 'anthropic',
73 | name: Provider.Anthropic,
74 | },
75 | {
76 | id: 'azure',
77 | name: Provider.Azure,
78 | },
79 | {
80 | id: 'cohere',
81 | name: Provider.Cohere,
82 | },
83 | {
84 | id: 'fireworks',
85 | name: Provider.Fireworks,
86 | },
87 | {
88 | id: 'groq',
89 | name: Provider.Groq,
90 | },
91 | {
92 | id: 'huggingface',
93 | name: Provider.HuggingFace,
94 | },
95 | {
96 | id: 'mistral',
97 | name: Provider.Mistral,
98 | },
99 | {
100 | id: 'perplexity',
101 | name: Provider.Perplexity,
102 | },
103 | {
104 | id: 'custom',
105 | name: Provider.Custom,
106 | },
107 | ];
108 |
--------------------------------------------------------------------------------
/config/provider/mistral.ts:
--------------------------------------------------------------------------------
1 | import { Model } from '@/types/model';
2 |
3 | export type MistralModelId = 'open-mistral-7b' | 'open-mixtral-8x7b' | 'mistral-small-latest' | 'mistral-medium-latest' | 'mistral-large-latest';
4 | export type MistralModelName = 'open-mistral-7b' | 'open-mixtral-8x7b' | 'mistral-small-latest' | 'mistral-medium-latest' | 'mistral-large-latest';
5 |
6 | export const model: Model[] = [
7 | {
8 | id: 'open-mistral-7b',
9 | name: 'open-mistral-7b',
10 | maxInputTokens: null,
11 | maxOutputTokens: null,
12 | maxTokens: null,
13 | price: null,
14 | },
15 | {
16 | id: 'open-mixtral-8x7b',
17 | name: 'open-mixtral-8x7b',
18 | maxInputTokens: null,
19 | maxOutputTokens: null,
20 | maxTokens: null,
21 | price: null,
22 | },
23 | {
24 | id: 'mistral-small-latest',
25 | name: 'mistral-small-latest',
26 | maxInputTokens: null,
27 | maxOutputTokens: null,
28 | maxTokens: null,
29 | price: null,
30 | },
31 | {
32 | id: 'mistral-medium-latest',
33 | name: 'mistral-medium-latest',
34 | maxInputTokens: null,
35 | maxOutputTokens: null,
36 | maxTokens: null,
37 | price: null,
38 | },
39 | {
40 | id: 'mistral-large-latest',
41 | name: 'mistral-large-latest',
42 | maxInputTokens: null,
43 | maxOutputTokens: null,
44 | maxTokens: null,
45 | price: null,
46 | },
47 | ];
48 |
--------------------------------------------------------------------------------
/config/provider/perplexity.ts:
--------------------------------------------------------------------------------
1 | import { Model } from '@/types/model';
2 |
3 | export type PerplexityModelId = 'sonar-small-chat' | 'sonar-small-online' | 'sonar-medium-chat' | 'sonar-medium-online' | 'mistral-7b-instruct' | 'mixtral-8x7b-instruct';
4 | export type PerplexityModelName = 'Sonar Small Chat' | 'Sonar Small Online' | 'Sonar Medium Chat' | 'Sonar Medium Online' | 'Mistral 7B Instruct' | 'Mixtral 8x7B Instruct';
5 |
6 | export const model: Model[] = [
7 | {
8 | id: 'sonar-small-chat',
9 | name: 'Sonar Small Chat',
10 | maxInputTokens: null,
11 | maxOutputTokens: null,
12 | maxTokens: null,
13 | price: null,
14 | },
15 | {
16 | id: 'sonar-small-online',
17 | name: 'Sonar Small Online',
18 | maxInputTokens: null,
19 | maxOutputTokens: null,
20 | maxTokens: null,
21 | price: null,
22 | },
23 | {
24 | id: 'sonar-medium-chat',
25 | name: 'Sonar Medium Chat',
26 | maxInputTokens: null,
27 | maxOutputTokens: null,
28 | maxTokens: null,
29 | price: null,
30 | },
31 | {
32 | id: 'sonar-medium-online',
33 | name: 'Sonar Medium Online',
34 | maxInputTokens: null,
35 | maxOutputTokens: null,
36 | maxTokens: null,
37 | price: null,
38 | },
39 | {
40 | id: 'mistral-7b-instruct',
41 | name: 'Mistral 7B Instruct',
42 | maxInputTokens: null,
43 | maxOutputTokens: null,
44 | maxTokens: null,
45 | price: null,
46 | },
47 | {
48 | id: 'mixtral-8x7b-instruct',
49 | name: 'Mixtral 8x7B Instruct',
50 | maxInputTokens: null,
51 | maxOutputTokens: null,
52 | maxTokens: null,
53 | price: null,
54 | },
55 | ];
56 |
--------------------------------------------------------------------------------
/config/search/index.ts:
--------------------------------------------------------------------------------
1 | export enum SearchEngine {
2 | Google = 'Google',
3 | Tavily = 'Tavily',
4 | }
5 |
6 | export const SearchEngines: {
7 | id: string;
8 | name: SearchEngine;
9 | }[] = [
10 | {
11 | id: 'google',
12 | name: SearchEngine.Google,
13 | },
14 | {
15 | id: 'tavily',
16 | name: SearchEngine.Tavily,
17 | },
18 | ];
19 |
--------------------------------------------------------------------------------
/config/theme/index.ts:
--------------------------------------------------------------------------------
1 | export const ThemeList = [
2 | {
3 | id: 'system',
4 | name: 'System',
5 | icon: '🌐',
6 | },
7 | {
8 | id: 'light',
9 | name: 'Light',
10 | icon: '🌞',
11 | },
12 | {
13 | id: 'dark',
14 | name: 'Dark',
15 | icon: '🌚',
16 | },
17 | ];
18 |
19 | export const themeId = ['system', 'light', 'dark'];
20 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.4'
2 |
3 | services:
4 | chatchat:
5 | image: chatchat
6 | build:
7 | context: .
8 | dockerfile: ./Dockerfile
9 | environment:
10 | - AWS_ACCESS_KEY=
11 | - AWS_SECRET_KEY=
12 | - AWS_REGION=
13 |
14 | - ANTHROPIC_API_KEY=
15 |
16 | - AZURE_OPENAI_API_KEY=
17 | - AZURE_OPENAI_ENDPOINT=
18 | - AZURE_OPENAI_DEPLOY_INSTANCE_NAME=
19 |
20 | - COHERE_API_KEY=
21 |
22 | - FIREWORKS_API_KEY=
23 |
24 | - GOOGLE_API_KEY=
25 |
26 | - GROQ_API_KEY=
27 |
28 | - HUGGINGFACE_API_KEY=
29 |
30 | - MISTRAL_API_KEY=
31 |
32 | - OPENAI_API_KEY=
33 | - OPENAI_API_ENDPOINT=
34 |
35 | - PERPLEXITY_API_KEY=
36 | - PERPLEXITY_ENDPOINT=
37 | restart: always
38 | ports:
39 | - 3000:3000
40 |
--------------------------------------------------------------------------------
/hooks/storage.ts:
--------------------------------------------------------------------------------
1 | export const setLocalStorage = (key: string, value: any) => {
2 | return localStorage.setItem(key, JSON.stringify(value));
3 | };
4 |
5 | export const getLocalStorage = (key: string) => {
6 | return JSON.parse(localStorage.getItem(key) ?? '[]');
7 | };
8 |
--------------------------------------------------------------------------------
/hooks/store.ts:
--------------------------------------------------------------------------------
1 | import { atom } from 'jotai';
2 | import { atomWithStorage } from 'jotai/utils';
3 |
4 | import { Provider } from '@/config/provider';
5 | import { OpenAIModelId, OpenAIModelName } from '@/config/provider/openai';
6 | import { SearchEngine } from '@/config/search';
7 | import { Conversation } from '@/types/conversation';
8 | import { Model, SimpleModel } from '@/types/model';
9 | import { SearchEngineSetting } from '@/types/search';
10 | import { AdvancedSettings, ConversationSettings, Preference, ProviderSetting, Theme } from '@/types/settings';
11 |
12 | // provider management
13 | const currentProviderSettingsAtom = atomWithStorage('currentProviderSettings', null);
14 |
15 | const customProviderModelAtom = atomWithStorage('customProviderModel', null);
16 |
17 | // search engine management
18 | const currentSearchEngineSettingsAtom = atomWithStorage('currentSearchEngineSettings', null);
19 |
20 | const currentSearchEngineAtom = atomWithStorage('currentSearchEngine', SearchEngine.Tavily);
21 |
22 | // model management
23 | const currentUseModelAtom = atomWithStorage('currentUseModel', {
24 | provider: Provider.OpenAI,
25 | model_id: 'gpt-3.5-turbo' as OpenAIModelId,
26 | model_name: 'GPT-3.5 Turbo' as OpenAIModelName,
27 | });
28 |
29 | const recentUsedModelsAtom = atomWithStorage('recentUsedModels', null);
30 |
31 | // settings management
32 | const themeAtom = atom(Theme.System);
33 |
34 | const preferencesAtom = atomWithStorage('preferences', {
35 | theme: Theme.System,
36 | // sendKey: 'enter',
37 | enterSend: true,
38 | // autoRead: false,
39 | // renderMode: 'markdown',
40 | useMarkdown: false,
41 | // autoScroll: false,
42 | });
43 |
44 | const advancedSettingsAtom = atomWithStorage('advancedSettings', {
45 | unifiedEndpoint: false,
46 | streamMessages: false,
47 | });
48 |
49 | const conversationSettingsAtom = atomWithStorage('conversationSettings', {
50 | numOfContext: 0,
51 | systemPrompt: null,
52 | });
53 |
54 | // conversation management
55 | const conversationsAtom = atomWithStorage('conversations', null);
56 |
57 | // search management
58 | const sameCitationAtom = atom('sameCitationId');
59 | const isProSearchAtom = atomWithStorage('pro', false);
60 |
61 | // export atoms
62 | export default {
63 | currentUseModelAtom,
64 | recentUsedModelsAtom,
65 | currentProviderSettingsAtom,
66 | customProviderModelAtom,
67 | currentSearchEngineSettingsAtom,
68 | currentSearchEngineAtom,
69 | themeAtom,
70 | preferencesAtom,
71 | advancedSettingsAtom,
72 | conversationSettingsAtom,
73 | conversationsAtom,
74 | sameCitationAtom,
75 | isProSearchAtom,
76 | };
77 |
--------------------------------------------------------------------------------
/hooks/theme.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import * as React from 'react';
4 | import { ThemeProvider as NextThemesProvider } from 'next-themes';
5 | import { type ThemeProviderProps } from 'next-themes/dist/types';
6 |
7 | export function ThemeProvider({ children, ...props }: Readonly) {
8 | return {children};
9 | }
10 |
--------------------------------------------------------------------------------
/hooks/window.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 |
3 | export function useMediaQuery(query: string) {
4 | const [value, setValue] = useState(false);
5 |
6 | useEffect(() => {
7 | function onChange(event: MediaQueryListEvent) {
8 | setValue(event.matches);
9 | }
10 |
11 | const result = matchMedia(query);
12 | result.addEventListener('change', onChange);
13 | setValue(result.matches);
14 |
15 | return () => result.removeEventListener('change', onChange);
16 | }, [query]);
17 |
18 | return value;
19 | }
20 |
--------------------------------------------------------------------------------
/i18n.ts:
--------------------------------------------------------------------------------
1 | import { notFound } from 'next/navigation';
2 | import { getRequestConfig } from 'next-intl/server';
3 |
4 | import { languageId } from '@/config/i18n';
5 |
6 | const locales = languageId;
7 |
8 | export default getRequestConfig(async ({ locale }) => {
9 | if (!locales.includes(locale as any)) notFound();
10 |
11 | return {
12 | messages: (await import(`@/locales/${locale}.json`)).default,
13 | };
14 | });
15 |
--------------------------------------------------------------------------------
/lib/provider/Anthropic.ts:
--------------------------------------------------------------------------------
1 | import { createAnthropic } from '@ai-sdk/anthropic';
2 |
3 | export const anthropic = createAnthropic({
4 | apiKey: process.env.ANTHROPIC_API_KEY ?? '',
5 | });
6 |
--------------------------------------------------------------------------------
/lib/provider/Google.ts:
--------------------------------------------------------------------------------
1 | import { createGoogleGenerativeAI } from '@ai-sdk/google';
2 |
3 | export const google = createGoogleGenerativeAI({
4 | apiKey: process.env.GOOGLE_API_KEY ?? '',
5 | // baseURL: process.env.OPENAI_API_ENDPOINT ?? '',
6 | });
7 |
--------------------------------------------------------------------------------
/lib/provider/OpenAI.ts:
--------------------------------------------------------------------------------
1 | import { createOpenAI } from '@ai-sdk/openai';
2 |
3 | export const openai = createOpenAI({
4 | apiKey: process.env.OPENAI_API_KEY ?? '',
5 | // baseURL: process.env.OPENAI_API_ENDPOINT ?? '',
6 | });
7 |
--------------------------------------------------------------------------------
/lib/search/challenger.tsx:
--------------------------------------------------------------------------------
1 | import { createOpenAI } from '@ai-sdk/openai';
2 | import { CoreMessage, generateObject } from 'ai';
3 | import { z } from 'zod';
4 |
5 | import { challengerPrompt } from '@/lib/prompt';
6 | import { SimpleModel } from '@/types/model';
7 | import { ProviderSetting } from '@/types/settings';
8 |
9 | export const challengerSchema = z.object({
10 | next: z.enum(['proceed', 'challenge']),
11 | });
12 |
13 | export const challenger = async (messages: CoreMessage[], model: SimpleModel, currentProviderSettings: ProviderSetting | null) => {
14 | 'use server';
15 |
16 | const openai = createOpenAI({
17 | apiKey: currentProviderSettings?.OpenAI?.apiKey ?? process.env.OPENAI_API_KEY ?? '',
18 | // baseURL: currentProviderSettings?.OpenAI?.endpoint ?? process.env.OPENAI_API_ENDPOINT ?? 'https://api.openai.com/v1',
19 | });
20 |
21 | return await generateObject({
22 | model: openai.chat(model.model_id ?? 'gpt-3.5-turbo'),
23 | system: challengerPrompt,
24 | messages,
25 | schema: challengerSchema,
26 | });
27 | };
28 |
--------------------------------------------------------------------------------
/lib/search/clarifier.tsx:
--------------------------------------------------------------------------------
1 | import { createOpenAI } from '@ai-sdk/openai';
2 | import { CoreMessage, streamObject } from 'ai';
3 | import { createStreamableUI, createStreamableValue } from 'ai/rsc';
4 | import { z } from 'zod';
5 |
6 | import { Clarifier } from '@/components/layout/search/block/clarifier';
7 | import { clarifierPrompt } from '@/lib/prompt';
8 | import { SimpleModel } from '@/types/model';
9 | import { TClarifier } from '@/types/search';
10 | import { ProviderSetting } from '@/types/settings';
11 |
12 | export const clarifierSchema = z.object({
13 | question: z.string().describe('The clarify question'),
14 | options: z.array(z.object({ value: z.string(), content: z.string() })).describe('The clarify options'),
15 | allowsInput: z.boolean().describe('Whether the clarify allows for input'),
16 | clarifyLabel: z.string().optional().describe('The clarify label for input'),
17 | clarifyPlaceholder: z.string().optional().describe('The clarify placeholder for input'),
18 | });
19 |
20 | export const clarifier = async (uiStream: ReturnType, messages: CoreMessage[], model: SimpleModel, currentProviderSettings: ProviderSetting | null) => {
21 | 'use server';
22 |
23 | const objectStream = createStreamableValue();
24 |
25 | uiStream.update();
26 |
27 | let clarifierResponse: TClarifier = {};
28 |
29 | const openai = createOpenAI({
30 | apiKey: currentProviderSettings?.OpenAI?.apiKey ?? process.env.OPENAI_API_KEY ?? '',
31 | // baseURL: currentProviderSettings?.OpenAI?.endpoint ?? process.env.OPENAI_API_ENDPOINT ?? 'https://api.openai.com/v1',
32 | });
33 |
34 | await streamObject({
35 | model: openai.chat(model.model_id ?? 'gpt-4-turbo'),
36 | system: clarifierPrompt,
37 | messages,
38 | schema: clarifierSchema,
39 | })
40 | .then(async (res) => {
41 | for await (const obj of res.partialObjectStream) {
42 | if (obj) {
43 | objectStream.update(obj);
44 | clarifierResponse = obj;
45 | }
46 | }
47 | })
48 | .finally(() => {
49 | objectStream.done();
50 | });
51 |
52 | return clarifierResponse;
53 | };
54 |
--------------------------------------------------------------------------------
/lib/search/illustrator.tsx:
--------------------------------------------------------------------------------
1 | import { createOpenAI } from '@ai-sdk/openai';
2 | import { CoreMessage, streamObject } from 'ai';
3 | import { createStreamableUI, createStreamableValue } from 'ai/rsc';
4 | import { z } from 'zod';
5 |
6 | import { Related } from '@/components/layout/search/block/related';
7 | import { illustratorPrompt } from '@/lib/prompt';
8 | import { SimpleModel } from '@/types/model';
9 | import { TIllustrator } from '@/types/search';
10 | import { ProviderSetting } from '@/types/settings';
11 |
12 | export const illustratorSchema = z.object({
13 | items: z.array(z.object({ query: z.string() })).length(3),
14 | });
15 |
16 | export const illustrator = async (uiStream: ReturnType, messages: CoreMessage[], model: SimpleModel, currentProviderSettings: ProviderSetting | null) => {
17 | 'use server';
18 |
19 | const objectStream = createStreamableValue();
20 |
21 | uiStream.append();
22 |
23 | const openai = createOpenAI({
24 | apiKey: currentProviderSettings?.OpenAI?.apiKey ?? process.env.OPENAI_API_KEY ?? '',
25 | // baseURL: currentProviderSettings?.OpenAI?.endpoint ?? process.env.OPENAI_API_ENDPOINT ?? 'https://api.openai.com/v1',
26 | });
27 |
28 | await streamObject({
29 | model: openai.chat(model.model_id ?? 'gpt-4-turbo'),
30 | system: illustratorPrompt,
31 | messages,
32 | schema: illustratorSchema,
33 | })
34 | .then(async (res) => {
35 | for await (const obj of res.partialObjectStream) {
36 | objectStream.update(obj);
37 | }
38 | })
39 | .finally(() => {
40 | objectStream.done();
41 | });
42 | };
43 |
--------------------------------------------------------------------------------
/lib/ui/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 |
--------------------------------------------------------------------------------
/locales/ja.json:
--------------------------------------------------------------------------------
1 | {
2 | "cookies": "クッキー",
3 | "good_afternoon": "こんにちは!",
4 | "good_morning": "おはようございます!",
5 | "good_evening": "こんばんは!",
6 | "input_box_placeholder": "ここにメッセージを入力...",
7 | "new_version_available": "新しいバージョンがあります",
8 | "open_source": "オープンソース",
9 | "open_source_message": "オープンソース、{license}に準拠",
10 | "made_by_author": "{author}制作",
11 | "privacy_policy": "プライバシーポリシー",
12 | "system": "システム",
13 | "dark": "ダークモード",
14 | "light": "ライトモード",
15 | "send_message": "メッセージを送信",
16 | "new_conversation": "新規会話",
17 | "share_conversation": "会話を共有",
18 | "recent_used": "最近使用したもの",
19 | "more_providers": "他のプロバイダ",
20 | "provider_not_configured": "プロバイダが設定されていません",
21 | "go_to_settings": "設定に移動",
22 | "preferences": "設定",
23 | "save": "保存",
24 | "provider_settings": "プロバイダ設定",
25 | "advanced": "詳細設定",
26 | "provider": "プロバイダ",
27 | "general": "一般",
28 | "settings_saved": "設定を保存しました",
29 | "upload_file": "ファイルをアップロード",
30 | "copy": "コピー",
31 | "resend": "再送信",
32 | "copied": "コピーしました",
33 | "conversation": "会話",
34 | "all_conversations_deleted": "すべての会話を削除しました!",
35 | "send_message_with_enter": "Enterキーでメッセージを送信",
36 | "message_to_speech": "メッセージを音声で再生",
37 | "render_message_with_markdown": "Markdownを使ってメッセージを表示",
38 | "auto_scroll_to_end_of_conversation": "会話の最後までスクロールする",
39 | "conversation_records": "会話の履歴",
40 | "export": "エクスポート",
41 | "delete": "削除",
42 | "use_unified_endpoint": "統一エンドポイントを使用",
43 | "stream_messages": "メッセージをストリーミング",
44 | "may_not_works_for_some_models": "一部のモデルでは使用できない可能性があります",
45 | "number_of_context": "コンテキストの数",
46 | "no_limit": "制限なし",
47 | "default_system_prompt": "デフォルトのシステムプロンプト",
48 | "system_prompt": "システムプロンプト",
49 | "custom_system_prompt": "カスタムシステムプロンプト",
50 | "based_provider": "ベースのプロバイダ",
51 | "custom": "カスタム",
52 | "saved_custom_model": "保存されたカスタムモデル",
53 | "add_mode_custom_provider": "他のカスタムプロバイダを追加",
54 | "image": "画像",
55 | "custom_api_key": "カスタムAPIキー",
56 | "custom_api_endpoint": "カスタムAPIエンドポイント",
57 | "custom_model": "カスタムモデル",
58 | "conversation_not_found": "会話が見つかりません",
59 | "stop_message": "メッセージを停止",
60 | "enter_something_to_send": "送信するメッセージを入力",
61 | "system_prompt_may_not_works_for_some_models": "システムプロンプトは一部のモデルで使用できない可能性があります",
62 | "ask_anything_here": "ここで何でも質問してください",
63 | "send_question": "質問を送信",
64 | "chat": "チャット",
65 | "search": "検索",
66 | "confirm": "確認",
67 | "ask_follow_up_question": "フォローアップの質問をする",
68 | "search_slogan": "あなたの質問、ここで終わります。",
69 | "answer": "回答",
70 | "images": "画像",
71 | "related": "関連情報",
72 | "sources": "情報源",
73 | "no_idea": "わかりません",
74 | "your_question": "あなたの質問",
75 | "your_additions": "あなたの補足",
76 | "try_asking": "質問してみてください",
77 | "searching": "検索中",
78 | "searching_slogan": "システムはあなたの質問を理解しようと努めており、できるだけ早く回答します。",
79 | "error": "エラー",
80 | "show_all_resources": "すべてのリソースを表示",
81 | "show_less_resources": "リソースを少なく表示",
82 | "show_more_images": "より多くの画像を表示",
83 | "show_less_images": "画像を少なく表示",
84 | "no_image_found": "画像が見つかりませんでした",
85 | "pro": "プロ版",
86 | "enhanced_search_ability": "強化された検索機能",
87 | "provider_not_supported": "そのプロバイダはまだサポートされていません。",
88 | "search_engine_not_configured": "検索エンジンが設定されていません",
89 | "search_engine_configured_globally": "グローバルに設定された検索エンジン",
90 | "provider_configured_globally": "グローバルに構成されたプロバイダー",
91 | "focus_on": "焦点を当てる",
92 | "and": "そして",
93 | "docs": "ドキュメント",
94 | "previous_7_days": "過去7日間",
95 | "today": "今日",
96 | "yesterday": "昨日",
97 | "specific_information_not_sure": "質問に関連する具体的な情報がわからない場合は、",
98 | "no_conversations": "会話なし",
99 | "clarifier": "清澄剤"
100 | }
101 |
--------------------------------------------------------------------------------
/locales/ko.json:
--------------------------------------------------------------------------------
1 | {
2 | "cookies": "쿠키",
3 | "good_afternoon": "안녕하세요!",
4 | "good_morning": "좋은 아침입니다!",
5 | "good_evening": "안녕하세요!",
6 | "input_box_placeholder": "메시지를 여기에 입력하세요...",
7 | "new_version_available": "새 버전이 출시되었습니다",
8 | "open_source": "오픈 소스",
9 | "open_source_message": "오픈 소스, {license} 준수",
10 | "made_by_author": "{author}에 의해 제작됨",
11 | "privacy_policy": "개인정보 보호정책",
12 | "system": "시스템",
13 | "dark": "다크 모드",
14 | "light": "라이트 모드",
15 | "send_message": "메시지 보내기",
16 | "new_conversation": "새 대화",
17 | "share_conversation": "대화 공유",
18 | "recent_used": "최근 사용된 것",
19 | "more_providers": "더 많은 공급자",
20 | "provider_not_configured": "공급자가 구성되지 않음",
21 | "go_to_settings": "설정으로 이동",
22 | "preferences": "환경설정",
23 | "save": "저장",
24 | "provider_settings": "공급자 설정",
25 | "advanced": "고급",
26 | "provider": "공급자",
27 | "general": "일반",
28 | "settings_saved": "설정이 저장되었습니다",
29 | "upload_file": "파일 업로드",
30 | "copy": "복사",
31 | "resend": "다시 보내기",
32 | "copied": "복사됨",
33 | "conversation": "대화",
34 | "all_conversations_deleted": "모든 대화가 삭제되었습니다!",
35 | "send_message_with_enter": "Enter 키로 메시지 보내기",
36 | "message_to_speech": "메시지를 음성으로 전환",
37 | "render_message_with_markdown": "Markdown으로 메시지 렌더링",
38 | "auto_scroll_to_end_of_conversation": "대화 끝까지 자동 스크롤",
39 | "conversation_records": "대화 기록",
40 | "export": "내보내기",
41 | "delete": "삭제",
42 | "use_unified_endpoint": "통합 엔드포인트 사용",
43 | "stream_messages": "메시지 스트리밍",
44 | "may_not_works_for_some_models": "일부 모델에서는 작동하지 않을 수 있습니다",
45 | "number_of_context": "컨텍스트 수",
46 | "no_limit": "제한 없음",
47 | "default_system_prompt": "기본 시스템 프롬프트",
48 | "system_prompt": "시스템 프롬프트",
49 | "custom_system_prompt": "사용자 정의 시스템 프롬프트",
50 | "based_provider": "기반 공급자",
51 | "custom": "사용자 정의",
52 | "saved_custom_model": "저장된 사용자 정의 모델",
53 | "add_mode_custom_provider": "더 많은 사용자 정의 공급자 추가",
54 | "image": "이미지",
55 | "custom_api_key": "사용자 정의 API 키",
56 | "custom_api_endpoint": "사용자 정의 API 엔드포인트",
57 | "custom_model": "사용자 정의 모델",
58 | "conversation_not_found": "대화를 찾을 수 없습니다",
59 | "stop_message": "메시지 중지",
60 | "enter_something_to_send": "보낼 내용을 입력하세요",
61 | "system_prompt_may_not_works_for_some_models": "시스템 프롬프트가 일부 모델에서 작동하지 않을 수 있습니다",
62 | "ask_anything_here": "여기서 무엇이든 물어보세요",
63 | "send_question": "질문 보내기",
64 | "chat": "채팅",
65 | "search": "검색",
66 | "confirm": "확인",
67 | "ask_follow_up_question": "후속 질문하기",
68 | "search_slogan": "당신의 질문, 여기서 끝납니다.",
69 | "answer": "답변",
70 | "images": "이미지",
71 | "related": "관련",
72 | "sources": "출처",
73 | "no_idea": "모르겠습니다",
74 | "your_question": "당신의 질문",
75 | "your_additions": "당신의 추가 사항",
76 | "try_asking": "질문해 보세요",
77 | "searching": "검색 중",
78 | "searching_slogan": "시스템이 귀하의 질문을 이해하려 노력하고 있으며 가능한 빨리 답변드리겠습니다.",
79 | "error": "오류",
80 | "show_all_resources": "모든 리소스 표시",
81 | "show_less_resources": "리소스 적게 표시",
82 | "show_more_images": "더 많은 이미지 보기",
83 | "show_less_images": "이미지 적게 표시",
84 | "no_image_found": "이미지를 찾을 수 없습니다",
85 | "pro": "프로",
86 | "enhanced_search_ability": "향상된 검색 기능",
87 | "provider_not_supported": "아직 해당 공급자를 지원하지 않습니다.",
88 | "search_engine_not_configured": "검색 엔진이 구성되지 않았습니다.",
89 | "search_engine_configured_globally": "전역적으로 구성된 검색 엔진",
90 | "provider_configured_globally": "전역적으로 구성된 공급자",
91 | "focus_on": "집중하다",
92 | "and": "그리고",
93 | "docs": "문서",
94 | "previous_7_days": "지난 7일",
95 | "today": "오늘",
96 | "yesterday": "어제",
97 | "specific_information_not_sure": "귀하의 질문과 관련된 구체적인 정보가 확실하지 않습니까?",
98 | "no_conversations": "대화 없음",
99 | "clarifier": "청징제"
100 | }
101 |
--------------------------------------------------------------------------------
/locales/zh-CN.json:
--------------------------------------------------------------------------------
1 | {
2 | "cookies": "饼干",
3 | "good_afternoon": "下午好!",
4 | "good_morning": "早上好!",
5 | "good_evening": "晚上好!",
6 | "input_box_placeholder": "在此输入您的消息...",
7 | "new_version_available": "有新版本可用",
8 | "open_source": "开源",
9 | "open_source_message": "开源, 符合{license}",
10 | "made_by_author": "由{author}制作",
11 | "privacy_policy": "隐私政策",
12 | "system": "系统",
13 | "dark": "深色",
14 | "light": "浅色",
15 | "send_message": "发送消息",
16 | "new_conversation": "新对话",
17 | "share_conversation": "分享对话",
18 | "recent_used": "最近使用",
19 | "more_providers": "更多提供商",
20 | "provider_not_configured": "提供商未配置",
21 | "go_to_settings": "进入设置",
22 | "preferences": "偏好设置",
23 | "save": "保存",
24 | "provider_settings": "提供商设置",
25 | "advanced": "高级",
26 | "provider": "提供商",
27 | "general": "常规",
28 | "settings_saved": "设置已保存",
29 | "upload_file": "上传文件",
30 | "copy": "复制",
31 | "resend": "重发",
32 | "copied": "已复制",
33 | "conversation": "对话",
34 | "all_conversations_deleted": "所有对话已删除!",
35 | "send_message_with_enter": "使用回车发送消息",
36 | "message_to_speech": "消息转语音",
37 | "render_message_with_markdown": "使用Markdown渲染消息",
38 | "auto_scroll_to_end_of_conversation": "自动滚动到对话结尾",
39 | "conversation_records": "对话记录",
40 | "export": "导出",
41 | "delete": "删除",
42 | "use_unified_endpoint": "使用统一端点",
43 | "stream_messages": "流式消息",
44 | "may_not_works_for_some_models": "部分模型可能无法使用",
45 | "number_of_context": "上下文数量",
46 | "no_limit": "无限制",
47 | "default_system_prompt": "默认系统提示",
48 | "system_prompt": "系统提示",
49 | "custom_system_prompt": "自定义系统提示",
50 | "based_provider": "基于提供商",
51 | "custom": "自定义",
52 | "saved_custom_model": "已保存的自定义模型",
53 | "add_mode_custom_provider": "添加更多自定义提供商",
54 | "image": "图像",
55 | "custom_api_key": "自定义API密钥",
56 | "custom_api_endpoint": "自定义API端点",
57 | "custom_model": "自定义模型",
58 | "conversation_not_found": "找不到对话",
59 | "stop_message": "停止消息",
60 | "enter_something_to_send": "输入要发送的内容",
61 | "system_prompt_may_not_works_for_some_models": "系统提示可能无法用于某些模型",
62 | "ask_anything_here": "在这里问任何问题",
63 | "send_question": "发送问题",
64 | "chat": "聊天",
65 | "search": "搜索",
66 | "confirm": "确认",
67 | "ask_follow_up_question": "提出后续问题",
68 | "search_slogan": "您的问题,到此为止。",
69 | "answer": "答案",
70 | "images": "图像",
71 | "related": "相关",
72 | "sources": "来源",
73 | "no_idea": "无从知晓",
74 | "your_question": "您的问题",
75 | "your_additions": "您的补充",
76 | "try_asking": "试着问问",
77 | "searching": "正在搜索",
78 | "searching_slogan": "系统正在努力理解您的问题,会尽快给您回复。",
79 | "error": "错误",
80 | "show_all_resources": "显示所有资源",
81 | "show_less_resources": "显示较少资源",
82 | "show_more_images": "查看更多图像",
83 | "show_less_images": "显示较少图像",
84 | "no_image_found": "未找到图像",
85 | "pro": "专业版",
86 | "enhanced_search_ability": "增强的搜索能力",
87 | "provider_not_supported": "尚未支持该提供商。",
88 | "search_engine_not_configured": "搜索引擎未配置",
89 | "search_engine_configured_globally": "全局配置的搜索引擎",
90 | "provider_configured_globally": "全局配置的提供者",
91 | "focus_on": "专注于",
92 | "and": "和",
93 | "docs": "文档",
94 | "previous_7_days": "前 7 天",
95 | "today": "今天",
96 | "yesterday": "昨天",
97 | "specific_information_not_sure": "不确定与您的问题相关的具体信息?",
98 | "no_conversations": "没有对话",
99 | "clarifier": "澄清器"
100 | }
101 |
--------------------------------------------------------------------------------
/locales/zh-HK.json:
--------------------------------------------------------------------------------
1 | {
2 | "cookies": "曲奇餅",
3 | "good_afternoon": "午安!",
4 | "good_morning": "早晨!",
5 | "good_evening": "晚安!",
6 | "input_box_placeholder": "在此輸入您的訊息...",
7 | "new_version_available": "有新版本可用",
8 | "open_source": "開源",
9 | "open_source_message": "開源, 符合{license}",
10 | "made_by_author": "由{author}製作",
11 | "privacy_policy": "私隱政策",
12 | "system": "系統",
13 | "dark": "深色",
14 | "light": "淺色",
15 | "send_message": "發送訊息",
16 | "new_conversation": "新對話",
17 | "share_conversation": "分享對話",
18 | "recent_used": "最近使用",
19 | "more_providers": "更多供應商",
20 | "provider_not_configured": "供應商未配置",
21 | "go_to_settings": "前往設置",
22 | "preferences": "偏好設置",
23 | "save": "儲存",
24 | "provider_settings": "供應商設置",
25 | "advanced": "進階",
26 | "provider": "供應商",
27 | "general": "一般",
28 | "settings_saved": "設置已儲存",
29 | "upload_file": "上傳檔案",
30 | "copy": "複製",
31 | "resend": "重發",
32 | "copied": "已複製",
33 | "conversation": "對話",
34 | "all_conversations_deleted": "所有對話已刪除!",
35 | "send_message_with_enter": "使用Enter鍵發送訊息",
36 | "message_to_speech": "訊息轉語音",
37 | "render_message_with_markdown": "使用Markdown渲染訊息",
38 | "auto_scroll_to_end_of_conversation": "自動捲動至對話末尾",
39 | "conversation_records": "對話記錄",
40 | "export": "匯出",
41 | "delete": "刪除",
42 | "use_unified_endpoint": "使用統一端點",
43 | "stream_messages": "串流訊息",
44 | "may_not_works_for_some_models": "部分模型可能無法使用",
45 | "number_of_context": "上下文數量",
46 | "no_limit": "無限制",
47 | "default_system_prompt": "預設系統提示",
48 | "system_prompt": "系統提示",
49 | "custom_system_prompt": "自訂系統提示",
50 | "based_provider": "基於供應商",
51 | "custom": "自訂",
52 | "saved_custom_model": "已儲存的自訂模型",
53 | "add_mode_custom_provider": "添加更多自訂供應商",
54 | "image": "圖像",
55 | "custom_api_key": "自訂API密鑰",
56 | "custom_api_endpoint": "自訂API端點",
57 | "custom_model": "自訂模型",
58 | "conversation_not_found": "找不到對話",
59 | "stop_message": "停止訊息",
60 | "enter_something_to_send": "輸入要發送的內容",
61 | "system_prompt_may_not_works_for_some_models": "系統提示可能無法用於某些模型",
62 | "ask_anything_here": "在這裡問任何問題",
63 | "send_question": "發送問題",
64 | "chat": "聊天",
65 | "search": "搜尋",
66 | "confirm": "確認",
67 | "ask_follow_up_question": "提出後續問題",
68 | "search_slogan": "您的問題,到此為止。",
69 | "answer": "答案",
70 | "images": "圖像",
71 | "related": "相關",
72 | "sources": "來源",
73 | "no_idea": "無從知曉",
74 | "your_question": "您的問題",
75 | "your_additions": "您的補充",
76 | "try_asking": "試著問問",
77 | "searching": "正在搜尋",
78 | "searching_slogan": "系統正在努力理解您的問題,會盡快給您回覆。",
79 | "error": "錯誤",
80 | "show_all_resources": "顯示所有資源",
81 | "show_less_resources": "顯示較少資源",
82 | "show_more_images": "查看更多圖像",
83 | "show_less_images": "顯示較少圖像",
84 | "no_image_found": "未找到圖像",
85 | "pro": "專業版",
86 | "enhanced_search_ability": "增強的搜尋能力",
87 | "provider_not_supported": "尚未支持該供應商。",
88 | "search_engine_not_configured": "搜尋引擎未配置",
89 | "search_engine_configured_globally": "全域配置的搜尋引擎",
90 | "provider_configured_globally": "全域配置的提供者",
91 | "focus_on": "專注於",
92 | "and": "和",
93 | "docs": "文件",
94 | "previous_7_days": "前 7 天",
95 | "today": "今天",
96 | "yesterday": "昨天",
97 | "specific_information_not_sure": "不確定與您的問題相關的具體資訊?",
98 | "no_conversations": "沒有對話",
99 | "clarifier": "澄清器"
100 | }
101 |
--------------------------------------------------------------------------------
/locales/zh-TW.json:
--------------------------------------------------------------------------------
1 | {
2 | "cookies": "餅乾",
3 | "good_afternoon": "午安!",
4 | "good_morning": "早安!",
5 | "good_evening": "晚安!",
6 | "input_box_placeholder": "在此輸入您的訊息...",
7 | "new_version_available": "有新版本可用",
8 | "open_source": "開源",
9 | "open_source_message": "開源, 符合{license}",
10 | "made_by_author": "由{author}製作",
11 | "privacy_policy": "隱私權政策",
12 | "system": "系統",
13 | "dark": "深色",
14 | "light": "淺色",
15 | "send_message": "傳送訊息",
16 | "new_conversation": "新對話",
17 | "share_conversation": "分享對話",
18 | "recent_used": "最近使用",
19 | "more_providers": "更多提供者",
20 | "provider_not_configured": "提供者未設定",
21 | "go_to_settings": "前往設定",
22 | "preferences": "偏好設定",
23 | "save": "儲存",
24 | "provider_settings": "提供者設定",
25 | "advanced": "進階",
26 | "provider": "提供者",
27 | "general": "一般",
28 | "settings_saved": "設定已儲存",
29 | "upload_file": "上傳檔案",
30 | "copy": "複製",
31 | "resend": "重新傳送",
32 | "copied": "已複製",
33 | "conversation": "對話",
34 | "all_conversations_deleted": "所有對話已刪除!",
35 | "send_message_with_enter": "使用Enter鍵傳送訊息",
36 | "message_to_speech": "訊息轉語音",
37 | "render_message_with_markdown": "使用Markdown渲染訊息",
38 | "auto_scroll_to_end_of_conversation": "自動捲動至對話結尾",
39 | "conversation_records": "對話記錄",
40 | "export": "匯出",
41 | "delete": "刪除",
42 | "use_unified_endpoint": "使用統一端點",
43 | "stream_messages": "串流訊息",
44 | "may_not_works_for_some_models": "部分模型可能無法使用",
45 | "number_of_context": "上下文數量",
46 | "no_limit": "無限制",
47 | "default_system_prompt": "預設系統提示",
48 | "system_prompt": "系統提示",
49 | "custom_system_prompt": "自訂系統提示",
50 | "based_provider": "基於提供者",
51 | "custom": "自訂",
52 | "saved_custom_model": "已儲存的自訂模型",
53 | "add_mode_custom_provider": "新增更多自訂提供者",
54 | "image": "圖像",
55 | "custom_api_key": "自訂API金鑰",
56 | "custom_api_endpoint": "自訂API端點",
57 | "custom_model": "自訂模型",
58 | "conversation_not_found": "找不到對話",
59 | "stop_message": "停止訊息",
60 | "enter_something_to_send": "輸入要傳送的內容",
61 | "system_prompt_may_not_works_for_some_models": "系統提示可能無法用於某些模型",
62 | "ask_anything_here": "在這裡問任何問題",
63 | "send_question": "傳送問題",
64 | "chat": "聊天",
65 | "search": "搜尋",
66 | "confirm": "確認",
67 | "ask_follow_up_question": "提出後續問題",
68 | "search_slogan": "您的問題,到此為止。",
69 | "answer": "答案",
70 | "images": "圖像",
71 | "related": "相關",
72 | "sources": "來源",
73 | "no_idea": "無從知曉",
74 | "your_question": "您的問題",
75 | "your_additions": "您的補充",
76 | "try_asking": "試著問問",
77 | "searching": "正在搜尋",
78 | "searching_slogan": "系統正在努力理解您的問題,會盡快給您回覆。",
79 | "error": "錯誤",
80 | "show_all_resources": "顯示所有資源",
81 | "show_less_resources": "顯示較少資源",
82 | "show_more_images": "查看更多圖像",
83 | "show_less_images": "顯示較少圖像",
84 | "no_image_found": "未找到圖像",
85 | "pro": "專業版",
86 | "enhanced_search_ability": "增強的搜尋能力",
87 | "provider_not_supported": "尚未支援該提供者。",
88 | "search_engine_not_configured": "搜尋引擎未配置",
89 | "search_engine_configured_globally": "全域配置的搜尋引擎",
90 | "provider_configured_globally": "全域配置的提供者",
91 | "focus_on": "專注於",
92 | "and": "和",
93 | "docs": "文件",
94 | "previous_7_days": "前 7 天",
95 | "today": "今天",
96 | "yesterday": "昨天",
97 | "specific_information_not_sure": "不確定與您的問題相關的具體資訊?",
98 | "no_conversations": "沒有對話",
99 | "clarifier": "澄清器"
100 | }
101 |
--------------------------------------------------------------------------------
/middleware.ts:
--------------------------------------------------------------------------------
1 | import createMiddleware from 'next-intl/middleware';
2 |
3 | import { languageId } from '@/config/i18n';
4 |
5 | export default createMiddleware({
6 | locales: languageId,
7 |
8 | defaultLocale: 'en',
9 |
10 | localePrefix: 'never',
11 | });
12 |
13 | export const config = {
14 | matcher: ['/', '/search', '/(de|en|es|fr|it|ja|ko|nl|pt|ru|zh-CN|zh-HK|zh-TW)/:path*'],
15 | };
16 |
--------------------------------------------------------------------------------
/next.config.mjs:
--------------------------------------------------------------------------------
1 | import withPWAInit from '@ducanh2912/next-pwa';
2 | import createNextIntlPlugin from 'next-intl/plugin';
3 |
4 | const withNextIntl = createNextIntlPlugin();
5 |
6 | const withPWA = withPWAInit({
7 | dest: 'public',
8 | });
9 |
10 | /** @type {import('next').NextConfig} */
11 | const nextConfig = {};
12 |
13 | export default withPWA(withNextIntl(nextConfig));
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chat-chat",
3 | "version": "0.1.9",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint",
10 | "eslint:check": "eslint --ext .ts,.tsx .",
11 | "eslint:fix": "eslint --ext .ts,.tsx --fix ."
12 | },
13 | "dependencies": {
14 | "@ai-sdk/anthropic": "^0.0.17",
15 | "@ai-sdk/google": "^0.0.17",
16 | "@ai-sdk/openai": "^0.0.17",
17 | "@anthropic-ai/sdk": "^0.21.1",
18 | "@aws-sdk/client-bedrock-runtime": "^3.556.0",
19 | "@azure/openai": "^1.0.0-beta.12",
20 | "@ducanh2912/next-pwa": "^10.2.6",
21 | "@google/generative-ai": "^0.7.1",
22 | "@huggingface/inference": "^2.6.7",
23 | "@mistralai/mistralai": "^0.1.3",
24 | "@radix-ui/react-avatar": "^1.0.4",
25 | "@radix-ui/react-checkbox": "^1.0.4",
26 | "@radix-ui/react-dialog": "^1.0.5",
27 | "@radix-ui/react-dropdown-menu": "^2.0.6",
28 | "@radix-ui/react-label": "^2.0.2",
29 | "@radix-ui/react-select": "^2.0.0",
30 | "@radix-ui/react-slot": "^1.0.2",
31 | "@radix-ui/react-switch": "^1.0.3",
32 | "@radix-ui/react-tabs": "^1.0.4",
33 | "@tippyjs/react": "^4.2.6",
34 | "@types/file-saver": "^2.0.7",
35 | "@vercel/analytics": "^1.3.1",
36 | "@vercel/speed-insights": "^1.0.11",
37 | "ai": "^3.1.17",
38 | "class-variance-authority": "^0.7.0",
39 | "clsx": "^2.1.0",
40 | "cohere-ai": "^7.9.5",
41 | "file-saver": "^2.0.5",
42 | "groq-sdk": "^0.3.2",
43 | "jotai": "^2.8.2",
44 | "lucide-react": "^0.363.0",
45 | "next": "14.2.3",
46 | "next-intl": "^3.14.1",
47 | "next-themes": "^0.3.0",
48 | "openai": "^4.38.2",
49 | "react": "^18.2.0",
50 | "react-dom": "^18.2.0",
51 | "react-icons": "^5.2.1",
52 | "react-markdown": "^9.0.1",
53 | "react-mathjax": "^1.0.1",
54 | "react-spring-lightbox": "^1.8.0",
55 | "rehype-katex": "^7.0.0",
56 | "remark-gfm": "^4.0.0",
57 | "remark-math": "^6.0.0",
58 | "sonner": "^1.4.41",
59 | "tailwind-merge": "^2.2.2",
60 | "tailwindcss-animate": "^1.0.7",
61 | "vaul": "^0.9.0",
62 | "zod": "^3.23.8"
63 | },
64 | "devDependencies": {
65 | "@types/node": "^20.12.7",
66 | "@types/react": "^18.2.79",
67 | "@types/react-dom": "^18.2.25",
68 | "@typescript-eslint/parser": "^7.5.0",
69 | "autoprefixer": "^10.4.19",
70 | "eslint": "^8.57.0",
71 | "eslint-config-next": "14.2.2",
72 | "eslint-plugin-simple-import-sort": "^12.1.0",
73 | "eslint-plugin-tailwindcss": "^3.17.0",
74 | "eslint-plugin-unused-imports": "^3.2.0",
75 | "postcss": "^8.4.38",
76 | "prettier": "^3.2.5",
77 | "prettier-plugin-tailwindcss": "^0.5.14",
78 | "tailwindcss": "^3.4.3",
79 | "typescript": "^5.4.5",
80 | "webpack": "^5.91.0"
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: ['prettier-plugin-tailwindcss'],
3 | tabWidth: 4,
4 | printWidth: 200,
5 | semi: true,
6 | singleQuote: true,
7 | jsxSingleQuote: true,
8 | vueIndentScriptAndStyle: true,
9 | arrowParens: 'always',
10 | trailingComma: 'es5',
11 | useTabs: false,
12 | };
13 |
--------------------------------------------------------------------------------
/public/OpenAI.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/okisdev/ChatChat/12674287a96b32ee4f04194b1e44944cf733a084/public/favicon.ico
--------------------------------------------------------------------------------
/public/hero.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/okisdev/ChatChat/12674287a96b32ee4f04194b1e44944cf733a084/public/hero.png
--------------------------------------------------------------------------------
/public/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/public/icons/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/okisdev/ChatChat/12674287a96b32ee4f04194b1e44944cf733a084/public/icons/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/icons/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/okisdev/ChatChat/12674287a96b32ee4f04194b1e44944cf733a084/public/icons/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/icons/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/okisdev/ChatChat/12674287a96b32ee4f04194b1e44944cf733a084/public/icons/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/icons/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #da532c
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/public/icons/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/okisdev/ChatChat/12674287a96b32ee4f04194b1e44944cf733a084/public/icons/favicon-16x16.png
--------------------------------------------------------------------------------
/public/icons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/okisdev/ChatChat/12674287a96b32ee4f04194b1e44944cf733a084/public/icons/favicon-32x32.png
--------------------------------------------------------------------------------
/public/icons/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/okisdev/ChatChat/12674287a96b32ee4f04194b1e44944cf733a084/public/icons/favicon.ico
--------------------------------------------------------------------------------
/public/icons/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/okisdev/ChatChat/12674287a96b32ee4f04194b1e44944cf733a084/public/icons/mstile-150x150.png
--------------------------------------------------------------------------------
/public/img/Amazon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/okisdev/ChatChat/12674287a96b32ee4f04194b1e44944cf733a084/public/img/Amazon.png
--------------------------------------------------------------------------------
/public/img/Anthropic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/okisdev/ChatChat/12674287a96b32ee4f04194b1e44944cf733a084/public/img/Anthropic.png
--------------------------------------------------------------------------------
/public/img/Azure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/okisdev/ChatChat/12674287a96b32ee4f04194b1e44944cf733a084/public/img/Azure.png
--------------------------------------------------------------------------------
/public/img/Cohere.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/okisdev/ChatChat/12674287a96b32ee4f04194b1e44944cf733a084/public/img/Cohere.png
--------------------------------------------------------------------------------
/public/img/Custom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/okisdev/ChatChat/12674287a96b32ee4f04194b1e44944cf733a084/public/img/Custom.png
--------------------------------------------------------------------------------
/public/img/Fireworks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/okisdev/ChatChat/12674287a96b32ee4f04194b1e44944cf733a084/public/img/Fireworks.png
--------------------------------------------------------------------------------
/public/img/Google.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/okisdev/ChatChat/12674287a96b32ee4f04194b1e44944cf733a084/public/img/Google.png
--------------------------------------------------------------------------------
/public/img/Groq.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/okisdev/ChatChat/12674287a96b32ee4f04194b1e44944cf733a084/public/img/Groq.png
--------------------------------------------------------------------------------
/public/img/HuggingFace.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/okisdev/ChatChat/12674287a96b32ee4f04194b1e44944cf733a084/public/img/HuggingFace.png
--------------------------------------------------------------------------------
/public/img/Mistral.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/okisdev/ChatChat/12674287a96b32ee4f04194b1e44944cf733a084/public/img/Mistral.png
--------------------------------------------------------------------------------
/public/img/OpenAI.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/okisdev/ChatChat/12674287a96b32ee4f04194b1e44944cf733a084/public/img/OpenAI.png
--------------------------------------------------------------------------------
/public/img/Perplexity.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/okisdev/ChatChat/12674287a96b32ee4f04194b1e44944cf733a084/public/img/Perplexity.png
--------------------------------------------------------------------------------
/public/img/Replicate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/okisdev/ChatChat/12674287a96b32ee4f04194b1e44944cf733a084/public/img/Replicate.png
--------------------------------------------------------------------------------
/public/img/Tavily.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/okisdev/ChatChat/12674287a96b32ee4f04194b1e44944cf733a084/public/img/Tavily.png
--------------------------------------------------------------------------------
/public/img/Team.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/okisdev/ChatChat/12674287a96b32ee4f04194b1e44944cf733a084/public/img/Team.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Chat Chat",
3 | "short_name": "Chat Chat",
4 | "icons": [
5 | {
6 | "src": "/icons/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png",
9 | "purpose": "any maskable"
10 | },
11 | {
12 | "src": "/icons/android-chrome-512x512.png",
13 | "sizes": "512x512",
14 | "type": "image/png"
15 | }
16 | ],
17 | "theme_color": "#FFFFFF",
18 | "background_color": "#FFFFFF",
19 | "start_url": "/",
20 | "display": "standalone",
21 | "orientation": "portrait"
22 | }
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "config:base"
4 | ]
5 | }
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | ::-webkit-scrollbar {
6 | display: none;
7 | }
8 |
9 | @layer base {
10 | :root {
11 | --background: 0 0% 100%;
12 | --foreground: 0 0% 3.9%;
13 |
14 | --card: 0 0% 100%;
15 | --card-foreground: 0 0% 3.9%;
16 |
17 | --popover: 0 0% 100%;
18 | --popover-foreground: 0 0% 3.9%;
19 |
20 | --primary: 0 0% 9%;
21 | --primary-foreground: 0 0% 98%;
22 |
23 | --secondary: 0 0% 96.1%;
24 | --secondary-foreground: 0 0% 9%;
25 |
26 | --muted: 0 0% 96.1%;
27 | --muted-foreground: 0 0% 45.1%;
28 |
29 | --accent: 0 0% 96.1%;
30 | --accent-foreground: 0 0% 9%;
31 |
32 | --destructive: 0 84.2% 60.2%;
33 | --destructive-foreground: 0 0% 98%;
34 |
35 | --border: 0 0% 89.8%;
36 | --input: 0 0% 89.8%;
37 | --ring: 0 0% 3.9%;
38 |
39 | --radius: 0.5rem;
40 | }
41 |
42 | .dark {
43 | --background: 0 0% 3.9%;
44 | --foreground: 0 0% 98%;
45 |
46 | --card: 0 0% 3.9%;
47 | --card-foreground: 0 0% 98%;
48 |
49 | --popover: 0 0% 3.9%;
50 | --popover-foreground: 0 0% 98%;
51 |
52 | --primary: 0 0% 98%;
53 | --primary-foreground: 0 0% 9%;
54 |
55 | --secondary: 0 0% 14.9%;
56 | --secondary-foreground: 0 0% 98%;
57 |
58 | --muted: 0 0% 14.9%;
59 | --muted-foreground: 0 0% 63.9%;
60 |
61 | --accent: 0 0% 14.9%;
62 | --accent-foreground: 0 0% 98%;
63 |
64 | --destructive: 0 62.8% 30.6%;
65 | --destructive-foreground: 0 0% 98%;
66 |
67 | --border: 0 0% 14.9%;
68 | --input: 0 0% 14.9%;
69 | --ring: 0 0% 83.1%;
70 | }
71 | }
72 |
73 | @layer base {
74 | * {
75 | @apply border-border;
76 | }
77 | body {
78 | @apply bg-background text-foreground;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'tailwindcss';
2 |
3 | const config = {
4 | darkMode: ['class'],
5 | content: ['./pages/**/*.{ts,tsx}', './components/**/*.{ts,tsx}', './app/**/*.{ts,tsx}', './src/**/*.{ts,tsx}'],
6 | prefix: '',
7 | theme: {
8 | container: {
9 | center: true,
10 | padding: '2rem',
11 | screens: {
12 | '2xl': '1400px',
13 | },
14 | },
15 | extend: {
16 | colors: {
17 | border: 'hsl(var(--border))',
18 | input: 'hsl(var(--input))',
19 | ring: 'hsl(var(--ring))',
20 | background: 'hsl(var(--background))',
21 | foreground: 'hsl(var(--foreground))',
22 | primary: {
23 | DEFAULT: 'hsl(var(--primary))',
24 | foreground: 'hsl(var(--primary-foreground))',
25 | },
26 | secondary: {
27 | DEFAULT: 'hsl(var(--secondary))',
28 | foreground: 'hsl(var(--secondary-foreground))',
29 | },
30 | destructive: {
31 | DEFAULT: 'hsl(var(--destructive))',
32 | foreground: 'hsl(var(--destructive-foreground))',
33 | },
34 | muted: {
35 | DEFAULT: 'hsl(var(--muted))',
36 | foreground: 'hsl(var(--muted-foreground))',
37 | },
38 | accent: {
39 | DEFAULT: 'hsl(var(--accent))',
40 | foreground: 'hsl(var(--accent-foreground))',
41 | },
42 | popover: {
43 | DEFAULT: 'hsl(var(--popover))',
44 | foreground: 'hsl(var(--popover-foreground))',
45 | },
46 | card: {
47 | DEFAULT: 'hsl(var(--card))',
48 | foreground: 'hsl(var(--card-foreground))',
49 | },
50 | },
51 | borderRadius: {
52 | lg: 'var(--radius)',
53 | md: 'calc(var(--radius) - 2px)',
54 | sm: 'calc(var(--radius) - 4px)',
55 | },
56 | keyframes: {
57 | 'accordion-down': {
58 | from: { height: '0' },
59 | to: { height: 'var(--radix-accordion-content-height)' },
60 | },
61 | 'accordion-up': {
62 | from: { height: 'var(--radix-accordion-content-height)' },
63 | to: { height: '0' },
64 | },
65 | },
66 | animation: {
67 | 'accordion-down': 'accordion-down 0.2s ease-out',
68 | 'accordion-up': 'accordion-up 0.2s ease-out',
69 | },
70 | },
71 | },
72 | plugins: [require('tailwindcss-animate')],
73 | } satisfies Config;
74 |
75 | export default config;
76 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": [
4 | "dom",
5 | "dom.iterable",
6 | "esnext"
7 | ],
8 | "allowJs": true,
9 | "skipLibCheck": true,
10 | "strict": true,
11 | "noEmit": true,
12 | "esModuleInterop": true,
13 | "module": "esnext",
14 | "moduleResolution": "bundler",
15 | "resolveJsonModule": true,
16 | "isolatedModules": true,
17 | "jsx": "preserve",
18 | "incremental": true,
19 | "plugins": [
20 | {
21 | "name": "next"
22 | }
23 | ],
24 | "paths": {
25 | "@/*": [
26 | "./*"
27 | ]
28 | }
29 | },
30 | "include": [
31 | "next-env.d.ts",
32 | "**/*.ts",
33 | "**/*.tsx",
34 | ".next/types/**/*.ts"
35 | ],
36 | "exclude": [
37 | "node_modules"
38 | ]
39 | }
--------------------------------------------------------------------------------
/types/app.ts:
--------------------------------------------------------------------------------
1 | import { SimpleModel } from '@/types/model';
2 | import { SpecifiedProviderSetting } from '@/types/settings';
3 |
4 | export interface ApiConfig {
5 | // apiKey?: string;
6 | // endpoint?: string;
7 | provider: SpecifiedProviderSetting;
8 | model: SimpleModel;
9 | stream: boolean | undefined;
10 | numberOfContext: number;
11 | }
12 |
--------------------------------------------------------------------------------
/types/conversation.ts:
--------------------------------------------------------------------------------
1 | import { Message } from 'ai';
2 |
3 | export interface Conversation {
4 | id: string;
5 | conversation: Message[];
6 | createdAt: string;
7 | updatedAt?: string;
8 | }
9 |
--------------------------------------------------------------------------------
/types/i18n.ts:
--------------------------------------------------------------------------------
1 | import { languageId } from '@/config/i18n';
2 |
3 | export type TLocale = (typeof languageId)[number];
4 |
--------------------------------------------------------------------------------
/types/model.ts:
--------------------------------------------------------------------------------
1 | import { AllModelId, AllModelName, Provider } from '@/config/provider';
2 |
3 | export interface Model {
4 | id: AllModelId;
5 | name: AllModelName;
6 | maxInputTokens?: number | null;
7 | maxOutputTokens?: number | null;
8 | maxTokens?: number | null;
9 | price?: number | null;
10 | vision?: boolean;
11 | }
12 |
13 | export interface SimpleModel {
14 | provider: Provider;
15 | model_id: Model['id'];
16 | model_name: Model['name'];
17 | }
18 |
--------------------------------------------------------------------------------
/types/search/resources.ts:
--------------------------------------------------------------------------------
1 | export type Resources = TavilyResult[] | YouResult[];
2 | export type Resource = TavilyResult | YouResult;
3 |
4 | export interface TavilyResult {
5 | title: string;
6 | url: string;
7 | content: string;
8 | }
9 |
10 | export interface YouResult {
11 | title: string;
12 | url: string;
13 | description: string;
14 | }
15 |
--------------------------------------------------------------------------------
/types/settings.ts:
--------------------------------------------------------------------------------
1 | import { Provider } from '@/config/provider';
2 |
3 | export enum Theme {
4 | Light = 'light',
5 | Dark = 'dark',
6 | System = 'system',
7 | }
8 |
9 | export type SendKeyType = 'ctrl+enter' | 'enter' | 'shift+enter';
10 | export type RenderMode = 'markdown' | 'plain';
11 |
12 | export interface Preference {
13 | theme?: Theme;
14 | // sendKey?: SendKeyType;
15 | enterSend?: boolean;
16 | // autoRead?: boolean;
17 | // renderMode?: RenderMode;
18 | useMarkdown?: boolean;
19 | // autoScroll?: boolean;
20 | }
21 |
22 | export interface AdvancedSettings {
23 | unifiedEndpoint?: boolean;
24 | streamMessages?: boolean;
25 | }
26 |
27 | export interface ConversationSettings {
28 | numOfContext: number;
29 | systemPrompt?: string | null;
30 | }
31 |
32 | export interface OpenAISettings {
33 | apiKey?: string;
34 | endpoint?: string;
35 | }
36 |
37 | export interface GoogleSettings {
38 | apiKey?: string;
39 | endpoint?: string;
40 | }
41 |
42 | export interface AmazonSettings {
43 | apiKey?: string;
44 | endpoint?: string;
45 | secretKey?: string;
46 | region?: string;
47 | }
48 |
49 | export interface AnthropicSettings {
50 | apiKey?: string;
51 | endpoint?: string;
52 | }
53 |
54 | export interface AzureSettings {
55 | apiKey?: string;
56 | endpoint?: string;
57 | deploymentName?: string;
58 | }
59 |
60 | export interface CohereSettings {
61 | apiKey?: string;
62 | endpoint?: string;
63 | }
64 |
65 | export interface FireworksSettings {
66 | apiKey?: string;
67 | endpoint?: string;
68 | }
69 |
70 | export interface GroqSettings {
71 | apiKey?: string;
72 | endpoint?: string;
73 | }
74 |
75 | export interface HuggingFaceSettings {
76 | apiKey?: string;
77 | endpoint?: string;
78 | }
79 |
80 | export interface MistralSettings {
81 | apiKey?: string;
82 | endpoint?: string;
83 | }
84 |
85 | export interface PerplexitySettings {
86 | apiKey?: string;
87 | endpoint?: string;
88 | }
89 |
90 | export type CustomSettings = SingleCustomSettings[];
91 |
92 | export interface SingleCustomSettings {
93 | basedProvider?: Provider;
94 | apiKey?: string;
95 | endpoint?: string;
96 | model?: string;
97 | }
98 |
99 | export interface ProviderSetting {
100 | [Provider.Amazon]?: AmazonSettings;
101 | [Provider.OpenAI]?: OpenAISettings;
102 | [Provider.Google]?: GoogleSettings;
103 | [Provider.Anthropic]?: AnthropicSettings;
104 | [Provider.Azure]?: AzureSettings;
105 | [Provider.Cohere]?: CohereSettings;
106 | [Provider.Fireworks]?: FireworksSettings;
107 | [Provider.Groq]?: GroqSettings;
108 | [Provider.HuggingFace]?: HuggingFaceSettings;
109 | [Provider.Mistral]?: MistralSettings;
110 | [Provider.Perplexity]?: PerplexitySettings;
111 | [Provider.Custom]?: CustomSettings;
112 | }
113 |
114 | export type SpecifiedProviderSetting =
115 | | AmazonSettings
116 | | OpenAISettings
117 | | GoogleSettings
118 | | AnthropicSettings
119 | | AzureSettings
120 | | CohereSettings
121 | | FireworksSettings
122 | | GroqSettings
123 | | HuggingFaceSettings
124 | | MistralSettings
125 | | PerplexitySettings
126 | | SingleCustomSettings;
127 |
--------------------------------------------------------------------------------
/utils/app/time.ts:
--------------------------------------------------------------------------------
1 | export const whatTimeOfDay = () => {
2 | const currentTime = new Date().getHours();
3 |
4 | if (currentTime < 12) {
5 | return 'good_morning';
6 | } else if (currentTime < 18) {
7 | return 'good_afternoon';
8 | } else {
9 | return 'good_evening';
10 | }
11 | };
12 |
--------------------------------------------------------------------------------
/utils/app/uuid.ts:
--------------------------------------------------------------------------------
1 | export const generateUUID = () => {
2 | let dt = new Date().getTime();
3 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
4 | const r = (dt + Math.random() * 16) % 16 | 0;
5 | dt = Math.floor(dt / 16);
6 | return (c == 'x' ? r : (r & 0x3) | 0x8).toString(16);
7 | });
8 | };
9 |
--------------------------------------------------------------------------------
/utils/app/version.ts:
--------------------------------------------------------------------------------
1 | export const getLatestVersion = async ({ owner, repo }: { owner: string; repo: string }) => {
2 | const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/releases/latest`);
3 |
4 | return await response.json();
5 | };
6 |
--------------------------------------------------------------------------------
/utils/provider/cohere.tsx:
--------------------------------------------------------------------------------
1 | import { Cohere } from 'cohere-ai';
2 |
3 | export const toCohereRole = (role: string): Cohere.ChatMessageRole => {
4 | if (role === 'user') {
5 | return Cohere.ChatMessageRole.User;
6 | }
7 | return Cohere.ChatMessageRole.Chatbot;
8 | };
9 |
--------------------------------------------------------------------------------
/utils/provider/google.tsx:
--------------------------------------------------------------------------------
1 | import { GenerateContentRequest } from '@google/generative-ai';
2 | import { Message } from 'ai';
3 |
4 | export const toGoogleMessage = (messages: Message[]): GenerateContentRequest => ({
5 | contents: messages
6 | .filter((message) => message.role === 'user' || message.role === 'assistant')
7 | .map((message) => ({
8 | role: message.role === 'user' ? 'user' : 'model',
9 | parts: [{ text: message.content }],
10 | })),
11 | });
12 |
--------------------------------------------------------------------------------
/utils/search/engines/google.ts:
--------------------------------------------------------------------------------
1 | import { GoogleSearchResult, GoogleSearchResultItem } from '@/types/search';
2 |
3 | export const withGoogleSearch = async (query: string, searchAPIKey?: string, searchEngineID?: string, lang: string = 'en') => {
4 | const apiKey = process.env.GOOGLE_SEARCH_API_KEY ?? searchAPIKey ?? '';
5 |
6 | const engineId = process.env.GOOGLE_SEARCH_ENGINE_ID ?? searchEngineID ?? '';
7 |
8 | const searchUrl = `https://www.googleapis.com/customsearch/v1?key=${apiKey}&cx=${engineId}&q=${query}&lr=lang_${lang}`;
9 |
10 | const response = await fetch(searchUrl, {
11 | method: 'GET',
12 | });
13 |
14 | if (!response.ok) {
15 | throw new Error(`Error(${response.status}): ${response.statusText}`);
16 | }
17 |
18 | return await response.json();
19 | };
20 |
21 | export const getGoogleSearchResultImage = (result: GoogleSearchResult) => {
22 | return result.items.map((item: GoogleSearchResultItem) => item.pagemap.cse_image?.[0].src);
23 | };
24 |
--------------------------------------------------------------------------------
/utils/search/engines/tavily.ts:
--------------------------------------------------------------------------------
1 | import { TavilySearchEngine } from '@/types/search';
2 |
3 | export const withTavilySearch = async (query: string, maxResults: number = 10, searchDepth: 'basic' | 'advanced' = 'basic', config?: TavilySearchEngine) => {
4 | const apiKey = config?.apiKey ?? process.env.TAVILY_SEARCH_API_KEY;
5 |
6 | const response = await fetch('https://api.tavily.com/search', {
7 | method: 'POST',
8 | headers: {
9 | 'Content-Type': 'application/json',
10 | },
11 | body: JSON.stringify({
12 | api_key: apiKey,
13 | query,
14 | max_results: maxResults < 5 ? 5 : maxResults,
15 | search_depth: searchDepth,
16 | include_images: true,
17 | include_answers: true,
18 | }),
19 | });
20 |
21 | if (!response.ok) {
22 | throw new Error(`Error(${response.status}): ${response.statusText}`);
23 | }
24 |
25 | return await response.json();
26 | };
27 |
28 | export const getTavilySearchResultImage = (result: any) => {
29 | return result.images;
30 | };
31 |
--------------------------------------------------------------------------------
/utils/search/engines/you.ts:
--------------------------------------------------------------------------------
1 | export const withYouSearch = async (query: string, maxResults: number = 10, searchDepth: 'basic' | 'advanced' = 'basic') => {
2 | const apiKey = process.env.YOU_SEARCH_API_KEY ?? '';
3 |
4 | const url = `https://api.ydc-index.io/search?query=${query}`;
5 |
6 | const response = await fetch(url, {
7 | method: 'POST',
8 | headers: {
9 | 'Content-Type': 'application/json',
10 | 'X-API-Key': apiKey,
11 | },
12 | });
13 |
14 | if (!response.ok) {
15 | throw new Error(`Error(${response.status}): ${response.statusText}`);
16 | }
17 |
18 | return await response.json();
19 | };
20 |
--------------------------------------------------------------------------------
/utils/search/image.ts:
--------------------------------------------------------------------------------
1 | import { getTavilySearchResultImage } from '@/utils/search/engines/tavily';
2 |
3 | export const getImage = ({ result }: { result: any }): string[] => {
4 | let images = [];
5 |
6 | images = getTavilySearchResultImage(result);
7 |
8 | return images;
9 | };
10 |
--------------------------------------------------------------------------------