├── .env
├── .env.template
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── CODEOWNERS
├── README.md
├── README_CN.md
├── app
├── Sidebar
│ ├── index.css
│ └── index.tsx
├── actions
│ └── publish.ts
├── api
│ ├── chat-o1
│ │ └── route.ts
│ ├── chat
│ │ └── route.ts
│ └── sandbox
│ │ └── route.ts
├── favicon.ico
├── globals.css
├── layout.tsx
├── page.tsx
└── providers.tsx
├── components.json
├── components
├── DeleteConfirmation
│ ├── index.css
│ └── index.tsx
├── Dialog
│ ├── Add
│ │ ├── index.css
│ │ └── index.tsx
│ ├── img
│ │ └── config_s.svg
│ ├── index.css
│ └── index.tsx
├── auth-dialog.tsx
├── auth-form.tsx
├── chat-input.tsx
├── chat-picker.tsx
├── chat-settings.tsx
├── chat.css
├── chat.tsx
├── code-theme.css
├── code-view.tsx
├── deploy-dialog.tsx
├── fragment-code.tsx
├── fragment-interpreter.tsx
├── fragment-preview.tsx
├── fragment-web.tsx
├── logo.tsx
├── navbar.tsx
├── preview.tsx
└── ui
│ ├── alert.tsx
│ ├── avatar.tsx
│ ├── button.tsx
│ ├── card.tsx
│ ├── copy-button.tsx
│ ├── dialog.tsx
│ ├── dropdown-menu.tsx
│ ├── input.tsx
│ ├── label.tsx
│ ├── select.tsx
│ ├── separator.tsx
│ ├── skeleton.tsx
│ ├── tabs.tsx
│ ├── textarea.tsx
│ ├── theme-toggle.tsx
│ ├── toast.tsx
│ ├── toaster.tsx
│ ├── tooltip.tsx
│ └── use-toast.ts
├── docs
├── docs
│ ├── assets
│ │ ├── add-mcp-server.png
│ │ ├── code-login.gif
│ │ ├── fast-mcp-weather.gif
│ │ ├── fast-mcp-weather.png
│ │ ├── favicon.ico
│ │ ├── image-to-UI-preview.png
│ │ ├── image-to-UI.gif
│ │ ├── image-to-UI.png
│ │ ├── landing-page.png
│ │ ├── logo.svg
│ │ ├── mcp-server.gif
│ │ ├── text-to-UI-preview.png
│ │ ├── text-to-UI.gif
│ │ └── text-to-UI.png
│ ├── en
│ │ ├── contribute.md
│ │ ├── generate-code.md
│ │ ├── get-started.md
│ │ ├── index.md
│ │ └── work-with-mcp.md
│ ├── javascripts
│ │ └── feedback.js
│ ├── overrides
│ │ └── main.html
│ └── zh
│ │ ├── contribute.md
│ │ ├── generate-code.md
│ │ ├── get-started.md
│ │ ├── index.md
│ │ └── work-with-mcp.md
└── mkdocs.yml
├── lib
├── api.ts
├── auth.ts
├── duration.ts
├── messages.ts
├── models copy.json
├── models.json
├── models.ts
├── prompt.ts
├── ratelimit.ts
├── request.ts
├── schema.ts
├── supabase.ts
├── templates.json
├── templates.ts
├── types.ts
└── utils.ts
├── middleware.ts
├── next.config.mjs
├── package-lock.json
├── package.json
├── postcss.config.mjs
├── preview.png
├── public
└── thirdparty
│ ├── logo.png
│ ├── logo.svg
│ ├── logo2.png
│ ├── logos
│ ├── anthropic.svg
│ ├── azure.svg
│ ├── fireworks.svg
│ ├── fireworksai.svg
│ ├── google.svg
│ ├── groq.svg
│ ├── mistral.svg
│ ├── ollama.svg
│ ├── openai.svg
│ ├── togetherai.svg
│ ├── vertex.svg
│ └── xai.svg
│ └── templates
│ ├── code-interpreter-v1.svg
│ ├── gradio-developer.svg
│ ├── nextjs-developer.svg
│ ├── streamlit-developer.svg
│ └── vue-developer.svg
├── sandbox-templates
├── gradio-developer
│ ├── app.py
│ ├── e2b.Dockerfile
│ └── e2b.toml
├── nextjs-developer
│ ├── _app.tsx
│ ├── compile_page.sh
│ ├── e2b.Dockerfile
│ └── e2b.toml
├── streamlit-developer
│ ├── app.py
│ ├── e2b.Dockerfile
│ └── e2b.toml
└── vue-developer
│ ├── e2b.Dockerfile
│ ├── e2b.toml
│ └── nuxt.config.ts
├── tailwind.config.ts
└── tsconfig.json
/.env:
--------------------------------------------------------------------------------
1 | # API URL
2 | NEXT_PUBLIC_API_URL=
3 |
4 | # Get your E2B API key here https://e2b.dev/docs/getting-started/api-key
5 | E2B_API_KEY=
6 |
7 | # Get your Azure API key here https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/create-resource?tabs=portal
8 | AZURE_API_KEY=
9 |
10 | # Get your Anthropic API key here https://console.anthropic.com
11 | ANTHROPIC_API_KEY=
--------------------------------------------------------------------------------
/.env.template:
--------------------------------------------------------------------------------
1 | # API URL
2 | NEXT_PUBLIC_API_URL=
3 |
4 | # Get your E2B API key here https://e2b.dev/docs/getting-started/api-key
5 | E2B_API_KEY=
6 |
7 | # Get your Azure API key here https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/create-resource?tabs=portal
8 | AZURE_API_KEY=
9 |
10 | # Get your Anthropic API key here https://console.anthropic.com
11 | ANTHROPIC_API_KEY=
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.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*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
38 | # static doc files
39 | docs/site/
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "semi": false,
4 | "plugins": ["@trivago/prettier-plugin-sort-imports"]
5 | }
6 |
--------------------------------------------------------------------------------
/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # These owners will be the default owners for everything in
2 | # the repo. Unless a later match takes precedence,
3 | * @mishushakov
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # EFFLUX
2 |
3 | [English](./README.md) | [简体中文](./README_CN.md)
4 |
5 | ## What's Efflux?
6 |
7 | Efflux, the next-generation AI interaction platform, is a powerful, lightweight, and highly flexible framework that seamlessly integrates state-of-the-art LLMs (large language models), generative front-end technologies, and MCP (Model Context Protocols) servers. It redefines the way AI-driven applications are deployed and scaled, offering unparalleled efficiency and adaptability.
8 |
9 | In essence, Efflux can serve as:
10 |
11 | * **An LLM-powered chatbot** that engages in natural conversations with users.
12 |
13 | * **A text-to-artifact tool** that helps developers create code snippets effortlessly —— simply by describing their ideas. Efflux can also render the generated UI code in real time, allowing you to immediately test and iterate.
14 |
15 | * **A ready-to-use MCP (Model Context Protocol) host**, unlocking your LLMs' potential and expanding more capabilities by enabling wider data access and integrating custom tools, including but not limited to database interaction and business intelligence.
16 |
17 |
18 | ## Online Demo
19 | Experience Efflux in action through our [online demo](http://47.236.204.213:3000/login).
20 |
21 |
22 | ## Features
23 |
24 | - Based on Next.js 14 (App Router, Server Actions), shadcn/ui, TailwindCSS, Vercel AI SDK.
25 | - Uses the [E2B SDK](https://github.com/e2b-dev/code-interpreter) by [E2B](https://e2b.dev) to securely execute code generated by AI.
26 | - Streaming in the UI.
27 | - Can install and use any package from npm, pip.
28 |
29 |
30 | ## Get started
31 |
32 |
33 | ### 1. Clone the repository
34 |
35 | In your terminal:
36 |
37 | ```
38 | git clone https://github.com/isoftstone-data-intelligence-ai/efflux-frontend.git
39 | ```
40 |
41 | ### 2. Install the dependencies
42 |
43 | Enter the repository:
44 |
45 | ```
46 | cd efflux-frontend
47 | ```
48 |
49 | Run the following to install the required dependencies:
50 |
51 | ```
52 | npm i
53 | ```
54 |
55 | ### 3. Set the environment variables
56 |
57 | Create a `.env.local` file and set the following:
58 |
59 | ```sh
60 | # Get your API key here - https://e2b.dev/
61 | E2B_API_KEY="your-e2b-api-key"
62 |
63 | # Get your Azure API key here https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/create-resource?tabs=portal
64 | AZURE_API_KEY=
65 |
66 | # API URL
67 | NEXT_PUBLIC_API_URL=
68 |
69 | # OpenAI API Key
70 | OPENAI_API_KEY=
71 |
72 | # Other providers
73 | ANTHROPIC_API_KEY=
74 | GROQ_API_KEY=
75 | FIREWORKS_API_KEY=
76 | TOGETHER_API_KEY=
77 | GOOGLE_AI_API_KEY=
78 | GOOGLE_VERTEX_CREDENTIALS=
79 | MISTRAL_API_KEY=
80 | XAI_API_KEY=
81 |
82 | ### Optional env vars
83 |
84 | # Domain of the site
85 | NEXT_PUBLIC_SITE_URL=
86 |
87 | # Disabling API key and base URL input in the chat
88 | NEXT_PUBLIC_NO_API_KEY_INPUT=
89 | NEXT_PUBLIC_NO_BASE_URL_INPUT=
90 |
91 | # Rate limit
92 | RATE_LIMIT_MAX_REQUESTS=
93 | RATE_LIMIT_WINDOW=
94 |
95 | # Vercel/Upstash KV (short URLs, rate limiting)
96 | KV_REST_API_URL=
97 | KV_REST_API_TOKEN=
98 |
99 | # Supabase (auth)
100 | SUPABASE_URL=
101 | SUPABASE_ANON_KEY=
102 |
103 | # PostHog (analytics)
104 | NEXT_PUBLIC_POSTHOG_KEY=
105 | NEXT_PUBLIC_POSTHOG_HOST=
106 | ```
107 |
108 | ### 4. Start the development server
109 |
110 | ```
111 | npm run dev
112 | ```
113 |
114 | ### 5. Build the web app
115 |
116 | ```
117 | npm run build
118 | ```
119 |
120 |
121 | ## Documentation
122 |
123 | For more information and guidance, check out [Efflux Docs](https://jun-ma.github.io/efflux-frontend/).
--------------------------------------------------------------------------------
/README_CN.md:
--------------------------------------------------------------------------------
1 | # EFFLUX
2 |
3 | [English](./README.md) | [简体中文](./README_CN.md)
4 |
5 | ## Efflux是什么?
6 |
7 | Efflux 是一款新一代 AI 交互平台 —— 一个强大、轻量且高度灵活的框架,可无缝集成最先进的大语言模型(LLMs)、生成式前端技术以及 MCP(模型上下文协议)服务器。它重新定义了 AI 驱动应用的部署和扩展方式,可以通过接入海量社区工具,构建普惠AI生态。
8 |
9 | Efflux 可以是:
10 |
11 | * **基于 LLM 的聊天机器人**,能够与用户进行自然语言对话。
12 |
13 | * **文本到组件(Text-to-Artifact)生成工具**,帮助开发者轻松创建代码片段 —— 只需描述你的想法即可。Efflux 能实时渲染生成的 UI 代码,让你能够立即测试和迭代。
14 |
15 | * **开箱即用的 MCP(模型上下文协议)主机**,通过更广泛的数据访问和集成自定义工具释放 LLM 潜力。
16 |
17 |
18 | ## 在线演示
19 |
20 | 您可以通过访问[在线演示](http://47.236.204.213:3000/login)来体验Efflux的功能。
21 |
22 |
23 | ## 特性
24 |
25 | - 基于 Next.js 14 (App Router, Server Actions)、shadcn/ui、TailwindCSS 和 Vercel AI SDK 构建
26 | - 使用 [E2B](https://e2b.dev) 开发的 [E2B SDK](https://github.com/e2b-dev/code-interpreter) 来安全执行 AI 生成的代码
27 | - UI 流式响应
28 | - 支持安装和使用任何 npm、pip 包
29 |
30 | ## 快速开始
31 |
32 | ### 1. 克隆仓库
33 |
34 | 在终端中执行:
35 |
36 | ```
37 | git clone https://github.com/isoftstone-data-intelligence-ai/efflux-frontend.git
38 | ```
39 |
40 | ### 2. 安装依赖
41 |
42 | 进入项目目录:
43 |
44 | ```
45 | cd efflux-frontend
46 | ```
47 |
48 | 运行以下命令安装所需依赖:
49 |
50 | ```
51 | npm i
52 | ```
53 |
54 | ### 3. 配置环境变量
55 |
56 | 创建 `.env.local` 文件并设置以下环境变量:
57 |
58 | ```sh
59 | # 在此获取 E2B API 密钥 - https://e2b.dev/
60 | E2B_API_KEY="your-e2b-api-key"
61 |
62 | # 在此获取 Azure API 密钥 https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/create-resource?tabs=portal
63 | AZURE_API_KEY=
64 |
65 | # API 地址
66 | NEXT_PUBLIC_API_URL=
67 |
68 | # OpenAI API 密钥
69 | OPENAI_API_KEY=
70 |
71 | # 其他服务提供商
72 | ANTHROPIC_API_KEY=
73 | GROQ_API_KEY=
74 | FIREWORKS_API_KEY=
75 | TOGETHER_API_KEY=
76 | GOOGLE_AI_API_KEY=
77 | GOOGLE_VERTEX_CREDENTIALS=
78 | MISTRAL_API_KEY=
79 | XAI_API_KEY=
80 |
81 | ### 可选环境变量
82 |
83 | # 站点域名
84 | NEXT_PUBLIC_SITE_URL=
85 |
86 | # 禁用聊天中的 API 密钥和基础 URL 输入
87 | NEXT_PUBLIC_NO_API_KEY_INPUT=
88 | NEXT_PUBLIC_NO_BASE_URL_INPUT=
89 |
90 | # 速率限制
91 | RATE_LIMIT_MAX_REQUESTS=
92 | RATE_LIMIT_WINDOW=
93 |
94 | # Vercel/Upstash KV(短 URL、速率限制)
95 | KV_REST_API_URL=
96 | KV_REST_API_TOKEN=
97 |
98 | # Supabase(认证)
99 | SUPABASE_URL=
100 | SUPABASE_ANON_KEY=
101 |
102 | # PostHog(分析)
103 | NEXT_PUBLIC_POSTHOG_KEY=
104 | NEXT_PUBLIC_POSTHOG_HOST=
105 | ```
106 |
107 | ### 4. 启动开发服务器
108 |
109 | ```
110 | npm run dev
111 | ```
112 |
113 | ### 5. 构建网页应用
114 |
115 | ```
116 | npm run build
117 | ```
118 |
119 |
120 | ## 文档
121 |
122 | 有关更多信息和指导,请查看 [Efflux Docs](http://localhost:8080/efflux-frontend/zh/)。
--------------------------------------------------------------------------------
/app/Sidebar/index.css:
--------------------------------------------------------------------------------
1 | .sidebar-wrapper .drawer-overlay {
2 | background-color: rgba(0, 0, 0, 0.4);
3 | position: fixed;
4 | inset: 0;
5 | animation: overlayShow 150ms cubic-bezier(0.16, 1, 0.3, 1);
6 | z-index: 9998;
7 | }
8 |
9 | .sidebar-wrapper .drawer-content {
10 | position: fixed;
11 | top: 0;
12 | left: 0;
13 | bottom: 0;
14 | box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
15 | animation: drawerSlideIn 150ms cubic-bezier(0.16, 1, 0.3, 1);
16 | z-index: 9999;
17 | }
18 |
19 | .sidebar-wrapper button.trigger-button {
20 | position: fixed;
21 | left: 0;
22 | top: 50%;
23 | transform: translateY(-50%);
24 | border-left: none;
25 | padding: 8px 4px;
26 | border-radius: 0 4px 4px 0;
27 | cursor: pointer;
28 | z-index: 9997;
29 | transition: all 0.2s ease;
30 | }
31 |
32 | .sidebar-wrapper button.close-button {
33 | padding: 8px;
34 | border-radius: 4px;
35 | cursor: pointer;
36 | display: flex;
37 | align-items: center;
38 | justify-content: center;
39 | transition: all 0.2s ease;
40 | }
41 |
42 | /* Light Theme */
43 | .sidebar-wrapper .light {
44 | background-color: #ffffff;
45 | color: #18181B;
46 | }
47 |
48 | .sidebar-wrapper button.trigger-button.light {
49 | background-color: #ffffff;
50 | border: 1px solid #e5e7eb;
51 | border-left: none;
52 | color: #6b7280;
53 | }
54 |
55 | .sidebar-wrapper button.trigger-button.light:hover {
56 | background-color: #f3f4f6;
57 | color: #374151;
58 | }
59 |
60 | .sidebar-wrapper button.close-button.light {
61 | color: #6b7280;
62 | }
63 |
64 | .sidebar-wrapper button.close-button.light:hover {
65 | background-color: #f3f4f6;
66 | color: #374151;
67 | }
68 |
69 | .sidebar-wrapper .light .border-t {
70 | border-color: #e5e7eb;
71 | }
72 |
73 | .sidebar-wrapper .light h2 {
74 | color: #6b7280;
75 | }
76 |
77 | .sidebar-wrapper .light button {
78 | background-color: #f3f4f6;
79 | color: #374151;
80 | }
81 |
82 | .sidebar-wrapper .light button:hover {
83 | background-color: #e5e7eb;
84 | }
85 |
86 | .sidebar-wrapper .light i {
87 | color: #6b7280;
88 | }
89 |
90 | .sidebar-wrapper .light a {
91 | color: #6b7280;
92 | }
93 |
94 | .sidebar-wrapper .light a:hover {
95 | color: #374151;
96 | }
97 |
98 | /* Dark Theme */
99 | .sidebar-wrapper .dark {
100 | background-color: #18181B;
101 | color: #ffffff;
102 | }
103 |
104 | .sidebar-wrapper button.trigger-button.dark {
105 | background-color: #18181B;
106 | border: 1px solid #27272a;
107 | border-left: none;
108 | color: #d4d4d8;
109 | }
110 |
111 | .sidebar-wrapper button.trigger-button.dark:hover {
112 | background-color: #27272a;
113 | color: #ffffff;
114 | }
115 |
116 | .sidebar-wrapper button.close-button.dark {
117 | color: #d4d4d8;
118 | }
119 |
120 | .sidebar-wrapper button.close-button.dark:hover {
121 | background-color: #27272a;
122 | color: #ffffff;
123 | }
124 |
125 | .sidebar-wrapper .dark .border-t {
126 | border-color: #27272a;
127 | }
128 |
129 | .sidebar-wrapper .dark h2 {
130 | color: #d4d4d8;
131 | }
132 |
133 | .sidebar-wrapper .dark button {
134 | background-color: #27272a;
135 | color: #ffffff;
136 | }
137 |
138 | .sidebar-wrapper .dark button:hover {
139 | background-color: #3f3f46;
140 | }
141 |
142 | .sidebar-wrapper .dark i {
143 | color: #d4d4d8;
144 | }
145 |
146 | .sidebar-wrapper .dark a {
147 | color: #d4d4d8;
148 | }
149 |
150 | .sidebar-wrapper .dark a:hover {
151 | color: #ffffff;
152 | }
153 |
154 | /* Dark theme text colors */
155 | .sidebar-wrapper .dark .text-black {
156 | color: #ffffff;
157 | }
158 |
159 | .sidebar-wrapper .dark .text-gray-500,
160 | .sidebar-wrapper .dark .text-gray-600,
161 | .sidebar-wrapper .dark .text-gray-800 {
162 | color: #d4d4d8;
163 | }
164 |
165 | /* Selected chat in dark theme */
166 | .sidebar-wrapper .dark .selected-chat {
167 | background-color: #d4d4d8 !important;
168 | }
169 |
170 | .sidebar-wrapper .dark .selected-chat span {
171 | color: #18181B !important;
172 | }
173 |
174 | /* Preserve New Chat button styles */
175 | .sidebar-wrapper .dark button.bg-gray-100 {
176 | background-color: #27272a;
177 | color: #ffffff;
178 | }
179 |
180 | .sidebar-wrapper .dark button.bg-gray-100:hover {
181 | background-color: #3f3f46;
182 | }
183 |
184 | @keyframes overlayShow {
185 | from {
186 | opacity: 0;
187 | }
188 | to {
189 | opacity: 1;
190 | }
191 | }
192 |
193 | @keyframes drawerSlideIn {
194 | from {
195 | transform: translateX(-100%);
196 | }
197 | to {
198 | transform: translateX(0);
199 | }
200 | }
--------------------------------------------------------------------------------
/app/Sidebar/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import * as Dialog from '@radix-ui/react-dialog'
3 | import './index.css'
4 | import { getChatList } from '@/lib/api'
5 |
6 |
7 | export default class extends React.Component {
8 | state = {
9 | open: false,
10 | selectedChat: null,
11 | selectedItem: null,
12 | chats: []
13 | };
14 |
15 | componentDidMount = async () => {
16 | this.getList((data) => {
17 | var selectedChat = window.localStorage.getItem('selectedChat')
18 | var selectedItem = null
19 | data.forEach((item) => {
20 | if (String(item.id) == selectedChat) {
21 | selectedItem = item
22 | }
23 | })
24 |
25 | var obj = {
26 | selectedChat: selectedChat ? parseInt(selectedChat) : null,
27 | selectedItem: selectedItem,
28 | }
29 |
30 | if(selectedItem){
31 | this.props.setMessages(selectedItem.chat_messages)
32 | }
33 |
34 | this.setState(obj)
35 | })
36 | }
37 |
38 | getList = async (back = () => { }) => {
39 | var rs = await getChatList({ userId: 1 })
40 | if (rs.data?.code == 200) {
41 | var data = rs.data.data
42 | this.setState({ chats: data }, () => {
43 | back(data)
44 | })
45 | }
46 | }
47 |
48 | newChat = () => {
49 | this.setState({ selectedChat: null, selectedItem: null });
50 | this.setState({ open: false });
51 | this.props.onAdd()
52 | }
53 | handleChatSelect = (chatId: string) => {
54 | var { chats } = this.state
55 | var obj = {}
56 | chats.forEach((item) => {
57 | if (item.id == chatId) obj = item
58 | })
59 | this.setState({ selectedChat: chatId, selectedItem: obj });
60 | window.localStorage.setItem('selectedChat', chatId)
61 | this.props.setMessages(obj.chat_messages)
62 | };
63 |
64 | render() {
65 | const { open, selectedChat, chats } = this.state;
66 | const { theme = 'light', disabled } = this.props;
67 |
68 | return (
69 |
70 |
{ this.setState({ open: val }) }}>
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | Navigation Menu
81 |
82 | Navigation menu for accessing different sections of the application
83 |
84 |
85 |
86 |
87 |
88 |
Chat
89 |
90 |
91 |
92 |
93 |
94 |
95 |
this.newChat()}>
96 | New Chat
97 |
98 |
99 |
100 |
101 |
102 |
103 |
Recent Chats
104 |
105 | {chats.map(chat => (
106 |
this.handleChatSelect(chat.id)}
109 | className={`relative group py-2 px-4 rounded-md text-sm font-medium flex justify-between items-center cursor-pointer ${selectedChat === chat.id ? 'bg-gray-100 selected-chat' : ''
110 | }`}
111 | >
112 | {chat.summary}
113 |
114 | ))}
115 |
116 |
117 |
118 |
119 |
120 |
121 | )
122 | }
123 | }
--------------------------------------------------------------------------------
/app/actions/publish.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { Duration, ms } from '@/lib/duration'
4 | import { Sandbox } from '@e2b/code-interpreter'
5 | import { kv } from '@vercel/kv'
6 | import { customAlphabet } from 'nanoid'
7 |
8 | const nanoid = customAlphabet('1234567890abcdef', 7)
9 |
10 | export async function publish(
11 | url: string,
12 | sbxId: string,
13 | duration: Duration,
14 | apiKey: string | undefined,
15 | ) {
16 | const expiration = ms(duration)
17 | await Sandbox.setTimeout(sbxId, expiration, { apiKey })
18 |
19 | if (process.env.KV_REST_API_URL && process.env.KV_REST_API_TOKEN) {
20 | const id = nanoid()
21 | await kv.set(`fragment:${id}`, url, { px: expiration })
22 |
23 | return {
24 | url: process.env.NEXT_PUBLIC_SITE_URL
25 | ? `https://${process.env.NEXT_PUBLIC_SITE_URL}/s/${id}`
26 | : `/s/${id}`,
27 | }
28 | }
29 |
30 | return {
31 | url,
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/api/chat-o1/route.ts:
--------------------------------------------------------------------------------
1 | import { Duration } from '@/lib/duration'
2 | import { getModelClient } from '@/lib/models'
3 | import { LLMModel, LLMModelConfig } from '@/lib/models'
4 | import { toPrompt } from '@/lib/prompt'
5 | import ratelimit from '@/lib/ratelimit'
6 | import { fragmentSchema as schema } from '@/lib/schema'
7 | import { Templates, templatesToPrompt } from '@/lib/templates'
8 | import { openai } from '@ai-sdk/openai'
9 | import { streamObject, LanguageModel, CoreMessage, generateText } from 'ai'
10 |
11 | export const maxDuration = 60
12 |
13 | const rateLimitMaxRequests = process.env.RATE_LIMIT_MAX_REQUESTS
14 | ? parseInt(process.env.RATE_LIMIT_MAX_REQUESTS)
15 | : 10
16 | const ratelimitWindow = process.env.RATE_LIMIT_WINDOW
17 | ? (process.env.RATE_LIMIT_WINDOW as Duration)
18 | : '1d'
19 |
20 | export async function POST(req: Request) {
21 | const {
22 | messages,
23 | userID,
24 | template,
25 | model,
26 | config,
27 | }: {
28 | messages: CoreMessage[]
29 | userID: string
30 | template: Templates
31 | model: LLMModel
32 | config: LLMModelConfig
33 | } = await req.json()
34 |
35 | const limit = !config.apiKey
36 | ? await ratelimit(
37 | userID,
38 | rateLimitMaxRequests,
39 | ratelimitWindow,
40 | )
41 | : false
42 |
43 | if (limit) {
44 | return new Response('You have reached your request limit for the day.', {
45 | status: 429,
46 | headers: {
47 | 'X-RateLimit-Limit': limit.amount.toString(),
48 | 'X-RateLimit-Remaining': limit.remaining.toString(),
49 | 'X-RateLimit-Reset': limit.reset.toString(),
50 | },
51 | })
52 | }
53 |
54 | console.log('userID', userID)
55 | // console.log('template', template)
56 | console.log('model', model)
57 | // console.log('config', config)
58 |
59 | const { model: modelNameString, apiKey: modelApiKey, ...modelParams } = config
60 | const modelClient = getModelClient(model, config)
61 |
62 | messages.unshift({
63 | role: 'user',
64 | content: toPrompt(template),
65 | })
66 |
67 | const { text } = await generateText({
68 | model: modelClient as LanguageModel,
69 | messages,
70 | ...modelParams,
71 | })
72 |
73 | const stream = await streamObject({
74 | model: openai('gpt-4o-mini') as LanguageModel,
75 | schema,
76 | system: `Please extract as required by the schema from the response. You can use one of the following templates:\n${templatesToPrompt(template)}`,
77 | prompt: text,
78 | ...modelParams,
79 | })
80 |
81 | return stream.toTextStreamResponse()
82 | }
83 |
--------------------------------------------------------------------------------
/app/api/chat/route.ts:
--------------------------------------------------------------------------------
1 | import { Duration } from '@/lib/duration'
2 | import { getModelClient, getDefaultMode } from '@/lib/models'
3 | import { LLMModel, LLMModelConfig } from '@/lib/models'
4 | import { toPrompt } from '@/lib/prompt'
5 | import ratelimit from '@/lib/ratelimit'
6 | import { fragmentSchema as schema } from '@/lib/schema'
7 | import { Templates } from '@/lib/templates'
8 | import { streamObject, LanguageModel, CoreMessage } from 'ai'
9 |
10 | export const maxDuration = 60
11 |
12 | const rateLimitMaxRequests = process.env.RATE_LIMIT_MAX_REQUESTS
13 | ? parseInt(process.env.RATE_LIMIT_MAX_REQUESTS)
14 | : 10
15 | const ratelimitWindow = process.env.RATE_LIMIT_WINDOW
16 | ? (process.env.RATE_LIMIT_WINDOW as Duration)
17 | : '1d'
18 |
19 | export async function POST(req: Request) {
20 | const {
21 | messages,
22 | userID,
23 | template,
24 | model,
25 | config,
26 | }: {
27 | messages: CoreMessage[]
28 | userID: string
29 | template: Templates
30 | model: LLMModel
31 | config: LLMModelConfig
32 | } = await req.json()
33 |
34 | const limit = !config.apiKey
35 | ? await ratelimit(
36 | userID,
37 | rateLimitMaxRequests,
38 | ratelimitWindow,
39 | )
40 | : false
41 |
42 | if (limit) {
43 | return new Response('You have reached your request limit for the day.', {
44 | status: 429,
45 | headers: {
46 | 'X-RateLimit-Limit': limit.amount.toString(),
47 | 'X-RateLimit-Remaining': limit.remaining.toString(),
48 | 'X-RateLimit-Reset': limit.reset.toString(),
49 | },
50 | })
51 | }
52 | console.log('---------------------------------')
53 | console.log('userID', userID)
54 | console.log('model', model)
55 | // console.log(JSON.stringify({
56 | // schema,
57 | // system: toPrompt(template),
58 | // messages,
59 | // mode: getDefaultMode(model),
60 | // }))
61 |
62 |
63 | const { model: modelNameString, apiKey: modelApiKey, ...modelParams } = config
64 | const modelClient = getModelClient(model, config)
65 |
66 | // 相当于用固定的 关键词 去让大模型生成代码
67 | const stream = await streamObject({
68 | model: modelClient as LanguageModel,
69 | schema,
70 | system: toPrompt(template),
71 | messages,
72 | mode: getDefaultMode(model),
73 | ...modelParams,
74 | })
75 |
76 | return stream.toTextStreamResponse()
77 | }
78 |
--------------------------------------------------------------------------------
/app/api/sandbox/route.ts:
--------------------------------------------------------------------------------
1 | import { FragmentSchema } from '@/lib/schema'
2 | import { ExecutionResultInterpreter, ExecutionResultWeb } from '@/lib/types'
3 | import { Sandbox } from '@e2b/code-interpreter'
4 |
5 | const sandboxTimeout = 10 * 60 * 1000 // 10 minute in ms
6 |
7 | export const maxDuration = 60
8 |
9 | export async function POST(req: Request) {
10 | const {
11 | fragment,
12 | userID,
13 | apiKey,
14 | }: { fragment: FragmentSchema; userID: string; apiKey?: string } =
15 | await req.json()
16 | console.log('fragment', fragment)
17 | console.log('userID', userID)
18 | // console.log('apiKey', apiKey)
19 |
20 | // Create a interpreter or a sandbox
21 | const sbx = await Sandbox.create(fragment.template, {
22 | metadata: { template: fragment.template, userID: userID },
23 | timeoutMs: sandboxTimeout,
24 | apiKey,
25 | })
26 |
27 | // Install packages
28 | if (fragment.has_additional_dependencies) {
29 | await sbx.commands.run(fragment.install_dependencies_command)
30 | console.log(
31 | `Installed dependencies: ${fragment.additional_dependencies.join(', ')} in sandbox ${sbx.sandboxId}`,
32 | )
33 | }
34 |
35 | // Copy code to fs
36 | if (fragment.code && Array.isArray(fragment.code)) {
37 | fragment.code.forEach(async (file) => {
38 | await sbx.files.write(file.file_path, file.file_content)
39 | console.log(`Copied file to ${file.file_path} in ${sbx.sandboxId}`)
40 | })
41 | } else {
42 | await sbx.files.write(fragment.file_path, fragment.code)
43 | console.log(`Copied file to ${fragment.file_path} in ${sbx.sandboxId}`)
44 | }
45 |
46 | // Execute code or return a URL to the running sandbox
47 | if (fragment.template === 'code-interpreter-v1') {
48 | const { logs, error, results } = await sbx.runCode(fragment.code || '')
49 |
50 | return new Response(
51 | JSON.stringify({
52 | sbxId: sbx?.sandboxId,
53 | template: fragment.template,
54 | stdout: logs.stdout,
55 | stderr: logs.stderr,
56 | runtimeError: error,
57 | cellResults: results,
58 | } as ExecutionResultInterpreter),
59 | )
60 | }
61 |
62 | return new Response(
63 | JSON.stringify({
64 | sbxId: sbx?.sandboxId,
65 | template: fragment.template,
66 | url: `https://${sbx?.getHost(fragment.port || 80)}`,
67 | } as ExecutionResultWeb),
68 | )
69 | }
70 |
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/isoftstone-data-intelligence-ai/efflux-frontend/1b941cc4ed746995a952e72cd8afa4c86eb4eab5/app/favicon.ico
--------------------------------------------------------------------------------
/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | :root {
7 | --background: 0 0% 100%;
8 | --foreground: 240 10% 3.9%;
9 | --card: 0 0% 100%;
10 | --card-foreground: 240 10% 3.9%;
11 | --popover: 0 0% 100%;
12 | --popover-foreground: 240 10% 3.9%;
13 | --primary: 240 5.9% 10%;
14 | --primary-foreground: 0 0% 98%;
15 | --secondary: 240 4.8% 95.9%;
16 | --secondary-foreground: 240 5.9% 10%;
17 | --muted: 240 4.8% 95.9%;
18 | --muted-foreground: 240 3.8% 46.1%;
19 | --accent: 240 4.8% 95.9%;
20 | --accent-foreground: 240 5.9% 10%;
21 | --destructive: 0 84.2% 60.2%;
22 | --destructive-foreground: 0 0% 98%;
23 | --border: 240 5.9% 90%;
24 | --input: 240 5.9% 90%;
25 | --ring: 240 10% 3.9%;
26 | --chart-1: 12 76% 61%;
27 | --chart-2: 173 58% 39%;
28 | --chart-3: 197 37% 24%;
29 | --chart-4: 43 74% 66%;
30 | --chart-5: 27 87% 67%;
31 | --radius: 0.75rem;
32 | }
33 |
34 | .dark {
35 | --background: 240, 6%, 10%;
36 | --foreground: 0 0% 98%;
37 | --card: 240 10% 3.9%;
38 | --card-foreground: 0 0% 98%;
39 | --popover: 240, 5%, 13%;
40 | --popover-foreground: 0 0% 98%;
41 | --primary: 0 0% 98%;
42 | --primary-foreground: 240 5.9% 10%;
43 | --secondary: 240 3.7% 15.9%;
44 | --secondary-foreground: 0 0% 98%;
45 | --muted: 240 3.7% 15.9%;
46 | --muted-foreground: 240 5% 64.9%;
47 | --accent: 240 3.7% 15.9%;
48 | --accent-foreground: 0 0% 98%;
49 | --destructive: 0 62.8% 30.6%;
50 | --destructive-foreground: 0 0% 98%;
51 | --border: 270, 2%, 19%;
52 | --input: 240 3.7% 15.9%;
53 | --ring: 0, 0%, 100%, 0.1;
54 | --chart-1: 220 70% 50%;
55 | --chart-2: 160 60% 45%;
56 | --chart-3: 30 80% 55%;
57 | --chart-4: 280 65% 60%;
58 | --chart-5: 340 75% 55%;
59 | }
60 | }
61 |
62 | @layer base {
63 | * {
64 | @apply border-border;
65 | }
66 | body {
67 | @apply bg-background text-foreground;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import './globals.css'
2 | import { PostHogProvider, ThemeProvider } from './providers'
3 | import { Toaster } from '@/components/ui/toaster'
4 | import type { Metadata } from 'next'
5 | import { Inter } from 'next/font/google'
6 |
7 | const inter = Inter({ subsets: ['latin'] })
8 |
9 | export const metadata: Metadata = {
10 | title: 'EFFLUX',
11 | description: "Open-source version of Anthropic's Artifacts",
12 | }
13 |
14 | export default function RootLayout({
15 | children,
16 | }: Readonly<{
17 | children: React.ReactNode
18 | }>) {
19 | return (
20 |
21 |
22 |
23 |
29 | {children}
30 |
31 |
32 |
33 |
34 |
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/app/providers.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { ThemeProvider as NextThemesProvider } from 'next-themes'
4 | import { type ThemeProviderProps } from 'next-themes/dist/types'
5 | import posthog from 'posthog-js'
6 | import { PostHogProvider as PostHogProviderJS } from 'posthog-js/react'
7 |
8 | if (typeof window !== 'undefined' && process.env.NEXT_PUBLIC_ENABLE_POSTHOG) {
9 | posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY ?? '', {
10 | api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
11 | person_profiles: 'identified_only',
12 | session_recording: {
13 | recordCrossOriginIframes: true,
14 | }
15 | })
16 | }
17 |
18 | export function PostHogProvider({ children }: { children: React.ReactNode }) {
19 | return process.env.NEXT_PUBLIC_ENABLE_POSTHOG ? (
20 | {children}
21 | ) : (
22 | children
23 | )
24 | }
25 |
26 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
27 | return {children}
28 | }
29 |
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "new-york",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.ts",
8 | "css": "app/globals.css",
9 | "baseColor": "zinc",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils",
16 | "ui": "@/components/ui",
17 | "lib": "@/lib",
18 | "hooks": "@/hooks"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/components/DeleteConfirmation/index.css:
--------------------------------------------------------------------------------
1 | .delete-confirmation-overlay {
2 | background-color: rgba(0, 0, 0, 0.5);
3 | position: fixed;
4 | inset: 0;
5 | animation: overlayShow 150ms cubic-bezier(0.16, 1, 0.3, 1);
6 | z-index: 999999;
7 | }
8 |
9 | .delete-confirmation-content {
10 | background-color: white;
11 | border-radius: 6px;
12 | box-shadow: 0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2);
13 | position: fixed;
14 | top: 50%;
15 | left: 50%;
16 | transform: translate(-50%, -50%);
17 | width: 90vw;
18 | max-width: 400px;
19 | max-height: 85vh;
20 | padding: 24px;
21 | animation: contentShow 150ms cubic-bezier(0.16, 1, 0.3, 1);
22 | z-index: 999999;
23 | }
24 |
25 | .delete-confirmation-title {
26 | font-size: 18px;
27 | font-weight: 600;
28 | color: #1a1a1a;
29 | margin-bottom: 8px;
30 | }
31 |
32 | .delete-confirmation-description {
33 | font-size: 14px;
34 | color: #666;
35 | margin-bottom: 24px;
36 | line-height: 1.5;
37 | }
38 |
39 | .delete-confirmation-buttons {
40 | display: flex;
41 | gap: 12px;
42 | justify-content: flex-end;
43 | }
44 |
45 | .delete-confirmation-button {
46 | border-radius: 4px;
47 | padding: 8px 16px;
48 | font-size: 14px;
49 | font-weight: 500;
50 | cursor: pointer;
51 | border: none;
52 | transition: background-color 0.2s ease;
53 | }
54 |
55 | .delete-confirmation-button.cancel {
56 | background-color: #f5f5f5;
57 | color: #666;
58 | }
59 |
60 | .delete-confirmation-button.cancel:hover {
61 | background-color: #e8e8e8;
62 | }
63 |
64 | .delete-confirmation-button.confirm {
65 | background-color: #dc2626;
66 | color: white;
67 | }
68 |
69 | .delete-confirmation-button.confirm:hover {
70 | background-color: #b91c1c;
71 | }
72 |
73 | @keyframes overlayShow {
74 | from {
75 | opacity: 0;
76 | }
77 | to {
78 | opacity: 1;
79 | }
80 | }
81 |
82 | @keyframes contentShow {
83 | from {
84 | opacity: 0;
85 | transform: translate(-50%, -48%) scale(0.96);
86 | }
87 | to {
88 | opacity: 1;
89 | transform: translate(-50%, -50%) scale(1);
90 | }
91 | }
--------------------------------------------------------------------------------
/components/DeleteConfirmation/index.tsx:
--------------------------------------------------------------------------------
1 | import * as AlertDialog from '@radix-ui/react-alert-dialog';
2 | import './index.css';
3 |
4 | interface DeleteConfirmationProps {
5 | open: boolean;
6 | onOpenChange: (open: boolean) => void;
7 | onConfirm: () => void;
8 | title?: string;
9 | description?: string;
10 | cancelText?: string;
11 | confirmText?: string;
12 | }
13 |
14 | const DeleteConfirmation = ({
15 | open,
16 | onOpenChange,
17 | onConfirm,
18 | title = 'Confirm Delete',
19 | description = 'This action will permanently delete this item. Do you want to continue?',
20 | cancelText = 'Cancel',
21 | confirmText = 'Delete'
22 | }: DeleteConfirmationProps) => {
23 | return (
24 |
25 |
26 |
27 |
28 |
29 | {title}
30 |
31 |
32 | {description}
33 |
34 |
35 |
36 |
37 | {cancelText}
38 |
39 |
40 |
41 |
45 | {confirmText}
46 |
47 |
48 |
49 |
50 |
51 |
52 | );
53 | }
54 |
55 | export default DeleteConfirmation
--------------------------------------------------------------------------------
/components/Dialog/Add/index.css:
--------------------------------------------------------------------------------
1 | .dialog-overlay {
2 | background-color: rgba(0, 0, 0, 0.5);
3 | position: fixed;
4 | inset: 0;
5 | animation: overlayShow 150ms cubic-bezier(0.16, 1, 0.3, 1);
6 | z-index: 999999;
7 | }
8 |
9 | .dialog-content {
10 | background-color: white;
11 | border-radius: 6px;
12 | box-shadow: 0px 10px 38px -10px rgba(22, 23, 24, 0.35),
13 | 0px 10px 20px -15px rgba(22, 23, 24, 0.2);
14 | position: fixed;
15 | top: 50%;
16 | left: 50%;
17 | transform: translate(-50%, -50%);
18 | width: 90vw;
19 | max-width: 450px;
20 | max-height: 85vh;
21 | padding: 24px;
22 | animation: contentShow 150ms cubic-bezier(0.16, 1, 0.3, 1);
23 | z-index: 999999;
24 | }
25 |
26 | .dialog-title {
27 | font-size: 18px;
28 | font-weight: 600;
29 | color: #1a1a1a;
30 | margin-bottom: 20px;
31 | }
32 |
33 | .form-root {
34 | display: flex;
35 | flex-direction: column;
36 | gap: 20px;
37 | }
38 |
39 | .form-field {
40 | display: flex;
41 | flex-direction: column;
42 | gap: 8px;
43 | }
44 |
45 | .field-row {
46 | display: flex;
47 | justify-content: space-between;
48 | align-items: center;
49 | }
50 |
51 | .form-label {
52 | font-size: 14px;
53 | font-weight: 500;
54 | color: #1a1a1a;
55 | }
56 |
57 | .form-message {
58 | font-size: 13px;
59 | color: #ef4444;
60 | opacity: 0;
61 | }
62 |
63 | .form-field[data-invalid] .form-message {
64 | opacity: 1;
65 | }
66 |
67 | .form-input,
68 | .form-textarea {
69 | width: 100%;
70 | border: 1px solid #e5e7eb;
71 | border-radius: 4px;
72 | padding: 8px 12px;
73 | font-size: 14px;
74 | color: #1a1a1a;
75 | background: white;
76 | transition: all 0.2s;
77 | }
78 |
79 | .form-input:focus,
80 | .form-textarea:focus {
81 | outline: none;
82 | border-color: #3b82f6;
83 | box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1);
84 | }
85 |
86 | .form-field[data-invalid] .form-input,
87 | .form-field[data-invalid] .form-textarea {
88 | border-color: #ef4444;
89 | }
90 |
91 | .form-buttons {
92 | display: flex;
93 | justify-content: flex-end;
94 | gap: 12px;
95 | margin-top: 8px;
96 | }
97 |
98 | .button {
99 | padding: 8px 16px;
100 | border-radius: 4px;
101 | font-size: 14px;
102 | font-weight: 500;
103 | cursor: pointer;
104 | transition: all 0.2s;
105 | border: none;
106 | }
107 |
108 | .button.cancel {
109 | background-color: #f3f4f6;
110 | color: #4b5563;
111 | }
112 |
113 | .button.cancel:hover {
114 | background-color: #e5e7eb;
115 | }
116 |
117 | .button.submit {
118 | background-color: #3b82f6;
119 | color: white;
120 | }
121 |
122 | .button.submit:hover {
123 | background-color: #2563eb;
124 | }
125 |
126 | @keyframes overlayShow {
127 | from {
128 | opacity: 0;
129 | }
130 | to {
131 | opacity: 1;
132 | }
133 | }
134 |
135 | @keyframes contentShow {
136 | from {
137 | opacity: 0;
138 | transform: translate(-50%, -48%) scale(0.96);
139 | }
140 | to {
141 | opacity: 1;
142 | transform: translate(-50%, -50%) scale(1);
143 | }
144 | }
--------------------------------------------------------------------------------
/components/Dialog/img/config_s.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/components/Dialog/index.css:
--------------------------------------------------------------------------------
1 |
2 | .overlay {
3 | position: fixed;
4 | top: 0;
5 | left: 0;
6 | right: 0;
7 | bottom: 0;
8 | background: rgba(0, 0, 0, 0.5);
9 | z-index: 1000;
10 | }
11 |
12 | .content {
13 | position: fixed;
14 | top: 50%;
15 | left: 50%;
16 | transform: translate(-50%, -50%);
17 | background: white;
18 | padding: 20px;
19 | border-radius: 8px;
20 | z-index: 1001;
21 | width: 300px;
22 | }
23 |
--------------------------------------------------------------------------------
/components/auth-dialog.tsx:
--------------------------------------------------------------------------------
1 | import AuthForm from './auth-form'
2 | import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog'
3 | import { AuthViewType } from '@/lib/auth'
4 | import { VisuallyHidden } from '@radix-ui/react-visually-hidden'
5 | import { SupabaseClient } from '@supabase/supabase-js'
6 |
7 | export function AuthDialog({
8 | open,
9 | setOpen,
10 | supabase,
11 | view,
12 | }: {
13 | open: boolean
14 | setOpen: (open: boolean) => void
15 | supabase: SupabaseClient
16 | view: AuthViewType
17 | }) {
18 | return (
19 |
20 |
21 |
22 | Sign in to E2B
23 |
24 |
25 |
26 |
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/components/auth-form.tsx:
--------------------------------------------------------------------------------
1 | import Logo from './logo'
2 | import { AuthViewType } from '@/lib/auth'
3 | import { Auth } from '@supabase/auth-ui-react'
4 | import { ThemeSupa } from '@supabase/auth-ui-shared'
5 | import { SupabaseClient } from '@supabase/supabase-js'
6 |
7 | function AuthForm({
8 | supabase,
9 | view = 'sign_in',
10 | }: {
11 | supabase: SupabaseClient
12 | view: AuthViewType
13 | }) {
14 | return (
15 |
16 |
17 |
18 |
19 |
20 | Sign in to Fragments
21 |
22 |
67 |
68 | )
69 | }
70 |
71 | export default AuthForm
72 |
--------------------------------------------------------------------------------
/components/chat.css:
--------------------------------------------------------------------------------
1 | .msgByUse{
2 | align-self: flex-end;
3 | }
--------------------------------------------------------------------------------
/components/chat.tsx:
--------------------------------------------------------------------------------
1 | import { Message } from '@/lib/messages'
2 | import { FragmentSchema } from '@/lib/schema'
3 | import { ExecutionResult } from '@/lib/types'
4 | import { DeepPartial } from 'ai'
5 | import { Loader2Icon, LoaderIcon, Terminal } from 'lucide-react'
6 | import { useEffect } from 'react'
7 | import ReactMarkdown from 'react-markdown'
8 | import './chat.css'
9 |
10 | export function Chat({
11 | messages,
12 | isLoading,
13 | setCurrentPreview,
14 | }: {
15 | messages: Message[]
16 | isLoading: boolean
17 | setCurrentPreview: (preview: {
18 | fragment: DeepPartial | undefined
19 | result: ExecutionResult | undefined
20 | }) => void
21 | }) {
22 | useEffect(() => {
23 | const chatContainer = document.getElementById('chat-container')
24 | if (chatContainer) {
25 | chatContainer.scrollTop = chatContainer.scrollHeight
26 | }
27 |
28 | }, [JSON.stringify(messages)])
29 |
30 | return (
31 |
35 | {messages.map((message: Message, index: number) => {
36 | return (
37 | (
38 |
43 | {message.content.map((content, id) => {
44 | if (content.type === 'image') {
45 | return (
46 |
52 | )
53 | }
54 | if(message.role == 'user'){
55 | return content.text
56 | }
57 | if (content.type === 'text') {
58 | return (
59 |
{content.text}
60 | )
61 | }
62 | })}
63 |
64 | {/* 生成代码区域 */}
65 | {message.object && (
66 |
68 | setCurrentPreview({
69 | fragment: message.object,
70 | result: message.result,
71 | })
72 | }
73 | className="py-2 pl-2 w-full md:w-max flex items-center border rounded-xl select-none hover:bg-white dark:hover:bg-white/5 hover:cursor-pointer"
74 | >
75 |
76 |
77 |
78 |
79 |
80 | {message.object.title}
81 |
82 |
83 | Click to see fragment
84 |
85 |
86 |
87 | )}
88 |
89 | )
90 | )
91 | })}
92 | {isLoading && (
93 |
94 |
95 | Generating...
96 |
97 | )}
98 |
99 | )
100 | }
101 |
--------------------------------------------------------------------------------
/components/code-theme.css:
--------------------------------------------------------------------------------
1 | /* Prism.js GitHub Dark Theme */
2 |
3 | code[class*='language-'],
4 | pre[class*='language-'] {
5 | font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Mono',
6 | 'Droid Sans Mono', 'Source Code Pro', monospace;
7 | text-align: left;
8 | white-space: pre;
9 | word-spacing: normal;
10 | word-break: normal;
11 | word-wrap: normal;
12 | line-height: 1.5;
13 | tab-size: 4;
14 | hyphens: none;
15 | }
16 |
17 | code[class*='language-'],
18 | pre[class*='language-'] {
19 | color: #24292e;
20 | }
21 |
22 | .token.comment,
23 | .token.prolog,
24 | .token.doctype,
25 | .token.cdata {
26 | color: #6a737d;
27 | }
28 |
29 | .token.punctuation {
30 | color: #24292e;
31 | }
32 |
33 | .token.namespace {
34 | opacity: 0.7;
35 | }
36 |
37 | .token.property,
38 | .token.tag,
39 | .token.boolean,
40 | .token.number,
41 | .token.constant,
42 | .token.symbol {
43 | color: #005cc5;
44 | }
45 |
46 | .token.selector,
47 | .token.attr-name,
48 | .token.string,
49 | .token.char,
50 | .token.builtin {
51 | color: #032f62;
52 | }
53 |
54 | .token.operator,
55 | .token.entity,
56 | .token.url,
57 | .language-css .token.string,
58 | .style .token.string {
59 | color: #d73a49;
60 | background: transparent;
61 | }
62 |
63 | .token.atrule,
64 | .token.attr-value,
65 | .token.keyword {
66 | color: #d73a49;
67 | }
68 |
69 | .token.function,
70 | .token.class-name {
71 | color: #6f42c1;
72 | }
73 |
74 | .token.regex,
75 | .token.important,
76 | .token.variable {
77 | color: #e36209;
78 | }
79 |
80 | .token.important,
81 | .token.bold {
82 | font-weight: bold;
83 | }
84 |
85 | .token.italic {
86 | font-style: italic;
87 | }
88 |
89 | .token.entity {
90 | cursor: help;
91 | }
92 |
93 | /* Dark */
94 | .dark code[class*='language-'],
95 | .dark pre[class*='language-'] {
96 | color: #e1e4e8;
97 | }
98 |
99 | .dark .token.comment,
100 | .dark .token.prolog,
101 | .dark .token.doctype,
102 | .dark .token.cdata {
103 | color: #6a737d; /* comment */
104 | }
105 |
106 | .dark .token.punctuation {
107 | color: #e1e4e8; /* editor.foreground */
108 | }
109 |
110 | .dark .token.namespace {
111 | opacity: 0.7;
112 | }
113 |
114 | .dark .token.property,
115 | .dark .token.tag,
116 | .dark .token.boolean,
117 | .dark .token.number,
118 | .dark .token.constant,
119 | .dark .token.symbol,
120 | .dark .token.deleted {
121 | color: #79b8ff; /* constant, entity.name.constant, variable.other.constant */
122 | }
123 |
124 | .dark .token.selector,
125 | .dark .token.attr-name,
126 | .dark .token.string,
127 | .dark .token.char,
128 | .dark .token.builtin,
129 | .dark .token.inserted {
130 | color: #9ecbff; /* string */
131 | }
132 |
133 | .dark .token.operator,
134 | .dark .token.entity,
135 | .dark .token.url,
136 | .dark .language-css .token.string,
137 | .dark .style .token.string {
138 | color: #e1e4e8; /* editor.foreground */
139 | }
140 |
141 | .dark .token.atrule,
142 | .dark .token.attr-value,
143 | .dark .token.keyword {
144 | color: #f97583; /* keyword */
145 | }
146 |
147 | .dark .token.function,
148 | .dark .token.class-name {
149 | color: #b392f0; /* entity, entity.name */
150 | }
151 |
152 | .dark .token.regex,
153 | .dark .token.important,
154 | .dark .token.variable {
155 | color: #ffab70; /* variable */
156 | }
157 |
158 | .dark .token.important,
159 | .dark .token.bold {
160 | font-weight: bold;
161 | }
162 |
163 | .dark .token.italic {
164 | font-style: italic;
165 | }
166 |
167 | .dark .token.entity {
168 | cursor: help;
169 | }
170 |
--------------------------------------------------------------------------------
/components/code-view.tsx:
--------------------------------------------------------------------------------
1 | // import "prismjs/plugins/line-numbers/prism-line-numbers.js";
2 | // import "prismjs/plugins/line-numbers/prism-line-numbers.css";
3 | import './code-theme.css'
4 | import Prism from 'prismjs'
5 | import 'prismjs/components/prism-javascript'
6 | import 'prismjs/components/prism-jsx'
7 | import 'prismjs/components/prism-python'
8 | import 'prismjs/components/prism-tsx'
9 | import 'prismjs/components/prism-typescript'
10 | import { useEffect } from 'react'
11 |
12 | export function CodeView({ code, lang }: { code: string; lang: string }) {
13 | useEffect(() => {
14 | Prism.highlightAll()
15 | }, [code])
16 |
17 | return (
18 |
27 | {code}
28 |
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/components/deploy-dialog.tsx:
--------------------------------------------------------------------------------
1 | import Logo from './logo'
2 | import { CopyButton } from './ui/copy-button'
3 | import {
4 | Select,
5 | SelectContent,
6 | SelectGroup,
7 | SelectItem,
8 | SelectLabel,
9 | SelectTrigger,
10 | SelectValue,
11 | } from './ui/select'
12 | import { publish } from '@/app/actions/publish'
13 | import { Button } from '@/components/ui/button'
14 | import {
15 | DropdownMenu,
16 | DropdownMenuContent,
17 | DropdownMenuTrigger,
18 | } from '@/components/ui/dropdown-menu'
19 | import { Input } from '@/components/ui/input'
20 | import { Duration } from '@/lib/duration'
21 | import { usePostHog } from 'posthog-js/react'
22 | import { useEffect, useState } from 'react'
23 |
24 | export function DeployDialog({
25 | url,
26 | sbxId,
27 | apiKey,
28 | }: {
29 | url: string
30 | sbxId: string
31 | apiKey: string | undefined
32 | }) {
33 | const posthog = usePostHog()
34 |
35 | const [publishedURL, setPublishedURL] = useState(null)
36 | const [duration, setDuration] = useState(null)
37 |
38 | useEffect(() => {
39 | setPublishedURL(null)
40 | }, [url])
41 |
42 | async function publishURL(e: React.FormEvent) {
43 | e.preventDefault()
44 | const { url: publishedURL } = await publish(
45 | url,
46 | sbxId,
47 | duration as Duration,
48 | apiKey,
49 | )
50 | setPublishedURL(publishedURL)
51 | posthog.capture('publish_url', {
52 | url: publishedURL,
53 | })
54 | }
55 |
56 | return (
57 |
58 | {/*
59 |
60 |
61 | Deploy to E2B
62 |
63 | */}
64 |
65 | Deploy to E2B
66 |
67 | Deploying the fragment will make it publicly accessible to others via
68 | link.
69 |
70 |
71 | The fragment will be available up until the expiration date you choose
72 | and you'll be billed based on our{' '}
73 |
78 | Compute pricing
79 |
80 | .
81 |
82 |
83 | All new accounts receive $100 worth of compute credits. Upgrade to{' '}
84 |
89 | Pro tier
90 | {' '}
91 | for longer expiration.
92 |
93 |
124 |
125 |
126 | )
127 | }
128 |
--------------------------------------------------------------------------------
/components/fragment-code.tsx:
--------------------------------------------------------------------------------
1 | import { CodeView } from './code-view'
2 | import { Button } from './ui/button'
3 | import { CopyButton } from './ui/copy-button'
4 | import {
5 | Tooltip,
6 | TooltipContent,
7 | TooltipProvider,
8 | TooltipTrigger,
9 | } from '@/components/ui/tooltip'
10 | import { Download, FileText } from 'lucide-react'
11 | import { useState } from 'react'
12 |
13 | export function FragmentCode({
14 | files,
15 | }: {
16 | files: { name: string; content: string }[]
17 | }) {
18 | const [currentFile, setCurrentFile] = useState(files[0].name)
19 | const currentFileContent = files.find(
20 | (file) => file.name === currentFile,
21 | )?.content
22 |
23 | function download(filename: string, content: string) {
24 | const blob = new Blob([content], { type: 'text/plain' })
25 | const url = window.URL.createObjectURL(blob)
26 | const a = document.createElement('a')
27 | a.style.display = 'none'
28 | a.href = url
29 | a.download = filename
30 | document.body.appendChild(a)
31 | a.click()
32 | window.URL.revokeObjectURL(url)
33 | document.body.removeChild(a)
34 | }
35 |
36 | return (
37 |
38 |
39 |
40 | {files.map((file) => (
41 |
setCurrentFile(file.name)}
47 | >
48 |
49 | {file.name}
50 |
51 | ))}
52 |
53 |
54 |
55 |
56 |
57 |
61 |
62 | Copy
63 |
64 |
65 |
66 |
67 |
68 |
73 | download(currentFile, currentFileContent || '')
74 | }
75 | >
76 |
77 |
78 |
79 | Download
80 |
81 |
82 |
83 |
84 |
85 |
89 |
90 |
91 | )
92 | }
93 |
--------------------------------------------------------------------------------
/components/fragment-interpreter.tsx:
--------------------------------------------------------------------------------
1 | import { Alert, AlertTitle, AlertDescription } from '@/components/ui/alert'
2 | import { ExecutionResultInterpreter } from '@/lib/types'
3 | import { Terminal } from 'lucide-react'
4 | import Image from 'next/image'
5 |
6 | function LogsOutput({
7 | stdout,
8 | stderr,
9 | }: {
10 | stdout: string[]
11 | stderr: string[]
12 | }) {
13 | if (stdout.length === 0 && stderr.length === 0) return null
14 |
15 | return (
16 |
17 | {stdout &&
18 | stdout.length > 0 &&
19 | stdout.map((out: string, index: number) => (
20 |
21 | {out}
22 |
23 | ))}
24 | {stderr &&
25 | stderr.length > 0 &&
26 | stderr.map((err: string, index: number) => (
27 |
28 | {err}
29 |
30 | ))}
31 |
32 | )
33 | }
34 |
35 | export function FragmentInterpreter({
36 | result,
37 | }: {
38 | result: ExecutionResultInterpreter
39 | }) {
40 | const { cellResults, stdout, stderr, runtimeError } = result
41 |
42 | // The AI-generated code experienced runtime error
43 | if (runtimeError) {
44 | const { name, value, traceback } = runtimeError
45 | return (
46 |
47 |
48 |
49 |
50 | {name}: {value}
51 |
52 |
53 | {traceback}
54 |
55 |
56 |
57 | )
58 | }
59 |
60 | // Cell results can contain text, pdfs, images, and code (html, latex, json)
61 | // TODO: Show all results
62 | // TODO: Check other formats than `png`
63 | if (cellResults.length > 0) {
64 | const imgInBase64 = cellResults[0].png
65 | return (
66 |
77 | )
78 | }
79 |
80 | // No cell results, but there is stdout or stderr
81 | if (stdout.length > 0 || stderr.length > 0) {
82 | return
83 | }
84 |
85 | return No output or logs
86 | }
87 |
--------------------------------------------------------------------------------
/components/fragment-preview.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { FragmentInterpreter } from './fragment-interpreter'
4 | import { FragmentWeb } from './fragment-web'
5 | import { ExecutionResult } from '@/lib/types'
6 |
7 | export function FragmentPreview({ result }: { result: ExecutionResult }) {
8 | if (result.template === 'code-interpreter-v1') {
9 | return
10 | }
11 |
12 | return
13 | }
14 |
--------------------------------------------------------------------------------
/components/fragment-web.tsx:
--------------------------------------------------------------------------------
1 | import { CopyButton } from './ui/copy-button'
2 | import { Button } from '@/components/ui/button'
3 | import {
4 | Tooltip,
5 | TooltipContent,
6 | TooltipProvider,
7 | TooltipTrigger,
8 | } from '@/components/ui/tooltip'
9 | import { ExecutionResultWeb } from '@/lib/types'
10 | import { RotateCw } from 'lucide-react'
11 | import { useState } from 'react'
12 |
13 | export function FragmentWeb({ result }: { result: ExecutionResultWeb }) {
14 | const [iframeKey, setIframeKey] = useState(0)
15 | if (!result) return null
16 |
17 | function refreshIframe() {
18 | setIframeKey((prevKey) => prevKey + 1)
19 | }
20 |
21 | return (
22 |
23 |
30 |
31 |
32 |
33 |
34 |
35 |
40 |
41 |
42 |
43 | Refresh
44 |
45 |
46 |
47 | {result.url}
48 |
49 |
50 |
51 |
52 |
57 |
58 | Copy URL
59 |
60 |
61 |
62 |
63 |
64 | )
65 | }
66 |
--------------------------------------------------------------------------------
/components/logo.tsx:
--------------------------------------------------------------------------------
1 | export type LogoStyle = 'e2b' | 'fragments'
2 |
3 | export default function Logo({
4 | style = 'e2b',
5 | ...props
6 | }: { style?: LogoStyle } & React.SVGProps) {
7 | return style === 'fragments' ? (
8 |
14 |
18 |
19 | ) : (
20 |
26 |
32 |
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/components/preview.tsx:
--------------------------------------------------------------------------------
1 | import { FragmentCode } from './fragment-code'
2 | import { FragmentPreview } from './fragment-preview'
3 | import { DeployDialog } from './deploy-dialog'
4 | import { Button } from '@/components/ui/button'
5 | import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
6 | import {
7 | Tooltip,
8 | TooltipContent,
9 | TooltipProvider,
10 | TooltipTrigger,
11 | } from '@/components/ui/tooltip'
12 | import { FragmentSchema } from '@/lib/schema'
13 | import { ExecutionResult } from '@/lib/types'
14 | import { DeepPartial } from 'ai'
15 | import { ChevronsRight, LoaderCircle } from 'lucide-react'
16 | import { Dispatch, SetStateAction } from 'react'
17 |
18 | export function Preview({
19 | apiKey,
20 | selectedTab,
21 | onSelectedTabChange,
22 | isChatLoading,
23 | isPreviewLoading,
24 | fragment,
25 | result,
26 | onClose,
27 | }: {
28 | apiKey: string | undefined
29 | selectedTab: 'code' | 'fragment'
30 | onSelectedTabChange: Dispatch>
31 | isChatLoading: boolean
32 | isPreviewLoading: boolean
33 | fragment?: DeepPartial
34 | result?: ExecutionResult
35 | onClose: () => void
36 | }) {
37 | if (!fragment) {
38 | return null
39 | }
40 |
41 | const isLinkAvailable = result?.template !== 'code-interpreter-v1'
42 |
43 | return (
44 |
45 |
48 | onSelectedTabChange(value as 'code' | 'fragment')
49 | }
50 | className="h-full flex flex-col items-start justify-start"
51 | >
52 |
53 |
54 |
55 |
56 |
62 |
63 |
64 |
65 | Close sidebar
66 |
67 |
68 |
69 |
70 |
74 | {isChatLoading && (
75 |
79 | )}
80 | Code
81 |
82 |
87 | Preview
88 | {isPreviewLoading && (
89 |
93 | )}
94 |
95 |
96 |
97 | {result && (
98 |
99 | {isLinkAvailable && (
100 |
105 | )}
106 |
107 | )}
108 |
109 | {fragment && (
110 |
111 |
112 | {fragment.code && fragment.file_path && (
113 |
121 | )}
122 |
123 |
124 | {result && }
125 |
126 |
127 | )}
128 |
129 |
130 | )
131 | }
132 |
--------------------------------------------------------------------------------
/components/ui/alert.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const alertVariants = cva(
7 | "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
8 | {
9 | variants: {
10 | variant: {
11 | default: "bg-background text-foreground",
12 | destructive:
13 | "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
14 | },
15 | },
16 | defaultVariants: {
17 | variant: "default",
18 | },
19 | }
20 | )
21 |
22 | const Alert = React.forwardRef<
23 | HTMLDivElement,
24 | React.HTMLAttributes & VariantProps
25 | >(({ className, variant, ...props }, ref) => (
26 |
32 | ))
33 | Alert.displayName = "Alert"
34 |
35 | const AlertTitle = React.forwardRef<
36 | HTMLParagraphElement,
37 | React.HTMLAttributes
38 | >(({ className, ...props }, ref) => (
39 |
44 | ))
45 | AlertTitle.displayName = "AlertTitle"
46 |
47 | const AlertDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | AlertDescription.displayName = "AlertDescription"
58 |
59 | export { Alert, AlertTitle, AlertDescription }
60 |
--------------------------------------------------------------------------------
/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/utils"
7 |
8 | const Avatar = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 | ))
21 | Avatar.displayName = AvatarPrimitive.Root.displayName
22 |
23 | const AvatarImage = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => (
27 |
32 | ))
33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName
34 |
35 | const AvatarFallback = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, ...props }, ref) => (
39 |
47 | ))
48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
49 |
50 | export { Avatar, AvatarImage, AvatarFallback }
51 |
--------------------------------------------------------------------------------
/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from '@/lib/utils'
2 | import { Slot } from '@radix-ui/react-slot'
3 | import { cva, type VariantProps } from 'class-variance-authority'
4 | import * as React from 'react'
5 |
6 | const buttonVariants = cva(
7 | 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
8 | {
9 | variants: {
10 | variant: {
11 | default:
12 | 'bg-primary text-primary-foreground shadow hover:bg-primary/90',
13 | destructive:
14 | 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
15 | outline:
16 | 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
17 | secondary:
18 | 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
19 | ghost: 'hover:bg-accent hover:text-accent-foreground',
20 | link: 'text-primary underline-offset-4 hover:underline',
21 | },
22 | size: {
23 | default: 'h-9 px-4 py-2',
24 | sm: 'h-8 rounded-md px-3 text-xs',
25 | lg: 'h-10 rounded-md px-8',
26 | icon: 'h-9 w-9',
27 | },
28 | },
29 | defaultVariants: {
30 | variant: 'default',
31 | size: 'default',
32 | },
33 | },
34 | )
35 |
36 | export interface ButtonProps
37 | extends React.ButtonHTMLAttributes,
38 | VariantProps {
39 | asChild?: boolean
40 | }
41 |
42 | const Button = React.forwardRef(
43 | ({ className, variant, size, asChild = false, ...props }, ref) => {
44 | const Comp = asChild ? Slot : 'button'
45 | return (
46 |
51 | )
52 | },
53 | )
54 | Button.displayName = 'Button'
55 |
56 | export { Button, buttonVariants }
57 |
--------------------------------------------------------------------------------
/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
17 | ))
18 | Card.displayName = "Card"
19 |
20 | const CardHeader = React.forwardRef<
21 | HTMLDivElement,
22 | React.HTMLAttributes
23 | >(({ className, ...props }, ref) => (
24 |
29 | ))
30 | CardHeader.displayName = "CardHeader"
31 |
32 | const CardTitle = React.forwardRef<
33 | HTMLParagraphElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
41 | ))
42 | CardTitle.displayName = "CardTitle"
43 |
44 | const CardDescription = React.forwardRef<
45 | HTMLParagraphElement,
46 | React.HTMLAttributes
47 | >(({ className, ...props }, ref) => (
48 |
53 | ))
54 | CardDescription.displayName = "CardDescription"
55 |
56 | const CardContent = React.forwardRef<
57 | HTMLDivElement,
58 | React.HTMLAttributes
59 | >(({ className, ...props }, ref) => (
60 |
61 | ))
62 | CardContent.displayName = "CardContent"
63 |
64 | const CardFooter = React.forwardRef<
65 | HTMLDivElement,
66 | React.HTMLAttributes
67 | >(({ className, ...props }, ref) => (
68 |
73 | ))
74 | CardFooter.displayName = "CardFooter"
75 |
76 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
77 |
--------------------------------------------------------------------------------
/components/ui/copy-button.tsx:
--------------------------------------------------------------------------------
1 | import { Button, ButtonProps } from './button'
2 | import { Check, Copy } from 'lucide-react'
3 | import { useState, forwardRef } from 'react'
4 |
5 | export const CopyButton = forwardRef<
6 | HTMLButtonElement,
7 | {
8 | variant?: ButtonProps['variant']
9 | content: string
10 | onCopy?: () => void
11 | className?: string
12 | }
13 | >(({ variant = 'ghost', content, onCopy, className, ...props }, ref) => {
14 | const [copied, setCopied] = useState(false)
15 |
16 | function copy(content: string) {
17 | setCopied(true)
18 | navigator.clipboard.writeText(content)
19 | setTimeout(() => setCopied(false), 1000)
20 | onCopy?.()
21 | }
22 |
23 | return (
24 | copy(content)}
31 | >
32 | {copied ? : }
33 |
34 | )
35 | })
36 |
37 | CopyButton.displayName = 'CopyButton'
38 |
--------------------------------------------------------------------------------
/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 { Cross2Icon } from "@radix-ui/react-icons"
6 |
7 | import { cn } from "@/lib/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<
18 | React.ElementRef,
19 | React.ComponentPropsWithoutRef
20 | >(({ className, ...props }, ref) => (
21 |
29 | ))
30 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
31 |
32 | const DialogContent = React.forwardRef<
33 | React.ElementRef,
34 | React.ComponentPropsWithoutRef
35 | >(({ className, children, ...props }, ref) => (
36 |
37 |
38 |
46 | {children}
47 |
48 |
49 | Close
50 |
51 |
52 |
53 | ))
54 | DialogContent.displayName = DialogPrimitive.Content.displayName
55 |
56 | const DialogHeader = ({
57 | className,
58 | ...props
59 | }: React.HTMLAttributes) => (
60 |
67 | )
68 | DialogHeader.displayName = "DialogHeader"
69 |
70 | const DialogFooter = ({
71 | className,
72 | ...props
73 | }: React.HTMLAttributes) => (
74 |
81 | )
82 | DialogFooter.displayName = "DialogFooter"
83 |
84 | const DialogTitle = React.forwardRef<
85 | React.ElementRef,
86 | React.ComponentPropsWithoutRef
87 | >(({ className, ...props }, ref) => (
88 |
96 | ))
97 | DialogTitle.displayName = DialogPrimitive.Title.displayName
98 |
99 | const DialogDescription = React.forwardRef<
100 | React.ElementRef,
101 | React.ComponentPropsWithoutRef
102 | >(({ className, ...props }, ref) => (
103 |
108 | ))
109 | DialogDescription.displayName = DialogPrimitive.Description.displayName
110 |
111 | export {
112 | Dialog,
113 | DialogPortal,
114 | DialogOverlay,
115 | DialogTrigger,
116 | DialogClose,
117 | DialogContent,
118 | DialogHeader,
119 | DialogFooter,
120 | DialogTitle,
121 | DialogDescription,
122 | }
123 |
--------------------------------------------------------------------------------
/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface InputProps
6 | extends React.InputHTMLAttributes {}
7 |
8 | const Input = React.forwardRef(
9 | ({ className, type, ...props }, ref) => {
10 | return (
11 |
20 | )
21 | }
22 | )
23 | Input.displayName = "Input"
24 |
25 | export { Input }
26 |
--------------------------------------------------------------------------------
/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as LabelPrimitive from "@radix-ui/react-label"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const labelVariants = cva(
10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11 | )
12 |
13 | const Label = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef &
16 | VariantProps
17 | >(({ className, ...props }, ref) => (
18 |
23 | ))
24 | Label.displayName = LabelPrimitive.Root.displayName
25 |
26 | export { Label }
27 |
--------------------------------------------------------------------------------
/components/ui/select.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import {
5 | CaretSortIcon,
6 | CheckIcon,
7 | ChevronDownIcon,
8 | ChevronUpIcon,
9 | } from "@radix-ui/react-icons"
10 | import * as SelectPrimitive from "@radix-ui/react-select"
11 |
12 | import { cn } from "@/lib/utils"
13 |
14 | const Select = SelectPrimitive.Root
15 |
16 | const SelectGroup = SelectPrimitive.Group
17 |
18 | const SelectValue = SelectPrimitive.Value
19 |
20 | const SelectTrigger = React.forwardRef<
21 | React.ElementRef,
22 | React.ComponentPropsWithoutRef
23 | >(({ className, children, ...props }, ref) => (
24 | span]:line-clamp-1",
28 | className
29 | )}
30 | {...props}
31 | >
32 | {children}
33 |
34 |
35 |
36 |
37 | ))
38 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
39 |
40 | const SelectScrollUpButton = React.forwardRef<
41 | React.ElementRef,
42 | React.ComponentPropsWithoutRef
43 | >(({ className, ...props }, ref) => (
44 |
52 |
53 |
54 | ))
55 | SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
56 |
57 | const SelectScrollDownButton = React.forwardRef<
58 | React.ElementRef,
59 | React.ComponentPropsWithoutRef
60 | >(({ className, ...props }, ref) => (
61 |
69 |
70 |
71 | ))
72 | SelectScrollDownButton.displayName =
73 | SelectPrimitive.ScrollDownButton.displayName
74 |
75 | const SelectContent = React.forwardRef<
76 | React.ElementRef,
77 | React.ComponentPropsWithoutRef
78 | >(({ className, children, position = "popper", ...props }, ref) => (
79 |
80 |
91 |
92 |
99 | {children}
100 |
101 |
102 |
103 |
104 | ))
105 | SelectContent.displayName = SelectPrimitive.Content.displayName
106 |
107 | const SelectLabel = React.forwardRef<
108 | React.ElementRef,
109 | React.ComponentPropsWithoutRef
110 | >(({ className, ...props }, ref) => (
111 |
116 | ))
117 | SelectLabel.displayName = SelectPrimitive.Label.displayName
118 |
119 | const SelectItem = React.forwardRef<
120 | React.ElementRef,
121 | React.ComponentPropsWithoutRef
122 | >(({ className, children, ...props }, ref) => (
123 |
131 |
132 |
133 |
134 |
135 |
136 | {children}
137 |
138 | ))
139 | SelectItem.displayName = SelectPrimitive.Item.displayName
140 |
141 | const SelectSeparator = React.forwardRef<
142 | React.ElementRef,
143 | React.ComponentPropsWithoutRef
144 | >(({ className, ...props }, ref) => (
145 |
150 | ))
151 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName
152 |
153 | export {
154 | Select,
155 | SelectGroup,
156 | SelectValue,
157 | SelectTrigger,
158 | SelectContent,
159 | SelectLabel,
160 | SelectItem,
161 | SelectSeparator,
162 | SelectScrollUpButton,
163 | SelectScrollDownButton,
164 | }
165 |
--------------------------------------------------------------------------------
/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SeparatorPrimitive from "@radix-ui/react-separator"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Separator = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(
12 | (
13 | { className, orientation = "horizontal", decorative = true, ...props },
14 | ref
15 | ) => (
16 |
27 | )
28 | )
29 | Separator.displayName = SeparatorPrimitive.Root.displayName
30 |
31 | export { Separator }
32 |
--------------------------------------------------------------------------------
/components/ui/skeleton.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils"
2 |
3 | function Skeleton({
4 | className,
5 | ...props
6 | }: React.HTMLAttributes) {
7 | return (
8 |
12 | )
13 | }
14 |
15 | export { Skeleton }
16 |
--------------------------------------------------------------------------------
/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/utils"
7 |
8 | const Tabs = TabsPrimitive.Root
9 |
10 | const TabsList = React.forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef
13 | >(({ className, ...props }, ref) => (
14 |
22 | ))
23 | TabsList.displayName = TabsPrimitive.List.displayName
24 |
25 | const TabsTrigger = React.forwardRef<
26 | React.ElementRef,
27 | React.ComponentPropsWithoutRef
28 | >(({ className, ...props }, ref) => (
29 |
37 | ))
38 | TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
39 |
40 | const TabsContent = React.forwardRef<
41 | React.ElementRef,
42 | React.ComponentPropsWithoutRef
43 | >(({ className, ...props }, ref) => (
44 |
52 | ))
53 | TabsContent.displayName = TabsPrimitive.Content.displayName
54 |
55 | export { Tabs, TabsList, TabsTrigger, TabsContent }
56 |
--------------------------------------------------------------------------------
/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface TextareaProps
6 | extends React.TextareaHTMLAttributes {}
7 |
8 | const Textarea = React.forwardRef(
9 | ({ className, ...props }, ref) => {
10 | return (
11 |
19 | )
20 | }
21 | )
22 | Textarea.displayName = "Textarea"
23 |
24 | export { Textarea }
25 |
--------------------------------------------------------------------------------
/components/ui/theme-toggle.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from '@/components/ui/button'
2 | import { MoonIcon, SunIcon } from 'lucide-react'
3 | import { useTheme } from 'next-themes'
4 | import { useState, useEffect, forwardRef } from 'react'
5 |
6 | export const ThemeToggle = forwardRef<
7 | HTMLButtonElement,
8 | {
9 | className?: string
10 | }
11 | >(({ className, ...props }, ref) => {
12 | const { setTheme, theme } = useTheme()
13 | const [mounted, setMounted] = useState(false)
14 |
15 | // useEffect only runs on the client, so now we can safely show the UI
16 | useEffect(() => {
17 | setMounted(true)
18 | }, [])
19 |
20 | if (!mounted) {
21 | return null
22 | }
23 |
24 | return (
25 | setTheme(theme === 'dark' ? 'light' : 'dark')}
32 | >
33 | {theme === 'light' ? (
34 |
35 | ) : (
36 |
37 | )}
38 |
39 | )
40 | })
41 |
42 | ThemeToggle.displayName = 'ThemeToggle'
43 |
--------------------------------------------------------------------------------
/components/ui/toast.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import { Cross2Icon } from "@radix-ui/react-icons"
5 | import * as ToastPrimitives from "@radix-ui/react-toast"
6 | import { cva, type VariantProps } from "class-variance-authority"
7 |
8 | import { cn } from "@/lib/utils"
9 |
10 | const ToastProvider = ToastPrimitives.Provider
11 |
12 | const ToastViewport = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, ...props }, ref) => (
16 |
24 | ))
25 | ToastViewport.displayName = ToastPrimitives.Viewport.displayName
26 |
27 | const toastVariants = cva(
28 | "group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
29 | {
30 | variants: {
31 | variant: {
32 | default: "border bg-background text-foreground",
33 | destructive:
34 | "destructive group border-destructive bg-destructive text-destructive-foreground",
35 | },
36 | },
37 | defaultVariants: {
38 | variant: "default",
39 | },
40 | }
41 | )
42 |
43 | const Toast = React.forwardRef<
44 | React.ElementRef,
45 | React.ComponentPropsWithoutRef &
46 | VariantProps
47 | >(({ className, variant, ...props }, ref) => {
48 | return (
49 |
54 | )
55 | })
56 | Toast.displayName = ToastPrimitives.Root.displayName
57 |
58 | const ToastAction = React.forwardRef<
59 | React.ElementRef,
60 | React.ComponentPropsWithoutRef
61 | >(({ className, ...props }, ref) => (
62 |
70 | ))
71 | ToastAction.displayName = ToastPrimitives.Action.displayName
72 |
73 | const ToastClose = React.forwardRef<
74 | React.ElementRef,
75 | React.ComponentPropsWithoutRef
76 | >(({ className, ...props }, ref) => (
77 |
86 |
87 |
88 | ))
89 | ToastClose.displayName = ToastPrimitives.Close.displayName
90 |
91 | const ToastTitle = React.forwardRef<
92 | React.ElementRef,
93 | React.ComponentPropsWithoutRef
94 | >(({ className, ...props }, ref) => (
95 |
100 | ))
101 | ToastTitle.displayName = ToastPrimitives.Title.displayName
102 |
103 | const ToastDescription = React.forwardRef<
104 | React.ElementRef,
105 | React.ComponentPropsWithoutRef
106 | >(({ className, ...props }, ref) => (
107 |
112 | ))
113 | ToastDescription.displayName = ToastPrimitives.Description.displayName
114 |
115 | type ToastProps = React.ComponentPropsWithoutRef
116 |
117 | type ToastActionElement = React.ReactElement
118 |
119 | export {
120 | type ToastProps,
121 | type ToastActionElement,
122 | ToastProvider,
123 | ToastViewport,
124 | Toast,
125 | ToastTitle,
126 | ToastDescription,
127 | ToastClose,
128 | ToastAction,
129 | }
130 |
--------------------------------------------------------------------------------
/components/ui/toaster.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { useToast } from "@/components/ui/use-toast"
4 | import {
5 | Toast,
6 | ToastClose,
7 | ToastDescription,
8 | ToastProvider,
9 | ToastTitle,
10 | ToastViewport,
11 | } from "@/components/ui/toast"
12 |
13 | export function Toaster() {
14 | const { toasts } = useToast()
15 |
16 | return (
17 |
18 | {toasts.map(function ({ id, title, description, action, ...props }) {
19 | return (
20 |
21 |
22 | {title && {title} }
23 | {description && (
24 | {description}
25 | )}
26 |
27 | {action}
28 |
29 |
30 | )
31 | })}
32 |
33 |
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as TooltipPrimitive from "@radix-ui/react-tooltip"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const TooltipProvider = TooltipPrimitive.Provider
9 |
10 | const Tooltip = TooltipPrimitive.Root
11 |
12 | const TooltipTrigger = TooltipPrimitive.Trigger
13 |
14 | const TooltipContent = React.forwardRef<
15 | React.ElementRef,
16 | React.ComponentPropsWithoutRef
17 | >(({ className, sideOffset = 4, ...props }, ref) => (
18 |
27 | ))
28 | TooltipContent.displayName = TooltipPrimitive.Content.displayName
29 |
30 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
31 |
--------------------------------------------------------------------------------
/components/ui/use-toast.ts:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | // Inspired by react-hot-toast library
4 | import * as React from "react"
5 |
6 | import type {
7 | ToastActionElement,
8 | ToastProps,
9 | } from "@/components/ui/toast"
10 |
11 | const TOAST_LIMIT = 1
12 | const TOAST_REMOVE_DELAY = 1000000
13 |
14 | type ToasterToast = ToastProps & {
15 | id: string
16 | title?: React.ReactNode
17 | description?: React.ReactNode
18 | action?: ToastActionElement
19 | }
20 |
21 | const actionTypes = {
22 | ADD_TOAST: "ADD_TOAST",
23 | UPDATE_TOAST: "UPDATE_TOAST",
24 | DISMISS_TOAST: "DISMISS_TOAST",
25 | REMOVE_TOAST: "REMOVE_TOAST",
26 | } as const
27 |
28 | let count = 0
29 |
30 | function genId() {
31 | count = (count + 1) % Number.MAX_SAFE_INTEGER
32 | return count.toString()
33 | }
34 |
35 | type ActionType = typeof actionTypes
36 |
37 | type Action =
38 | | {
39 | type: ActionType["ADD_TOAST"]
40 | toast: ToasterToast
41 | }
42 | | {
43 | type: ActionType["UPDATE_TOAST"]
44 | toast: Partial
45 | }
46 | | {
47 | type: ActionType["DISMISS_TOAST"]
48 | toastId?: ToasterToast["id"]
49 | }
50 | | {
51 | type: ActionType["REMOVE_TOAST"]
52 | toastId?: ToasterToast["id"]
53 | }
54 |
55 | interface State {
56 | toasts: ToasterToast[]
57 | }
58 |
59 | const toastTimeouts = new Map>()
60 |
61 | const addToRemoveQueue = (toastId: string) => {
62 | if (toastTimeouts.has(toastId)) {
63 | return
64 | }
65 |
66 | const timeout = setTimeout(() => {
67 | toastTimeouts.delete(toastId)
68 | dispatch({
69 | type: "REMOVE_TOAST",
70 | toastId: toastId,
71 | })
72 | }, TOAST_REMOVE_DELAY)
73 |
74 | toastTimeouts.set(toastId, timeout)
75 | }
76 |
77 | export const reducer = (state: State, action: Action): State => {
78 | switch (action.type) {
79 | case "ADD_TOAST":
80 | return {
81 | ...state,
82 | toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
83 | }
84 |
85 | case "UPDATE_TOAST":
86 | return {
87 | ...state,
88 | toasts: state.toasts.map((t) =>
89 | t.id === action.toast.id ? { ...t, ...action.toast } : t
90 | ),
91 | }
92 |
93 | case "DISMISS_TOAST": {
94 | const { toastId } = action
95 |
96 | // ! Side effects ! - This could be extracted into a dismissToast() action,
97 | // but I'll keep it here for simplicity
98 | if (toastId) {
99 | addToRemoveQueue(toastId)
100 | } else {
101 | state.toasts.forEach((toast) => {
102 | addToRemoveQueue(toast.id)
103 | })
104 | }
105 |
106 | return {
107 | ...state,
108 | toasts: state.toasts.map((t) =>
109 | t.id === toastId || toastId === undefined
110 | ? {
111 | ...t,
112 | open: false,
113 | }
114 | : t
115 | ),
116 | }
117 | }
118 | case "REMOVE_TOAST":
119 | if (action.toastId === undefined) {
120 | return {
121 | ...state,
122 | toasts: [],
123 | }
124 | }
125 | return {
126 | ...state,
127 | toasts: state.toasts.filter((t) => t.id !== action.toastId),
128 | }
129 | }
130 | }
131 |
132 | const listeners: Array<(state: State) => void> = []
133 |
134 | let memoryState: State = { toasts: [] }
135 |
136 | function dispatch(action: Action) {
137 | memoryState = reducer(memoryState, action)
138 | listeners.forEach((listener) => {
139 | listener(memoryState)
140 | })
141 | }
142 |
143 | type Toast = Omit
144 |
145 | function toast({ ...props }: Toast) {
146 | const id = genId()
147 |
148 | const update = (props: ToasterToast) =>
149 | dispatch({
150 | type: "UPDATE_TOAST",
151 | toast: { ...props, id },
152 | })
153 | const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
154 |
155 | dispatch({
156 | type: "ADD_TOAST",
157 | toast: {
158 | ...props,
159 | id,
160 | open: true,
161 | onOpenChange: (open) => {
162 | if (!open) dismiss()
163 | },
164 | },
165 | })
166 |
167 | return {
168 | id: id,
169 | dismiss,
170 | update,
171 | }
172 | }
173 |
174 | function useToast() {
175 | const [state, setState] = React.useState(memoryState)
176 |
177 | React.useEffect(() => {
178 | listeners.push(setState)
179 | return () => {
180 | const index = listeners.indexOf(setState)
181 | if (index > -1) {
182 | listeners.splice(index, 1)
183 | }
184 | }
185 | }, [state])
186 |
187 | return {
188 | ...state,
189 | toast,
190 | dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
191 | }
192 | }
193 |
194 | export { useToast, toast }
195 |
--------------------------------------------------------------------------------
/docs/docs/assets/add-mcp-server.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/isoftstone-data-intelligence-ai/efflux-frontend/1b941cc4ed746995a952e72cd8afa4c86eb4eab5/docs/docs/assets/add-mcp-server.png
--------------------------------------------------------------------------------
/docs/docs/assets/code-login.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/isoftstone-data-intelligence-ai/efflux-frontend/1b941cc4ed746995a952e72cd8afa4c86eb4eab5/docs/docs/assets/code-login.gif
--------------------------------------------------------------------------------
/docs/docs/assets/fast-mcp-weather.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/isoftstone-data-intelligence-ai/efflux-frontend/1b941cc4ed746995a952e72cd8afa4c86eb4eab5/docs/docs/assets/fast-mcp-weather.gif
--------------------------------------------------------------------------------
/docs/docs/assets/fast-mcp-weather.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/isoftstone-data-intelligence-ai/efflux-frontend/1b941cc4ed746995a952e72cd8afa4c86eb4eab5/docs/docs/assets/fast-mcp-weather.png
--------------------------------------------------------------------------------
/docs/docs/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/isoftstone-data-intelligence-ai/efflux-frontend/1b941cc4ed746995a952e72cd8afa4c86eb4eab5/docs/docs/assets/favicon.ico
--------------------------------------------------------------------------------
/docs/docs/assets/image-to-UI-preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/isoftstone-data-intelligence-ai/efflux-frontend/1b941cc4ed746995a952e72cd8afa4c86eb4eab5/docs/docs/assets/image-to-UI-preview.png
--------------------------------------------------------------------------------
/docs/docs/assets/image-to-UI.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/isoftstone-data-intelligence-ai/efflux-frontend/1b941cc4ed746995a952e72cd8afa4c86eb4eab5/docs/docs/assets/image-to-UI.gif
--------------------------------------------------------------------------------
/docs/docs/assets/image-to-UI.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/isoftstone-data-intelligence-ai/efflux-frontend/1b941cc4ed746995a952e72cd8afa4c86eb4eab5/docs/docs/assets/image-to-UI.png
--------------------------------------------------------------------------------
/docs/docs/assets/landing-page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/isoftstone-data-intelligence-ai/efflux-frontend/1b941cc4ed746995a952e72cd8afa4c86eb4eab5/docs/docs/assets/landing-page.png
--------------------------------------------------------------------------------
/docs/docs/assets/mcp-server.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/isoftstone-data-intelligence-ai/efflux-frontend/1b941cc4ed746995a952e72cd8afa4c86eb4eab5/docs/docs/assets/mcp-server.gif
--------------------------------------------------------------------------------
/docs/docs/assets/text-to-UI-preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/isoftstone-data-intelligence-ai/efflux-frontend/1b941cc4ed746995a952e72cd8afa4c86eb4eab5/docs/docs/assets/text-to-UI-preview.png
--------------------------------------------------------------------------------
/docs/docs/assets/text-to-UI.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/isoftstone-data-intelligence-ai/efflux-frontend/1b941cc4ed746995a952e72cd8afa4c86eb4eab5/docs/docs/assets/text-to-UI.gif
--------------------------------------------------------------------------------
/docs/docs/assets/text-to-UI.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/isoftstone-data-intelligence-ai/efflux-frontend/1b941cc4ed746995a952e72cd8afa4c86eb4eab5/docs/docs/assets/text-to-UI.png
--------------------------------------------------------------------------------
/docs/docs/en/contribute.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Contribute to Efflux
3 | description: # for seo optimization
4 | keywords: # for seo optimization
5 | contributors:
6 | - Jun Ma
7 | ---
8 |
9 | We welcome contributions from developers and users in the open source community in any ways to make Efflux better!
10 |
11 | ## Commit Code/Doc Changes
12 |
13 | 1. Fork the repository.
14 | 2. Create a new branch (`git checkout -b feature-branch`).
15 | 3. Make your changes.
16 | 4. Commit your changes (`git commit -am 'Add new feature'`).
17 | 5. Push to your branch (`git push origin feature-branch`).
18 | 6. Open a pull request.
19 |
20 | !!! note
21 | Make sure you follow the coding standards and guidelines defined in the project.
22 |
23 | ## Report Issues
24 |
25 | If you encounter any issues or bugs, feel free to open an issue in the respective repository and include as much detail as you can.
26 |
27 | Be sure to include:
28 |
29 | - A description of the issue.
30 | - Steps to reproduce.
31 | - Logs or screenshots that can help in debugging.
--------------------------------------------------------------------------------
/docs/docs/en/generate-code.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Generate Code
3 | description: # for seo optimization
4 | keywords: # for seo optimization
5 | contributors:
6 | - Jun Ma
7 | ---
8 |
9 | Efflux provides a chat-based interface where you can input text prompts to get responses with generative code snippets. For UI components, Efflux can render the generated code and provide an interactive environment for you to try it out. What's more, you can upload image files of UI components to build similar pages with one click.
10 |
11 | 
12 |
13 | ## How it Works
14 |
15 | 1. **Input Prompt**: Type text descriptions or upload images to let Efflux understand the required interface you want to build.
16 | 2. **Generate Code**: Efflux generates code using open-source tools, like React, Tailwind CSS, and Shadcn UI, based on your prompt.
17 | 3. **Preview and Edit**: Preview the code and make real-time edits in Efflux to fit your needs.
18 | 4. **Integrate Code**: Copy and paste the desired code into your application.
19 |
20 | ## Example of Text-to-UI
21 |
22 | The following is an example of generating a simple login page.
23 |
24 | 
25 |
26 | On the **Preview** tab, you can check out the rendered code for your desired UI component.
27 |
28 | 
29 |
30 | ## Example of Image-to-UI
31 |
32 | The following is an example of generating a similar user interface based on the uploaded image.
33 |
34 | 
35 |
36 | On the **Preview** tab, you can check out the rendered code for your desired UI component.
37 |
38 | 
39 |
40 | It can be a good starting point for you to iterate the GUI.
--------------------------------------------------------------------------------
/docs/docs/en/get-started.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Set up Efflux
3 | description: # for seo optimization
4 | keywords: # for seo optimization
5 | contributors:
6 | - Jun Ma
7 | ---
8 |
9 | ## Prerequisites
10 |
11 | Make sure the following dependencies have been installed:
12 |
13 | - Python 3.12 or higher versions
14 |
15 | - Node.js 18 or higher versions
16 |
17 | - [Git](https://git-scm.com/downloads)
18 |
19 | ## Set up Backend Service
20 |
21 | 1. Clone the [efflux-backend repo](https://github.com/isoftstone-data-intelligence-ai/efflux-backend).
22 |
23 | ```sh
24 | git clone git@github.com:isoftstone-data-intelligence-ai/efflux-backend.git
25 | ```
26 |
27 | 2. Install `uv`.
28 |
29 | ```sh
30 | cd efflux-backend
31 | pip install uv
32 | ```
33 |
34 | 3. Reload dependencies.
35 |
36 | ```sh
37 | uv sync --reinstall
38 | ```
39 |
40 | 4. Activate your virtual environment.
41 |
42 | ```sh
43 | # Activate virtual environment
44 | source .venv/bin/activate # MacOS/Linux
45 |
46 | # Deactivate when needed
47 | deactivate
48 | ```
49 |
50 | 5. Configure environment variables.
51 |
52 | ```sh
53 | # Copy environment variable template
54 | cp .env.sample .env
55 |
56 | # Edit .env file, configure:
57 | # 1. Database connection info (DATABASE_NAME, DATABASE_USERNAME, DATABASE_PASSWORD)
58 | # 2. At least one LLM configuration (e.g., Azure OpenAI, Qwen, Doubao, or Moonshot)
59 | ```
60 |
61 | 6. Select your LLM.
62 |
63 | ```sh
64 | # Edit core/common/container.py file
65 | # Find the llm registration section, replace with any of the following models (Qwen by default):
66 | # - QwenLlm: Qwen
67 | # - AzureLlm: Azure OpenAI
68 | # - DoubaoLlm: Doubao
69 | # - MoonshotLlm: Moonshot
70 |
71 | # Example: Using Azure OpenAI
72 | from core.llm.azure_open_ai import AzureLlm
73 | # ...
74 | llm = providers.Singleton(AzureLlm)
75 | ```
76 |
77 | 7. Start PostgreSQL database.
78 |
79 | ```sh
80 | # Method 1: If PostgreSQL is installed locally
81 | # Simply start your local PostgreSQL service
82 |
83 | # Method 2: Using Docker (example)
84 | docker run -d --name local-postgres \
85 | -e POSTGRES_DB=your_database_name \
86 | -e POSTGRES_USER=your_username \
87 | -e POSTGRES_PASSWORD=your_password \
88 | -p 5432:5432 \
89 | postgres
90 |
91 | # Note: Ensure database connection info matches the configuration in your .env file
92 | ```
93 |
94 | 8. Initialize the database.
95 |
96 | ```sh
97 | # Create a new version and generate a migration file in alembic/versions
98 | alembic revision --autogenerate -m "initial migration"
99 |
100 | # Preview SQL to be executed:
101 | alembic upgrade head --sql
102 |
103 | # If preview looks good, execute migration
104 | alembic upgrade head
105 | ```
106 |
107 | 9. Start the service.
108 |
109 | ```sh
110 | python -m uvicorn main:app --host 0.0.0.0 --port 8000
111 | ```
112 |
113 | ## Set up Frontend Service
114 |
115 | 1. Clone the [efflux-frontend repo](https://github.com/isoftstone-data-intelligence-ai/efflux-frontend).
116 |
117 | ```sh
118 | git clone https://github.com/isoftstone-data-intelligence-ai/efflux-frontend.git
119 | ```
120 |
121 | 2. Install the dependencies.
122 |
123 | ```sh
124 | cd efflux-frontend
125 | npm i
126 | ```
127 |
128 | 3. Create a `.env.local` file and set the environment variables as follows:
129 |
130 | ```sh
131 | # Get your API key here - https://e2b.dev/
132 | E2B_API_KEY="your-e2b-api-key"
133 |
134 | # Get your Azure API key here https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/create-resource?tabs=portal
135 | AZURE_API_KEY="your-azure-api-key"
136 |
137 | # API URL
138 | NEXT_PUBLIC_API_URL="your-backend-service-url"
139 |
140 | # OpenAI API Key
141 | OPENAI_API_KEY=
142 |
143 | # Other providers
144 | ANTHROPIC_API_KEY=
145 | GROQ_API_KEY=
146 | FIREWORKS_API_KEY=
147 | TOGETHER_API_KEY=
148 | GOOGLE_AI_API_KEY=
149 | GOOGLE_VERTEX_CREDENTIALS=
150 | MISTRAL_API_KEY=
151 | XAI_API_KEY=
152 |
153 | ### Optional env vars
154 |
155 | # Domain of the site
156 | NEXT_PUBLIC_SITE_URL=
157 |
158 | # Disabling API key and base URL input in the chat
159 | NEXT_PUBLIC_NO_API_KEY_INPUT=
160 | NEXT_PUBLIC_NO_BASE_URL_INPUT=
161 |
162 | # Rate limit
163 | RATE_LIMIT_MAX_REQUESTS=
164 | RATE_LIMIT_WINDOW=
165 |
166 | # Vercel/Upstash KV (short URLs, rate limiting)
167 | KV_REST_API_URL=
168 | KV_REST_API_TOKEN=
169 |
170 | # Supabase (auth)
171 | SUPABASE_URL=
172 | SUPABASE_ANON_KEY=
173 |
174 | # PostHog (analytics)
175 | NEXT_PUBLIC_POSTHOG_KEY=
176 | NEXT_PUBLIC_POSTHOG_HOST=
177 | ```
178 |
179 | 4. Start the development server.
180 |
181 | ```sh
182 | npm run dev
183 | ```
184 |
185 | Then you can access your local efflux at http://localhost:3000. The landing page looks like the following:
186 |
187 | 
188 |
189 | ## What's Next?
190 |
191 | * [Generate UI Code](generate-code.md)
192 |
193 | * [Work with MCP](work-with-mcp.md)
--------------------------------------------------------------------------------
/docs/docs/en/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Overview
3 | description: # for seo optimization
4 | keywords: # for seo optimization
5 | contributors:
6 | - Jun Ma
7 | ---
8 |
9 | ## What's Efflux?
10 |
11 | Efflux, the next-generation AI interaction platform, is a powerful, lightweight, and highly flexible framework that seamlessly integrates state-of-the-art LLMs (large language models), generative front-end technologies, and MCP (Model Context Protocols) servers. It redefines the way AI-driven applications are deployed and scaled, offering unparalleled efficiency and adaptability.
12 |
13 | In essence, Efflux can serve as:
14 |
15 | * **An LLM-powered chatbot** that engages in natural conversations with users.
16 |
17 | * **A text-to-artifact tool** that helps developers create code snippets effortlessly —— simply by describing their ideas. Efflux can also render the generated UI code in real time, allowing you to immediately test and iterate.
18 |
19 | *Explore more → [Generate Code](generate-code.md)*
20 |
21 | * **A ready-to-use MCP (Model Context Protocol) host**, unlocking your LLMs' potential and expanding more capabilities by enabling wider data access and integrating custom tools, including but not limited to database interaction and business intelligence.
22 |
23 | *Explore more → [Work with MCP](work-with-mcp.md)*
24 |
25 | ## 🌟Why Efflux?
26 |
27 | Efflux empowers developers and enthusiasts to:
28 |
29 | - **Build Conversational AI Agents**
30 |
31 | Create human-like chatbots with contextual awareness and conversation history management.
32 |
33 | - **Accelerate Development Workflows**
34 |
35 | Effortlessly generate code snippets - from UI components to APIs and full-stack modules.
36 |
37 | - **Unlock Advanced Capabilities**
38 |
39 | Leverage MCP servers to seamlessly integrate databases, various tools, and more while maintaining data governance.
40 |
41 | ## ✨Core Features
42 |
43 | - Intuitive chat-based interface with history management
44 | - Multi-LLM support (Claude, DeepSeek, and many more)
45 | - Native MCP integration with unlimited possibilities
46 | - Dynamic artifact rendering engine
47 | - Advanced context memory management
48 |
49 | ## 🧩Architecture
50 |
51 | ```
52 | Efflux
53 | ├── Core
54 | │ ├── MCP Manager # MCP management
55 | │ ├── Model Router # Intelligent routing
56 | │ └── Context Manager # Conversation context management
57 | ├── Service Layer
58 | │ ├── LLM Manager # Multiple LLM management
59 | │ ├── Model Switching # Model switching
60 | │ ├── Agent Node # Agent node
61 | │ └── Intelligent Reasoning # Intelligent reasoning
62 | ├── Extension
63 | │ └── Plugin System # Pluggable extension
64 | └── Web
65 | ├── Artifacts Engine # Dynamic front-end rendering
66 | ├── Web Interface # Interactive interface
67 | ├── UI Stream Response # Streaming response
68 | └── Sandbox Debugging # Sandbox debugging
69 | ```
70 |
71 | ## 🚀Get Started
72 |
73 | Setting up Efflux is just a few clicks away. For more details, visit [Get Started](get-started.md).
74 |
75 | ## 🤝Contribute to Efflux
76 |
77 | We’re committed to building Efflux as a collaborative, open-source project and ecosystem. Participation is not only welcomed – it's essential!
78 |
79 | Here's how you can get involved:
80 |
81 | * file issues to report bugs
82 | * commit pull requests to improve codes and docs
83 |
84 | For more details, visit [Contribute to Efflux](contribute.md).
--------------------------------------------------------------------------------
/docs/docs/javascripts/feedback.js:
--------------------------------------------------------------------------------
1 | document.addEventListener("DOMContentLoaded", function () {
2 | // 绑定 Yes 按钮 - 仅显示感谢消息
3 | document.getElementById("feedback-yes")?.addEventListener("click", function () {
4 | handleFeedbackUI(); // 更新 UI
5 | });
6 |
7 | // 绑定 No 按钮 - 显示消息并跳转 GitHub
8 | document.getElementById("feedback-no")?.addEventListener("click", function () {
9 | handleFeedbackUI();
10 | createGitHubIssue(); // 跳转 GitHub
11 | });
12 | });
13 |
14 | // 统一处理 UI 变化
15 | function handleFeedbackUI() {
16 | document.getElementById("feedback-message").style.display = "block";
17 | document.getElementById("feedback-yes").style.display = "none";
18 | document.getElementById("feedback-no").style.display = "none";
19 | }
20 |
21 | // 仅 No 按钮触发 GitHub Issue 跳转
22 | function createGitHubIssue() {
23 | const issueTitle = encodeURIComponent("Feedback for " + document.title);
24 | const issueBody = encodeURIComponent(
25 | `Feedback: **No**\nPage URL: ${window.location.href}\n问题描述(可选):`
26 | );
27 | const githubIssueUrl = `https://github.com/isoftstone-data-intelligence-ai/efflux-frontend/issues/new?title=${issueTitle}&body=${issueBody}`;
28 | window.open(githubIssueUrl, "_blank");
29 | }
30 |
31 | // 绑定 Yes 按钮 - 发送反馈
32 | function sendFeedback(response) {
33 | // 更新 UI
34 | document.getElementById("feedback-message").style.display = "block";
35 | document.getElementById("feedback-yes").style.display = "none";
36 | document.getElementById("feedback-no").style.display = "none";
37 |
38 | // 发送到 Google Analytics(仅 Yes 时)
39 | if (response === "yes") {
40 | gtag('event', 'feedback', {
41 | 'event_category': 'page_helpfulness',
42 | 'event_label': 'yes',
43 | 'value': 1
44 | });
45 | }
46 | }
--------------------------------------------------------------------------------
/docs/docs/overrides/main.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block content %}
4 | {{ super() }}
5 |
6 |
7 |
Was this page helpful?
8 |
👍 Yes
9 |
👎 No
10 |
Thanks for your feedback!
11 |
12 |
21 |
22 |
23 |
29 |
30 | {% endblock %}
--------------------------------------------------------------------------------
/docs/docs/zh/contribute.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Contribute to Efflux
3 | description: # for seo optimization
4 | keywords: # for seo optimization
5 | contributors:
6 | - Jun Ma
7 | ---
8 |
9 | We welcome contributions from developers and users in the open source community in any ways to make Efflux better!
10 |
11 | ## Commit Code/Doc Changes
12 |
13 | 1. Fork the repository.
14 | 2. Create a new branch (`git checkout -b feature-branch`).
15 | 3. Make your changes.
16 | 4. Commit your changes (`git commit -am 'Add new feature'`).
17 | 5. Push to your branch (`git push origin feature-branch`).
18 | 6. Open a pull request.
19 |
20 | !!! note
21 | Make sure you follow the coding standards and guidelines defined in the project.
22 |
23 | ## Report Issues
24 |
25 | If you encounter any issues or bugs, feel free to open an issue in the respective repository and include as much detail as you can.
26 |
27 | Be sure to include:
28 |
29 | - A description of the issue.
30 | - Steps to reproduce.
31 | - Logs or screenshots that can help in debugging.
--------------------------------------------------------------------------------
/docs/docs/zh/generate-code.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Generate Code
3 | description: # for seo optimization
4 | keywords: # for seo optimization
5 | contributors:
6 | - Jun Ma
7 | ---
8 |
9 | Efflux provides a chat-based interface where you can input text prompts to get responses with generative code snippets. For UI components, Efflux can render the generated code and provide an interactive environment for you to try it out. What's more, you can upload image files of UI components to build similar pages with one click.
10 |
11 | 
12 |
13 | ## How it Works
14 |
15 | 1. **Input Prompt**: Type text descriptions or upload images to let Efflux understand the required interface you want to build.
16 | 2. **Generate Code**: Efflux generates code using open-source tools, like React, Tailwind CSS, and Shadcn UI, based on your prompt.
17 | 3. **Preview and Edit**: Preview the code and make real-time edits in Efflux to fit your needs.
18 | 4. **Integrate Code**: Copy and paste the desired code into your application.
19 |
20 | ## Example of Text-to-UI
21 |
22 | The following is an example of generating a simple login page.
23 |
24 | 
25 |
26 | On the **Preview** tab, you can check out the rendered code for your desired UI component.
27 |
28 | 
29 |
30 | ## Example of Image-to-UI
31 |
32 | The following is an example of generating a similar user interface based on the uploaded image.
33 |
34 | 
35 |
36 | On the **Preview** tab, you can check out the rendered code for your desired UI component.
37 |
38 | 
39 |
40 | It can be a good starting point for you to iterate the GUI.
--------------------------------------------------------------------------------
/docs/docs/zh/get-started.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Set up Efflux
3 | description: # for seo optimization
4 | keywords: # for seo optimization
5 | contributors:
6 | - Jun Ma
7 | ---
8 |
9 | ## Prerequisites
10 |
11 | Make sure the following dependencies have been installed:
12 |
13 | - Python 3.12 or higher versions
14 |
15 | - Node.js 18 or higher versions
16 |
17 | - [Git](https://git-scm.com/downloads)
18 |
19 | ## Set up Backend Service
20 |
21 | 1. Clone the [efflux-backend repo](https://github.com/isoftstone-data-intelligence-ai/efflux-backend).
22 |
23 | ```sh
24 | git clone git@github.com:isoftstone-data-intelligence-ai/efflux-backend.git
25 | ```
26 |
27 | 2. Install `uv`.
28 |
29 | ```sh
30 | cd efflux-backend
31 | pip install uv
32 | ```
33 |
34 | 3. Reload dependencies.
35 |
36 | ```sh
37 | uv sync --reinstall
38 | ```
39 |
40 | 4. Activate your virtual environment.
41 |
42 | ```sh
43 | # Activate virtual environment
44 | source .venv/bin/activate # MacOS/Linux
45 |
46 | # Deactivate when needed
47 | deactivate
48 | ```
49 |
50 | 5. Configure environment variables.
51 |
52 | ```sh
53 | # Copy environment variable template
54 | cp .env.sample .env
55 |
56 | # Edit .env file, configure:
57 | # 1. Database connection info (DATABASE_NAME, DATABASE_USERNAME, DATABASE_PASSWORD)
58 | # 2. At least one LLM configuration (e.g., Azure OpenAI, Qwen, Doubao, or Moonshot)
59 | ```
60 |
61 | 6. Select your LLM.
62 |
63 | ```sh
64 | # Edit core/common/container.py file
65 | # Find the llm registration section, replace with any of the following models (Qwen by default):
66 | # - QwenLlm: Qwen
67 | # - AzureLlm: Azure OpenAI
68 | # - DoubaoLlm: Doubao
69 | # - MoonshotLlm: Moonshot
70 |
71 | # Example: Using Azure OpenAI
72 | from core.llm.azure_open_ai import AzureLlm
73 | # ...
74 | llm = providers.Singleton(AzureLlm)
75 | ```
76 |
77 | 7. Start PostgreSQL database.
78 |
79 | ```sh
80 | # Method 1: If PostgreSQL is installed locally
81 | # Simply start your local PostgreSQL service
82 |
83 | # Method 2: Using Docker (example)
84 | docker run -d --name local-postgres \
85 | -e POSTGRES_DB=your_database_name \
86 | -e POSTGRES_USER=your_username \
87 | -e POSTGRES_PASSWORD=your_password \
88 | -p 5432:5432 \
89 | postgres
90 |
91 | # Note: Ensure database connection info matches the configuration in your .env file
92 | ```
93 |
94 | 8. Initialize the database.
95 |
96 | ```sh
97 | # Create a new version and generate a migration file in alembic/versions
98 | alembic revision --autogenerate -m "initial migration"
99 |
100 | # Preview SQL to be executed:
101 | alembic upgrade head --sql
102 |
103 | # If preview looks good, execute migration
104 | alembic upgrade head
105 | ```
106 |
107 | 9. Start the service.
108 |
109 | ```sh
110 | python -m uvicorn main:app --host 0.0.0.0 --port 8000
111 | ```
112 |
113 | ## Set up Frontend Service
114 |
115 | 1. Clone the [efflux-frontend repo](https://github.com/isoftstone-data-intelligence-ai/efflux-frontend).
116 |
117 | ```sh
118 | git clone https://github.com/isoftstone-data-intelligence-ai/efflux-frontend.git
119 | ```
120 |
121 | 2. Install the dependencies.
122 |
123 | ```sh
124 | cd efflux-frontend
125 | npm i
126 | ```
127 |
128 | 3. Create a `.env.local` file and set the environment variables as follows:
129 |
130 | ```sh
131 | # Get your API key here - https://e2b.dev/
132 | E2B_API_KEY="your-e2b-api-key"
133 |
134 | # Get your Azure API key here https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/create-resource?tabs=portal
135 | AZURE_API_KEY="your-azure-api-key"
136 |
137 | # API URL
138 | NEXT_PUBLIC_API_URL="your-backend-service-url"
139 |
140 | # OpenAI API Key
141 | OPENAI_API_KEY=
142 |
143 | # Other providers
144 | ANTHROPIC_API_KEY=
145 | GROQ_API_KEY=
146 | FIREWORKS_API_KEY=
147 | TOGETHER_API_KEY=
148 | GOOGLE_AI_API_KEY=
149 | GOOGLE_VERTEX_CREDENTIALS=
150 | MISTRAL_API_KEY=
151 | XAI_API_KEY=
152 |
153 | ### Optional env vars
154 |
155 | # Domain of the site
156 | NEXT_PUBLIC_SITE_URL=
157 |
158 | # Disabling API key and base URL input in the chat
159 | NEXT_PUBLIC_NO_API_KEY_INPUT=
160 | NEXT_PUBLIC_NO_BASE_URL_INPUT=
161 |
162 | # Rate limit
163 | RATE_LIMIT_MAX_REQUESTS=
164 | RATE_LIMIT_WINDOW=
165 |
166 | # Vercel/Upstash KV (short URLs, rate limiting)
167 | KV_REST_API_URL=
168 | KV_REST_API_TOKEN=
169 |
170 | # Supabase (auth)
171 | SUPABASE_URL=
172 | SUPABASE_ANON_KEY=
173 |
174 | # PostHog (analytics)
175 | NEXT_PUBLIC_POSTHOG_KEY=
176 | NEXT_PUBLIC_POSTHOG_HOST=
177 | ```
178 |
179 | 4. Start the development server.
180 |
181 | ```sh
182 | npm run dev
183 | ```
184 |
185 | Then you can access your local efflux at http://localhost:3000. The landing page looks like the following:
186 |
187 | 
188 |
189 | ## What's Next?
190 |
191 | * [Generate UI Code](generate-code.md)
192 |
193 | * [Work with MCP](work-with-mcp.md)
--------------------------------------------------------------------------------
/docs/docs/zh/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Overview
3 | description: # for seo optimization
4 | keywords: # for seo optimization
5 | contributors:
6 | - Jun Ma
7 | ---
8 |
9 | ## Efflux是什么?
10 |
11 | Efflux 是一款新一代 AI 交互平台 —— 一个强大、轻量且高度灵活的框架,可无缝集成最先进的大语言模型(LLMs)、生成式前端技术以及 MCP(模型上下文协议)服务器。它重新定义了 AI 驱动应用的部署和扩展方式,可以通过接入海量社区工具,构建普惠AI生态。
12 |
13 | Efflux 可以是:
14 |
15 | * **基于 LLM 的聊天机器人**,能够与用户进行自然语言对话。
16 |
17 | * **文本到组件(Text-to-Artifact)生成工具**,帮助开发者轻松创建代码片段 —— 只需描述你的想法即可。Efflux 能实时渲染生成的 UI 代码,让你能够立即测试和迭代。
18 |
19 | *探索更多 → [生成UI代码](generate-code.md)*
20 |
21 | * **开箱即用的 MCP(模型上下文协议)主机**,通过更广泛的数据访问和集成自定义工具释放 LLM 潜力。
22 |
23 | *探索更多 → [使用MCP](work-with-mcp.md)*
24 |
25 | ## 🌟Why Efflux?
26 |
27 | Efflux empowers developers and enthusiasts to:
28 |
29 | - **Build Conversational AI Agents**
30 |
31 | Create human-like chatbots with contextual awareness and conversation history management.
32 |
33 | - **Accelerate Development Workflows**
34 |
35 | Effortlessly generate code snippets - from UI components to APIs and full-stack modules.
36 |
37 | - **Unlock Advanced Capabilities**
38 |
39 | Leverage MCP servers to seamlessly integrate databases, various tools, and more while maintaining data governance.
40 |
41 | ## ✨Core Features
42 |
43 | - Intuitive chat-based interface with history management
44 | - Multi-LLM support (Claude, DeepSeek, and many more)
45 | - Native MCP integration with unlimited possibilities
46 | - Dynamic artifact rendering engine
47 | - Advanced context memory management
48 |
49 | ## 🧩Architecture
50 |
51 | ```
52 | Efflux
53 | ├── Core
54 | │ ├── MCP Manager # MCP management
55 | │ ├── Model Router # Intelligent routing
56 | │ └── Context Manager # Conversation context management
57 | ├── Service Layer
58 | │ ├── LLM Manager # Multiple LLM management
59 | │ ├── Model Switching # Model switching
60 | │ ├── Agent Node # Agent node
61 | │ └── Intelligent Reasoning # Intelligent reasoning
62 | ├── Extension
63 | │ └── Plugin System # Pluggable extension
64 | └── Web
65 | ├── Artifacts Engine # Dynamic front-end rendering
66 | ├── Web Interface # Interactive interface
67 | ├── UI Stream Response # Streaming response
68 | └── Sandbox Debugging # Sandbox debugging
69 | ```
70 |
71 | ## 🚀Get Started
72 |
73 | Setting up Efflux is just a few clicks away. For more details, visit [Get Started](get-started.md).
74 |
75 | ## 🤝Contribute to Efflux
76 |
77 | We’re committed to building Efflux as a collaborative, open-source project and ecosystem. Participation is not only welcomed – it's essential!
78 |
79 | Here's how you can get involved:
80 |
81 | * file issues to report bugs
82 | * commit pull requests to improve codes and docs
83 |
84 | For more details, visit [Contribute to Efflux](contribute.md).
--------------------------------------------------------------------------------
/docs/mkdocs.yml:
--------------------------------------------------------------------------------
1 | # website title
2 | site_name: Efflux Docs
3 |
4 | # website URL
5 | site_url: https://jun-ma.github.io/efflux-frontend/
6 | repo_url: https://github.com/isoftstone-data-intelligence-ai/efflux-frontend
7 |
8 | # website style
9 | theme:
10 | name: material
11 | favicon: assets/favicon.ico
12 | logo: assets/logo.svg
13 | features:
14 | - content.code.copy # copy code
15 | - content.code.select # select code language
16 | - content.code.annotate # display code language
17 | - navigation.top # back to top
18 | - content.tabs.link # code tab
19 | - content.tooltips
20 | custom_dir: docs/overrides
21 |
22 | # website navigation and multi-language
23 | plugins:
24 | - search:
25 | lang:
26 | - en
27 | - zh
28 | - i18n:
29 | docs_structure: folder
30 | languages:
31 | - locale: en
32 | name: English
33 | build: true
34 | default: true
35 | folder: en
36 | nav:
37 | - Overview: index.md
38 | - Get Started: get-started.md
39 | - Generate Code: generate-code.md
40 | - Work with MCP: work-with-mcp.md
41 | - Contribute to Efflux: contribute.md
42 | - locale: zh
43 | name: 中文
44 | build: true
45 | folder: zh
46 | nav:
47 | - 概览: index.md
48 | - 快速开始: get-started.md
49 | - 生成UI代码: generate-code.md
50 | - 使用MCP: work-with-mcp.md
51 | - 贡献: contribute.md
52 |
53 | # mkdocs.yml
54 | dev_addr: 'localhost:8080' # port number for `mkdocs serve`
55 |
56 | # markdown formats
57 | markdown_extensions:
58 | - pymdownx.highlight:
59 | use_pygments: true
60 | pygments_lang_class: true
61 | - pymdownx.inlinehilite
62 | - pymdownx.snippets
63 | - pymdownx.extra
64 | - pymdownx.superfences
65 | - pymdownx.tabbed:
66 | alternate_style: true
67 | - admonition
68 | - pymdownx.details
69 | - toc:
70 | baselevel: 3 # maximum level of toc
71 |
72 |
73 | # feedback
74 | extra_javascript:
75 | - docs/javascripts/feedback.js
--------------------------------------------------------------------------------
/lib/api.ts:
--------------------------------------------------------------------------------
1 | import request from '@/lib/request';
2 | var apiUrl = process.env.NEXT_PUBLIC_API_URL;
3 |
4 | //列表
5 | export const getServerList = (data) => {
6 | return request({
7 | url: `/mcp/mcp_server_list/${data.userId}`,
8 | method: 'get',
9 | });
10 | };
11 |
12 | //添加 mcpServer
13 | export const addMcpServer = (data) => {
14 | return request({
15 | url: `/mcp/mcp_server`,
16 | method: 'post',
17 | data: data,
18 | });
19 | };
20 |
21 | //修改 mcpServer
22 | export const updateMcpServer = (data) => {
23 | return request({
24 | url: '/mcp/mcp_server',
25 | method: 'put',
26 | data: data,
27 | });
28 | };
29 |
30 | //删除 mcpServer
31 | export const delMcpServer = (data) => {
32 | return request({
33 | url: `/mcp/mcp_server/${data.id}`,
34 | method: 'delete',
35 | data: data,
36 | });
37 | };
38 |
39 | //查看 mcpServer
40 | export const getMcpServer = (data) => {
41 | return request({
42 | url: `/mcp/mcp_server/${data.id}`,
43 | method: 'get',
44 | });
45 | };
46 |
47 | // 查看会话
48 | export const getChatList = (data) => {
49 | return request({
50 | url: `/chat_window/chat_window_list/${data.userId}`,
51 | method: 'get',
52 | });
53 | };
--------------------------------------------------------------------------------
/lib/auth.ts:
--------------------------------------------------------------------------------
1 | import { supabase } from './supabase'
2 | import { Session } from '@supabase/supabase-js'
3 | import { usePostHog } from 'posthog-js/react'
4 | import { useState, useEffect } from 'react'
5 |
6 | export type AuthViewType =
7 | | 'sign_in'
8 | | 'sign_up'
9 | | 'magic_link'
10 | | 'forgotten_password'
11 | | 'update_password'
12 | | 'verify_otp'
13 |
14 | interface UserTeam {
15 | id: string
16 | name: string
17 | is_default: boolean
18 | tier: string
19 | email: string
20 | team_api_keys: { api_key: string }[]
21 | }
22 |
23 | export async function getUserAPIKey(session: Session) {
24 | // If Supabase is not initialized will use E2B_API_KEY env var
25 | if (!supabase || process.env.E2B_API_KEY) return process.env.E2B_API_KEY
26 |
27 | const { data: userTeams } = await supabase
28 | .from('users_teams')
29 | .select(
30 | 'teams (id, name, is_default, tier, email, team_api_keys (api_key))',
31 | )
32 | .eq('user_id', session?.user.id)
33 |
34 | const teams = userTeams
35 | ?.map((userTeam: any) => userTeam.teams)
36 | .map((team: UserTeam) => {
37 | return {
38 | ...team,
39 | apiKeys: team.team_api_keys.map((apiKey) => apiKey.api_key),
40 | }
41 | })
42 |
43 | const defaultTeam = teams?.find((team) => team.is_default)
44 | return defaultTeam?.apiKeys[0]
45 | }
46 |
47 | export function useAuth(
48 | setAuthDialog: (value: boolean) => void,
49 | setAuthView: (value: AuthViewType) => void,
50 | ) {
51 | const [session, setSession] = useState(null)
52 | const [apiKey, setApiKey] = useState(undefined)
53 | const posthog = usePostHog()
54 | let recovery = false
55 |
56 | useEffect(() => {
57 | if (!supabase) {
58 | console.warn('Supabase is not initialized')
59 | return setSession({ user: { email: 'demo@TestUser.dev' } } as Session)
60 | }
61 |
62 | supabase.auth.getSession().then(({ data: { session } }) => {
63 | setSession(session)
64 | if (session) {
65 | getUserAPIKey(session).then(setApiKey)
66 | if (!session.user.user_metadata.is_fragments_user) {
67 | supabase?.auth.updateUser({
68 | data: { is_fragments_user: true },
69 | })
70 | }
71 | posthog.identify(session?.user.id, {
72 | email: session?.user.email,
73 | supabase_id: session?.user.id,
74 | })
75 | posthog.capture('sign_in')
76 | }
77 | })
78 |
79 | const {
80 | data: { subscription },
81 | } = supabase.auth.onAuthStateChange((_event, session) => {
82 | setSession(session)
83 |
84 | if (_event === 'PASSWORD_RECOVERY') {
85 | recovery = true
86 | setAuthView('update_password')
87 | setAuthDialog(true)
88 | }
89 |
90 | if (_event === 'USER_UPDATED' && recovery) {
91 | recovery = false
92 | }
93 |
94 | if (_event === 'SIGNED_IN' && !recovery) {
95 | setAuthDialog(false)
96 | getUserAPIKey(session as Session).then(setApiKey)
97 | if (!session?.user.user_metadata.is_fragments_user) {
98 | supabase?.auth.updateUser({
99 | data: { is_fragments_user: true },
100 | })
101 | }
102 | posthog.identify(session?.user.id, {
103 | email: session?.user.email,
104 | supabase_id: session?.user.id,
105 | })
106 | posthog.capture('sign_in')
107 | }
108 |
109 | if (_event === 'SIGNED_OUT') {
110 | setApiKey(undefined)
111 | setAuthView('sign_in')
112 | posthog.capture('sign_out')
113 | posthog.reset()
114 | }
115 | })
116 |
117 | return () => subscription.unsubscribe()
118 | }, [])
119 |
120 | return {
121 | session,
122 | apiKey,
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/lib/duration.ts:
--------------------------------------------------------------------------------
1 | // Taken from https://github.com/upstash/ratelimit/blob/main/src/duration.ts
2 |
3 | type Unit = 'ms' | 's' | 'm' | 'h' | 'd'
4 | export type Duration = `${number} ${Unit}` | `${number}${Unit}`
5 |
6 | /**
7 | * Convert a human readable duration to milliseconds
8 | */
9 | export function ms(d: Duration): number {
10 | const match = d.match(/^(\d+)\s?(ms|s|m|h|d)$/)
11 | if (!match) {
12 | throw new Error(`Unable to parse window size: ${d}`)
13 | }
14 | const time = Number.parseInt(match[1])
15 | const unit = match[2] as Unit
16 |
17 | switch (unit) {
18 | case 'ms': {
19 | return time
20 | }
21 | case 's': {
22 | return time * 1000
23 | }
24 | case 'm': {
25 | return time * 1000 * 60
26 | }
27 | case 'h': {
28 | return time * 1000 * 60 * 60
29 | }
30 | case 'd': {
31 | return time * 1000 * 60 * 60 * 24
32 | }
33 |
34 | default: {
35 | throw new Error(`Unable to parse window size: ${d}`)
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/lib/messages.ts:
--------------------------------------------------------------------------------
1 | import { FragmentSchema } from './schema'
2 | import { ExecutionResult } from './types'
3 | import { DeepPartial } from 'ai'
4 |
5 | export type MessageText = {
6 | type: 'text'
7 | text: string
8 | }
9 |
10 | export type MessageCode = {
11 | type: 'code'
12 | text: string
13 | }
14 |
15 | export type MessageImage = {
16 | type: 'image'
17 | image: string
18 | }
19 |
20 | export type Message = {
21 | role: 'assistant' | 'user' | 'ai'
22 | content: Array
23 | object?: DeepPartial
24 | result?: ExecutionResult
25 | }
26 |
27 | export function toAISDKMessages(messages: Message[]) {
28 | return messages.map((message) => ({
29 | role: message.role,
30 | content: message.content.map((content) => {
31 | if (content.type === 'code') {
32 | return {
33 | type: 'text',
34 | text: content.text,
35 | }
36 | }
37 |
38 | return content
39 | }),
40 | }))
41 | }
42 |
43 | export async function toMessageImage(files: File[]) {
44 | if (files.length === 0) {
45 | return []
46 | }
47 |
48 | return Promise.all(
49 | files.map(async (file) => {
50 | const base64 = Buffer.from(await file.arrayBuffer()).toString('base64')
51 | return `data:${file.type};base64,${base64}`
52 | }),
53 | )
54 | }
55 |
--------------------------------------------------------------------------------
/lib/models copy.json:
--------------------------------------------------------------------------------
1 | {
2 | "models": [
3 | {
4 | "id": "isfot-ai",
5 | "provider": "Azure",
6 | "providerId": "azure",
7 | "name": "Azure",
8 | "multiModal": true
9 | },
10 | {
11 | "id": "o1-preview",
12 | "provider": "OpenAI",
13 | "providerId": "openai",
14 | "name": "o1 (Preview)",
15 | "multiModal": false
16 | },
17 | {
18 | "id": "o1-mini",
19 | "provider": "OpenAI",
20 | "providerId": "openai",
21 | "name": "o1 mini",
22 | "multiModal": false
23 | },
24 | {
25 | "id": "gpt-4o",
26 | "provider": "OpenAI",
27 | "providerId": "openai",
28 | "name": "GPT-4o",
29 | "multiModal": true
30 | },
31 | {
32 | "id": "gpt-4o-mini",
33 | "provider": "OpenAI",
34 | "providerId": "openai",
35 | "name": "GPT-4o Mini",
36 | "multiModal": true
37 | },
38 | {
39 | "id": "gpt-4-turbo",
40 | "provider": "OpenAI",
41 | "providerId": "openai",
42 | "name": "GPT-4 Turbo",
43 | "multiModal": true
44 | },
45 | {
46 | "id": "claude-3-5-sonnet-latest",
47 | "provider": "Anthropic",
48 | "providerId": "anthropic",
49 | "name": "Claude 3.5 Sonnet",
50 | "multiModal": true
51 | },
52 | {
53 | "id": "claude-3-5-haiku-latest",
54 | "provider": "Anthropic",
55 | "providerId": "anthropic",
56 | "name": "Claude 3.5 Haiku",
57 | "multiModal": false
58 | },
59 | {
60 | "id": "gemini-1.5-pro-002",
61 | "provider": "Google Vertex AI",
62 | "providerId": "vertex",
63 | "name": "Gemini Pro",
64 | "multiModal": true
65 | },
66 | {
67 | "id": "gemini-1.5-flash-002",
68 | "provider": "Google Vertex AI",
69 | "providerId": "vertex",
70 | "name": "Gemini Flash",
71 | "multiModal": true
72 | },
73 | {
74 | "id": "models/gemini-1.5-pro-002",
75 | "provider": "Google Generative AI",
76 | "providerId": "google",
77 | "name": "Gemini Pro",
78 | "multiModal": true
79 | },
80 | {
81 | "id": "models/gemini-1.5-flash-002",
82 | "provider": "Google Generative AI",
83 | "providerId": "google",
84 | "name": "Gemini Flash",
85 | "multiModal": true
86 | },
87 | {
88 | "id": "gemini-exp-1121",
89 | "provider": "Google Generative AI",
90 | "providerId": "google",
91 | "name": "Gemini Experimental 1121",
92 | "multiModal": true
93 | },
94 | {
95 | "id": "mistral-large-latest",
96 | "provider": "Mistral",
97 | "providerId": "mistral",
98 | "name": "Mistral Large",
99 | "multiModal": false
100 | },
101 | {
102 | "id": "pixtral-large-latest",
103 | "provider": "Mistral",
104 | "providerId": "mistral",
105 | "name": "Pixtral Large",
106 | "multiModal": true
107 | },
108 | {
109 | "id": "mistral-small-latest",
110 | "provider": "Mistral",
111 | "providerId": "mistral",
112 | "name": "Mistral Small",
113 | "multiModal": false
114 | },
115 | {
116 | "id": "open-mistral-nemo",
117 | "provider": "Mistral",
118 | "providerId": "mistral",
119 | "name": "Mistral Nemo",
120 | "multiModal": false
121 | },
122 | {
123 | "id": "llama-3.1-70b-versatile",
124 | "provider": "Groq",
125 | "providerId": "groq",
126 | "name": "Llama 3.1 70B (Preview)",
127 | "multiModal": false
128 | },
129 | {
130 | "id": "llama3-groq-70b-8192-tool-use-preview",
131 | "provider": "Groq",
132 | "providerId": "groq",
133 | "name": "Llama 3 Groq 70B Tool Use (Preview)",
134 | "multiModal": false
135 | },
136 | {
137 | "id": "accounts/fireworks/models/llama-v3p1-405b-instruct",
138 | "provider": "Fireworks",
139 | "providerId": "fireworks",
140 | "name": "Llama 3.1 405B",
141 | "multiModal": false
142 | },
143 | {
144 | "id": "accounts/fireworks/models/llama-v3p1-70b-instruct",
145 | "provider": "Fireworks",
146 | "providerId": "fireworks",
147 | "name": "Llama 3.1 70B",
148 | "multiModal": false
149 | },
150 | {
151 | "id": "accounts/fireworks/models/qwen2p5-coder-32b-instruct",
152 | "provider": "Fireworks",
153 | "providerId": "fireworks",
154 | "name": "Qwen2.5-Coder-32B-Instruct",
155 | "multiModal": false
156 | },
157 | {
158 | "id": "accounts/fireworks/models/qwen-qwq-32b-preview",
159 | "provider": "Fireworks",
160 | "providerId": "fireworks",
161 | "name": "Qwen-QWQ-32B-Preview",
162 | "multiModal": false
163 | },
164 | {
165 | "id": "meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo",
166 | "provider": "Together AI",
167 | "providerId": "togetherai",
168 | "name": "Llama 3.1 70B",
169 | "multiModal": false
170 | },
171 | {
172 | "id": "grok-beta",
173 | "provider": "xAI",
174 | "providerId": "xai",
175 | "name": "Grok (Beta)",
176 | "multiModal": false
177 | },
178 | {
179 | "id": "llama3.1",
180 | "provider": "Ollama",
181 | "providerId": "ollama",
182 | "name": "Llama 3.1",
183 | "multiModal": false
184 | },
185 | {
186 | "id": "mistral-nemo",
187 | "provider": "Ollama",
188 | "providerId": "ollama",
189 | "name": "Mistral Nemo",
190 | "multiModal": false
191 | },
192 | {
193 | "id": "mistral-large",
194 | "provider": "Ollama",
195 | "providerId": "ollama",
196 | "name": "Mistral Large",
197 | "multiModal": false
198 | }
199 | ]
200 | }
201 |
--------------------------------------------------------------------------------
/lib/models.json:
--------------------------------------------------------------------------------
1 | {
2 | "models": [
3 | {
4 | "id": "isfot-ai",
5 | "provider": "Azure",
6 | "providerId": "azure",
7 | "name": "Azure",
8 | "multiModal": true
9 | },
10 | {
11 | "id": "o1-preview",
12 | "provider": "OpenAI",
13 | "providerId": "openai",
14 | "name": "o1 (Preview)",
15 | "multiModal": false
16 | },
17 | {
18 | "id": "o1-mini",
19 | "provider": "OpenAI",
20 | "providerId": "openai",
21 | "name": "o1 mini",
22 | "multiModal": false
23 | },
24 | {
25 | "id": "gpt-4o",
26 | "provider": "OpenAI",
27 | "providerId": "openai",
28 | "name": "GPT-4o",
29 | "multiModal": true
30 | },
31 | {
32 | "id": "gpt-4o-mini",
33 | "provider": "OpenAI",
34 | "providerId": "openai",
35 | "name": "GPT-4o Mini",
36 | "multiModal": true
37 | },
38 | {
39 | "id": "gpt-4-turbo",
40 | "provider": "OpenAI",
41 | "providerId": "openai",
42 | "name": "GPT-4 Turbo",
43 | "multiModal": true
44 | },
45 | {
46 | "id": "claude-3-5-sonnet-latest",
47 | "provider": "Anthropic",
48 | "providerId": "anthropic",
49 | "name": "Claude 3.5 Sonnet",
50 | "multiModal": true
51 | },
52 | {
53 | "id": "claude-3-5-haiku-latest",
54 | "provider": "Anthropic",
55 | "providerId": "anthropic",
56 | "name": "Claude 3.5 Haiku",
57 | "multiModal": false
58 | },
59 | {
60 | "id": "gemini-1.5-pro-002",
61 | "provider": "Google Vertex AI",
62 | "providerId": "vertex",
63 | "name": "Gemini Pro",
64 | "multiModal": true
65 | },
66 | {
67 | "id": "gemini-1.5-flash-002",
68 | "provider": "Google Vertex AI",
69 | "providerId": "vertex",
70 | "name": "Gemini Flash",
71 | "multiModal": true
72 | },
73 | {
74 | "id": "models/gemini-1.5-pro-002",
75 | "provider": "Google Generative AI",
76 | "providerId": "google",
77 | "name": "Gemini Pro",
78 | "multiModal": true
79 | },
80 | {
81 | "id": "models/gemini-1.5-flash-002",
82 | "provider": "Google Generative AI",
83 | "providerId": "google",
84 | "name": "Gemini Flash",
85 | "multiModal": true
86 | },
87 | {
88 | "id": "gemini-exp-1121",
89 | "provider": "Google Generative AI",
90 | "providerId": "google",
91 | "name": "Gemini Experimental 1121",
92 | "multiModal": true
93 | },
94 | {
95 | "id": "mistral-large-latest",
96 | "provider": "Mistral",
97 | "providerId": "mistral",
98 | "name": "Mistral Large",
99 | "multiModal": false
100 | },
101 | {
102 | "id": "pixtral-large-latest",
103 | "provider": "Mistral",
104 | "providerId": "mistral",
105 | "name": "Pixtral Large",
106 | "multiModal": true
107 | },
108 | {
109 | "id": "mistral-small-latest",
110 | "provider": "Mistral",
111 | "providerId": "mistral",
112 | "name": "Mistral Small",
113 | "multiModal": false
114 | },
115 | {
116 | "id": "open-mistral-nemo",
117 | "provider": "Mistral",
118 | "providerId": "mistral",
119 | "name": "Mistral Nemo",
120 | "multiModal": false
121 | },
122 | {
123 | "id": "llama-3.1-70b-versatile",
124 | "provider": "Groq",
125 | "providerId": "groq",
126 | "name": "Llama 3.1 70B (Preview)",
127 | "multiModal": false
128 | },
129 | {
130 | "id": "llama3-groq-70b-8192-tool-use-preview",
131 | "provider": "Groq",
132 | "providerId": "groq",
133 | "name": "Llama 3 Groq 70B Tool Use (Preview)",
134 | "multiModal": false
135 | },
136 | {
137 | "id": "accounts/fireworks/models/llama-v3p1-405b-instruct",
138 | "provider": "Fireworks",
139 | "providerId": "fireworks",
140 | "name": "Llama 3.1 405B",
141 | "multiModal": false
142 | },
143 | {
144 | "id": "accounts/fireworks/models/llama-v3p1-70b-instruct",
145 | "provider": "Fireworks",
146 | "providerId": "fireworks",
147 | "name": "Llama 3.1 70B",
148 | "multiModal": false
149 | },
150 | {
151 | "id": "accounts/fireworks/models/qwen2p5-coder-32b-instruct",
152 | "provider": "Fireworks",
153 | "providerId": "fireworks",
154 | "name": "Qwen2.5-Coder-32B-Instruct",
155 | "multiModal": false
156 | },
157 | {
158 | "id": "accounts/fireworks/models/qwen-qwq-32b-preview",
159 | "provider": "Fireworks",
160 | "providerId": "fireworks",
161 | "name": "Qwen-QWQ-32B-Preview",
162 | "multiModal": false
163 | },
164 | {
165 | "id": "meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo",
166 | "provider": "Together AI",
167 | "providerId": "togetherai",
168 | "name": "Llama 3.1 70B",
169 | "multiModal": false
170 | },
171 | {
172 | "id": "grok-beta",
173 | "provider": "xAI",
174 | "providerId": "xai",
175 | "name": "Grok (Beta)",
176 | "multiModal": false
177 | },
178 | {
179 | "id": "llama3.1",
180 | "provider": "Ollama",
181 | "providerId": "ollama",
182 | "name": "Llama 3.1",
183 | "multiModal": false
184 | },
185 | {
186 | "id": "mistral-nemo",
187 | "provider": "Ollama",
188 | "providerId": "ollama",
189 | "name": "Mistral Nemo",
190 | "multiModal": false
191 | },
192 | {
193 | "id": "mistral-large",
194 | "provider": "Ollama",
195 | "providerId": "ollama",
196 | "name": "Mistral Large",
197 | "multiModal": false
198 | }
199 | ]
200 | }
201 |
--------------------------------------------------------------------------------
/lib/models.ts:
--------------------------------------------------------------------------------
1 | import { createAnthropic } from '@ai-sdk/anthropic'
2 | import { createGoogleGenerativeAI } from '@ai-sdk/google'
3 | import { createVertex } from '@ai-sdk/google-vertex'
4 | import { createMistral } from '@ai-sdk/mistral'
5 | import { createOpenAI } from '@ai-sdk/openai'
6 | import { createOllama } from 'ollama-ai-provider'
7 | import { createAzure } from '@ai-sdk/azure';
8 |
9 | export type LLMModel = {
10 | id: string
11 | name: string
12 | provider: string
13 | providerId: string
14 | }
15 |
16 | export type LLMModelConfig = {
17 | model?: string
18 | apiKey?: string
19 | baseURL?: string
20 | temperature?: number
21 | topP?: number
22 | topK?: number
23 | frequencyPenalty?: number
24 | presencePenalty?: number
25 | maxTokens?: number
26 | }
27 |
28 | export function getModelClient(model: LLMModel, config: LLMModelConfig) {
29 | const { id: modelNameString, providerId } = model
30 | const { apiKey, baseURL } = config
31 |
32 | const providerConfigs = {
33 | anthropic: () => createAnthropic({ apiKey, baseURL })(modelNameString),
34 | openai: () => createOpenAI({ apiKey, baseURL })(modelNameString),
35 | azure: () => {
36 | // 接入 自己的模型
37 | return createAzure({
38 | // apiKey:'c70ec31c1d794452a5e18eefb0e72781',
39 | // baseURL:'https://isfot-ai.openai.azure.com/openai/deployments',
40 | apiKey: apiKey || process.env.AZURE_API_KEY,
41 | baseURL: baseURL || 'https://isfot-ai.openai.azure.com/openai/deployments',
42 | apiVersion:'2024-08-01-preview',
43 | })(modelNameString)
44 | },
45 | google: () =>
46 | createGoogleGenerativeAI({ apiKey, baseURL })(modelNameString),
47 | mistral: () => createMistral({ apiKey, baseURL })(modelNameString),
48 | groq: () =>
49 | createOpenAI({
50 | apiKey: apiKey || process.env.GROQ_API_KEY,
51 | baseURL: baseURL || 'https://api.groq.com/openai/v1',
52 | })(modelNameString),
53 | togetherai: () =>
54 | createOpenAI({
55 | apiKey: apiKey || process.env.TOGETHER_API_KEY,
56 | baseURL: baseURL || 'https://api.together.xyz/v1',
57 | })(modelNameString),
58 | ollama: () => createOllama({ baseURL })(modelNameString),
59 | fireworks: () =>
60 | createOpenAI({
61 | apiKey: apiKey || process.env.FIREWORKS_API_KEY,
62 | baseURL: baseURL || 'https://api.fireworks.ai/inference/v1',
63 | })(modelNameString),
64 | vertex: () =>
65 | createVertex({
66 | googleAuthOptions: {
67 | credentials: JSON.parse(
68 | process.env.GOOGLE_VERTEX_CREDENTIALS || '{}',
69 | ),
70 | },
71 | })(modelNameString),
72 | xai: () =>
73 | createOpenAI({
74 | apiKey: apiKey || process.env.XAI_API_KEY,
75 | baseURL: baseURL || 'https://api.x.ai/v1',
76 | })(modelNameString),
77 | }
78 |
79 | const createClient =
80 | providerConfigs[providerId as keyof typeof providerConfigs]
81 |
82 | if (!createClient) {
83 | throw new Error(`Unsupported provider: ${providerId}`)
84 | }
85 |
86 | return createClient()
87 | }
88 |
89 | export function getDefaultMode(model: LLMModel) {
90 | const { id: modelNameString, providerId } = model
91 |
92 | // monkey patch fireworks
93 | if (providerId === 'fireworks') {
94 | return 'json'
95 | }
96 |
97 | return 'auto'
98 | }
99 |
--------------------------------------------------------------------------------
/lib/prompt.ts:
--------------------------------------------------------------------------------
1 | import { Templates, templatesToPrompt } from '@/lib/templates'
2 |
3 | export function toPrompt(template: Templates) {
4 | return `
5 | You are a skilled software engineer.
6 | You do not make mistakes.
7 | Generate an fragment.
8 | You can install additional dependencies.
9 | Do not touch project dependencies files like package.json, package-lock.json, requirements.txt, etc.
10 | You can use one of the following templates:
11 | ${templatesToPrompt(template)}
12 | `
13 | }
14 |
--------------------------------------------------------------------------------
/lib/ratelimit.ts:
--------------------------------------------------------------------------------
1 | import { Duration } from './duration'
2 | import { Ratelimit } from '@upstash/ratelimit'
3 | import { kv } from '@vercel/kv'
4 |
5 | export default async function ratelimit(
6 | key: string | null,
7 | maxRequests: number,
8 | window: Duration,
9 | ) {
10 | if (process.env.KV_REST_API_URL && process.env.KV_REST_API_TOKEN) {
11 | const ratelimit = new Ratelimit({
12 | redis: kv,
13 | limiter: Ratelimit.slidingWindow(maxRequests, window),
14 | })
15 |
16 | const { success, limit, reset, remaining } = await ratelimit.limit(
17 | `ratelimit_${key}`,
18 | )
19 |
20 | if (!success) {
21 | return {
22 | amount: limit,
23 | reset,
24 | remaining,
25 | }
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/lib/request.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | axios.defaults.timeout = 300000
4 |
5 | axios.defaults.baseURL = process.env.NEXT_PUBLIC_API_URL;
6 |
7 | // 请求拦截器
8 | axios.interceptors.request.use(
9 | (config) => {
10 | return config
11 | },
12 | (error) => {
13 | return Promise.reject(error)
14 | }
15 | )
16 | //HTTPrequest拦截
17 | axios.interceptors.response.use(
18 | (config) => {
19 | return config
20 | },
21 | (error) => {
22 | // if(error.response.status == 500){}
23 | return error.response
24 | }
25 | )
26 |
27 | export default axios
28 |
--------------------------------------------------------------------------------
/lib/schema.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod'
2 |
3 | export const fragmentSchema = z.object({
4 | commentary: z.string().describe(`Describe what you're about to do and the steps you want to take for generating the fragment in great detail.`),
5 | template: z.string().describe('Name of the template used to generate the fragment.'),
6 | // template_ready: z.boolean().describe('Detect if finished identifying the template.'),
7 | title: z.string().describe('Short title of the fragment. Max 3 words.'),
8 | description: z.string().describe('Short description of the fragment. Max 1 sentence.'),
9 | additional_dependencies: z.array(z.string()).describe('Additional dependencies required by the fragment. Do not include dependencies that are already included in the template.'),
10 | has_additional_dependencies: z.boolean().describe('Detect if additional dependencies that are not included in the template are required by the fragment.'),
11 | install_dependencies_command: z.string().describe('Command to install additional dependencies required by the fragment.'),
12 | // install_dependencies_ready: z.boolean().describe('Detect if finished identifying additional dependencies.'),
13 | port: z.number().nullable().describe('Port number used by the resulted fragment. Null when no ports are exposed.'),
14 | file_path: z.string().describe('Relative path to the file, including the file name.'),
15 | code: z.string().describe('Code generated by the fragment. Only runnable code is allowed.'),
16 | // code: z.array(z.object({
17 | // file_name: z.string().describe('Name of the file.'),
18 | // file_path: z.string().describe('Relative path to the file, including the file name.'),
19 | // file_content: z.string().describe('Content of the file.'),
20 | // file_finished: z.boolean().describe('Detect if finished generating the file.'),
21 | // })),
22 | // code_finished: z.boolean().describe('Detect if finished generating the code.'),
23 | // error: z.string().optional().describe('Error message if the fragment is not valid.'),
24 | })
25 |
26 | export type FragmentSchema = z.infer
27 |
--------------------------------------------------------------------------------
/lib/supabase.ts:
--------------------------------------------------------------------------------
1 | import { createClient } from '@supabase/supabase-js'
2 |
3 | export const supabase = process.env.NEXT_PUBLIC_ENABLE_SUPABASE
4 | ? createClient(
5 | process.env.NEXT_PUBLIC_SUPABASE_URL!,
6 | process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
7 | )
8 | : undefined
9 |
--------------------------------------------------------------------------------
/lib/templates.json:
--------------------------------------------------------------------------------
1 | {
2 | "code-interpreter-v1": {
3 | "name": "Python data analyst",
4 | "lib": ["python", "jupyter", "numpy", "pandas", "matplotlib", "seaborn", "plotly"],
5 | "file": "script.py",
6 | "instructions": "Runs code as a Jupyter notebook cell. Strong data analysis angle. Can use complex visualisation to explain results.",
7 | "port": null
8 | },
9 | "nextjs-developer": {
10 | "name": "Next.js developer",
11 | "lib": ["nextjs@14.2.5", "typescript", "@types/node", "@types/react", "@types/react-dom", "postcss", "tailwindcss", "shadcn"],
12 | "file": "pages/index.tsx",
13 | "instructions": "A Next.js 13+ app that reloads automatically. Using the pages router.",
14 | "port": 3000
15 | },
16 | "vue-developer": {
17 | "name": "Vue.js developer",
18 | "lib": ["vue@latest", "nuxt@3.13.0", "tailwindcss"],
19 | "file": "app.vue",
20 | "instructions": "A Vue.js 3+ app that reloads automatically. Only when asked specifically for a Vue app.",
21 | "port": 3000
22 | },
23 | "streamlit-developer": {
24 | "name": "Streamlit developer",
25 | "lib": [
26 | "streamlit",
27 | "pandas",
28 | "numpy",
29 | "matplotlib",
30 | "request",
31 | "seaborn",
32 | "plotly"
33 | ],
34 | "file": "app.py",
35 | "instructions": "A streamlit app that reloads automatically.",
36 | "port": 8501
37 | },
38 | "gradio-developer": {
39 | "name": "Gradio developer",
40 | "lib": [
41 | "gradio",
42 | "pandas",
43 | "numpy",
44 | "matplotlib",
45 | "request",
46 | "seaborn",
47 | "plotly"
48 | ],
49 | "file": "app.py",
50 | "instructions": "A gradio app. Gradio Blocks/Interface should be called demo.",
51 | "port": 7860
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/lib/templates.ts:
--------------------------------------------------------------------------------
1 | import templates from './templates.json'
2 |
3 | export default templates
4 | export type Templates = typeof templates
5 | export type TemplateId = keyof typeof templates
6 | export type TemplateConfig = typeof templates[TemplateId]
7 |
8 | export function templatesToPrompt(templates: Templates) {
9 | return `${Object.entries(templates).map(([id, t], index) => `${index + 1}. ${id}: "${t.instructions}". File: ${t.file || 'none'}. Dependencies installed: ${t.lib.join(', ')}. Port: ${t.port || 'none'}.`).join('\n')}`
10 | }
11 |
--------------------------------------------------------------------------------
/lib/types.ts:
--------------------------------------------------------------------------------
1 | import { TemplateId } from './templates'
2 | import { ExecutionError, Result } from '@e2b/code-interpreter'
3 |
4 | type ExecutionResultBase = {
5 | sbxId: string
6 | }
7 |
8 | export type ExecutionResultInterpreter = ExecutionResultBase & {
9 | template: 'code-interpreter-v1'
10 | stdout: string[]
11 | stderr: string[]
12 | runtimeError?: ExecutionError
13 | cellResults: Result[]
14 | }
15 |
16 | export type ExecutionResultWeb = ExecutionResultBase & {
17 | template: Exclude
18 | url: string
19 | }
20 |
21 | export type ExecutionResult = ExecutionResultInterpreter | ExecutionResultWeb
22 |
--------------------------------------------------------------------------------
/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { clsx, type ClassValue } from 'clsx'
2 | import { twMerge } from 'tailwind-merge'
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/middleware.ts:
--------------------------------------------------------------------------------
1 | import { kv } from '@vercel/kv'
2 | import { NextResponse } from 'next/server'
3 | import type { NextRequest } from 'next/server'
4 |
5 | export async function middleware(req: NextRequest) {
6 | if (process.env.KV_REST_API_URL && process.env.KV_REST_API_TOKEN) {
7 | const id = req.nextUrl.pathname.split('/').pop()
8 | const url = await kv.get(`fragment:${id}`)
9 |
10 | if (url) {
11 | return NextResponse.redirect(url as string)
12 | } else {
13 | return NextResponse.redirect(new URL('/', req.url))
14 | }
15 | }
16 |
17 | return NextResponse.redirect(new URL('/', req.url))
18 | }
19 |
20 | export const config = {
21 | matcher: '/s/:path*',
22 | }
23 |
--------------------------------------------------------------------------------
/next.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {}
3 |
4 | export default nextConfig
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "efflux-frontend",
3 | "version": "1.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev --turbo",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@ai-sdk/anthropic": "^0.0.23",
13 | "@ai-sdk/azure": "^1.0.10",
14 | "@ai-sdk/google": "^0.0.49",
15 | "@ai-sdk/google-vertex": "^0.0.39",
16 | "@ai-sdk/mistral": "^1.0.1",
17 | "@ai-sdk/openai": "^0.0.59",
18 | "@e2b/code-interpreter": "^1.0.2",
19 | "@radix-ui/react-alert-dialog": "^1.1.4",
20 | "@radix-ui/react-avatar": "^1.1.0",
21 | "@radix-ui/react-dialog": "^1.1.1",
22 | "@radix-ui/react-dropdown-menu": "^2.1.1",
23 | "@radix-ui/react-form": "^0.1.1",
24 | "@radix-ui/react-icons": "^1.3.0",
25 | "@radix-ui/react-label": "^2.1.0",
26 | "@radix-ui/react-select": "^2.1.1",
27 | "@radix-ui/react-separator": "^1.1.0",
28 | "@radix-ui/react-slot": "^1.1.0",
29 | "@radix-ui/react-tabs": "^1.1.0",
30 | "@radix-ui/react-toast": "^1.2.1",
31 | "@radix-ui/react-tooltip": "^1.1.2",
32 | "@radix-ui/react-visually-hidden": "^1.1.0",
33 | "@supabase/auth-ui-react": "^0.4.7",
34 | "@supabase/auth-ui-shared": "^0.1.8",
35 | "@supabase/supabase-js": "^2.45.1",
36 | "@upstash/ratelimit": "^2.0.1",
37 | "@vercel/kv": "^2.0.0",
38 | "ai": "^3.3.8",
39 | "axios": "^0.19.0",
40 | "class-variance-authority": "^0.7.0",
41 | "clsx": "^2.1.1",
42 | "core-js": "^3.38.0",
43 | "e2b": "^1.0.5",
44 | "lucide-react": "^0.396.0",
45 | "next": "^14.2.8",
46 | "next-themes": "^0.3.0",
47 | "ollama-ai-provider": "^0.12.0",
48 | "posthog-js": "^1.158.3",
49 | "prismjs": "^1.29.0",
50 | "react": "^18",
51 | "react-dom": "^18",
52 | "react-markdown": "^9.0.3",
53 | "react-textarea-autosize": "^8.5.3",
54 | "tailwind-merge": "^2.5.2",
55 | "tailwindcss-animate": "^1.0.7",
56 | "usehooks-ts": "^3.1.0",
57 | "zod": "^3.23.8"
58 | },
59 | "devDependencies": {
60 | "@trivago/prettier-plugin-sort-imports": "^4.3.0",
61 | "@types/node": "^22.2.0",
62 | "@types/prismjs": "^1.26.4",
63 | "@types/react": "^18",
64 | "@types/react-dom": "^18",
65 | "eslint": "^8",
66 | "eslint-config-next": "14.2.4",
67 | "postcss": "^8",
68 | "tailwindcss": "^3.4.1",
69 | "typescript": "^5.5.4"
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('postcss-load-config').Config} */
2 | const config = {
3 | plugins: {
4 | tailwindcss: {},
5 | },
6 | }
7 |
8 | export default config
9 |
--------------------------------------------------------------------------------
/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/isoftstone-data-intelligence-ai/efflux-frontend/1b941cc4ed746995a952e72cd8afa4c86eb4eab5/preview.png
--------------------------------------------------------------------------------
/public/thirdparty/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/isoftstone-data-intelligence-ai/efflux-frontend/1b941cc4ed746995a952e72cd8afa4c86eb4eab5/public/thirdparty/logo.png
--------------------------------------------------------------------------------
/public/thirdparty/logo2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/isoftstone-data-intelligence-ai/efflux-frontend/1b941cc4ed746995a952e72cd8afa4c86eb4eab5/public/thirdparty/logo2.png
--------------------------------------------------------------------------------
/public/thirdparty/logos/anthropic.svg:
--------------------------------------------------------------------------------
1 | Anthropic
2 |
--------------------------------------------------------------------------------
/public/thirdparty/logos/azure.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/thirdparty/logos/fireworks.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/public/thirdparty/logos/fireworksai.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/public/thirdparty/logos/google.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/public/thirdparty/logos/groq.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/public/thirdparty/logos/mistral.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/public/thirdparty/logos/ollama.svg:
--------------------------------------------------------------------------------
1 | Ollama
2 |
--------------------------------------------------------------------------------
/public/thirdparty/logos/openai.svg:
--------------------------------------------------------------------------------
1 | OpenAI
2 |
--------------------------------------------------------------------------------
/public/thirdparty/logos/togetherai.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/public/thirdparty/logos/vertex.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/thirdparty/logos/xai.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/thirdparty/templates/gradio-developer.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 | gradio
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/public/thirdparty/templates/nextjs-developer.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/public/thirdparty/templates/streamlit-developer.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/public/thirdparty/templates/vue-developer.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/sandbox-templates/gradio-developer/app.py:
--------------------------------------------------------------------------------
1 | # Scaffolding for a Streamlit app
2 | import gradio as gr
3 |
4 | def greet(name, intensity):
5 | return "Hello, " + name + "!" * int(intensity)
6 |
7 | demo = gr.Interface(
8 | fn=greet,
9 | inputs=["text", "slider"],
10 | outputs=["text"],
11 | )
12 |
13 | demo.launch()
14 |
--------------------------------------------------------------------------------
/sandbox-templates/gradio-developer/e2b.Dockerfile:
--------------------------------------------------------------------------------
1 | # You can use most Debian-based base images
2 | FROM python:3.9-slim
3 |
4 | RUN pip3 install --no-cache-dir gradio pandas numpy matplotlib requests seaborn plotly
5 |
6 | # Copy the code to the container
7 | WORKDIR /home/user
8 | COPY . /home/user
9 |
--------------------------------------------------------------------------------
/sandbox-templates/gradio-developer/e2b.toml:
--------------------------------------------------------------------------------
1 | # This is a config for E2B sandbox template.
2 | # You can use 'template_id' (1ypi8ae3amtyxttny60k) or 'template_name (gradio-developer) from this config to spawn a sandbox:
3 |
4 | # Python SDK
5 | # from e2b import Sandbox
6 | # sandbox = Sandbox(template='gradio-developer')
7 |
8 | # JS SDK
9 | # import { Sandbox } from 'e2b'
10 | # const sandbox = await Sandbox.create({ template: 'gradio-developer' })
11 |
12 | template_id = "1ypi8ae3amtyxttny60k"
13 | dockerfile = "e2b.Dockerfile"
14 | template_name = "gradio-developer"
15 | start_cmd = "cd /home/user && gradio app.py"
16 | cpu_count = 4
17 | memory_mb = 4_096
18 | team_id = "460355b3-4f64-48f9-9a16-4442817f79f5"
19 |
--------------------------------------------------------------------------------
/sandbox-templates/nextjs-developer/_app.tsx:
--------------------------------------------------------------------------------
1 | import "@/styles/globals.css";
2 | import posthog from "posthog-js";
3 | import { PostHogProvider } from "posthog-js/react";
4 | import type { AppProps } from "next/app";
5 |
6 | if (typeof window !== "undefined") {
7 | posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
8 | api_host:
9 | process.env.NEXT_PUBLIC_POSTHOG_HOST || "https://us.i.posthog.com",
10 | person_profiles: "identified_only",
11 | session_recording: {
12 | recordCrossOriginIframes: true,
13 | },
14 | });
15 | }
16 |
17 | export default function App({ Component, pageProps }: AppProps) {
18 | return (
19 |
20 |
21 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/sandbox-templates/nextjs-developer/compile_page.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | export NEXT_PUBLIC_POSTHOG_KEY=""
3 | export NEXT_PUBLIC_POSTHOG_HOST=""
4 |
5 | # This script runs during building the sandbox template
6 | # and makes sure the Next.js app is (1) running and (2) the `/` page is compiled
7 | function ping_server() {
8 | counter=0
9 | response=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:3000")
10 | while [[ ${response} -ne 200 ]]; do
11 | let counter++
12 | if (( counter % 20 == 0 )); then
13 | echo "Waiting for server to start..."
14 | sleep 0.1
15 | fi
16 |
17 | response=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:3000")
18 | done
19 | }
20 |
21 | ping_server &
22 | cd /home/user && npx next --turbo
23 |
--------------------------------------------------------------------------------
/sandbox-templates/nextjs-developer/e2b.Dockerfile:
--------------------------------------------------------------------------------
1 | # You can use most Debian-based base images
2 | FROM node:21-slim
3 |
4 | # Install curl
5 | RUN apt-get update && apt-get install -y curl && apt-get clean && rm -rf /var/lib/apt/lists/*
6 |
7 | COPY compile_page.sh /compile_page.sh
8 | RUN chmod +x /compile_page.sh
9 |
10 | # Install dependencies and customize sandbox
11 | WORKDIR /home/user/nextjs-app
12 |
13 | RUN npx create-next-app@latest . --ts --tailwind --no-eslint --import-alias "@/*" --use-npm --no-app --no-src-dir
14 | COPY _app.tsx pages/_app.tsx
15 |
16 | RUN npx shadcn@latest init -d
17 | RUN npx shadcn@latest add --all
18 | RUN npm install posthog-js
19 |
20 | # Move the Nextjs app to the home directory and remove the nextjs-app directory
21 | RUN mv /home/user/nextjs-app/* /home/user/ && rm -rf /home/user/nextjs-app
22 |
--------------------------------------------------------------------------------
/sandbox-templates/nextjs-developer/e2b.toml:
--------------------------------------------------------------------------------
1 | # This is a config for E2B sandbox template.
2 | # You can use 'template_id' (scwxnhs1apt5uj7na7db) or 'template_name (nextjs-developer) from this config to spawn a sandbox:
3 |
4 | # Python SDK
5 | # from e2b import Sandbox
6 | # sandbox = Sandbox(template='nextjs-developer')
7 |
8 | # JS SDK
9 | # import { Sandbox } from 'e2b'
10 | # const sandbox = await Sandbox.create({ template: 'nextjs-developer' })
11 |
12 | template_id = "scwxnhs1apt5uj7na7db"
13 | dockerfile = "e2b.Dockerfile"
14 | template_name = "nextjs-developer"
15 | start_cmd = "/compile_page.sh"
16 | cpu_count = 4
17 | memory_mb = 4_096
18 | team_id = "460355b3-4f64-48f9-9a16-4442817f79f5"
19 |
--------------------------------------------------------------------------------
/sandbox-templates/streamlit-developer/app.py:
--------------------------------------------------------------------------------
1 | # Scaffolding for a Streamlit app
2 |
3 | import streamlit as st
4 |
5 | st.write("Hello, World!")
--------------------------------------------------------------------------------
/sandbox-templates/streamlit-developer/e2b.Dockerfile:
--------------------------------------------------------------------------------
1 | # You can use most Debian-based base images
2 | FROM python:3.9-slim
3 |
4 | RUN pip3 install --no-cache-dir streamlit pandas numpy matplotlib requests seaborn plotly
5 |
6 | # Copy the code to the container
7 | WORKDIR /home/user
8 | COPY . /home/user
9 |
--------------------------------------------------------------------------------
/sandbox-templates/streamlit-developer/e2b.toml:
--------------------------------------------------------------------------------
1 | # This is a config for E2B sandbox template.
2 | # You can use 'template_id' (rtnj5ynpykzr3jz95pmw) or 'template_name (streamlit-developer) from this config to spawn a sandbox:
3 |
4 | # Python SDK
5 | # from e2b import Sandbox
6 | # sandbox = Sandbox(template='streamlit-developer')
7 |
8 | # JS SDK
9 | # import { Sandbox } from 'e2b'
10 | # const sandbox = await Sandbox.create({ template: 'streamlit-developer' })
11 |
12 | template_id = "rtnj5ynpykzr3jz95pmw"
13 | dockerfile = "e2b.Dockerfile"
14 | template_name = "streamlit-developer"
15 | start_cmd = "cd /home/user && streamlit run app.py"
16 | cpu_count = 4
17 | memory_mb = 4_096
18 |
--------------------------------------------------------------------------------
/sandbox-templates/vue-developer/e2b.Dockerfile:
--------------------------------------------------------------------------------
1 | # You can use most Debian-based base images
2 | FROM node:21-slim
3 |
4 | # Install dependencies and customize sandbox
5 | WORKDIR /home/user/vue-app
6 |
7 | RUN npx nuxi@latest init . --packageManager=npm --gitInit=no -f
8 | RUN npx nuxi@latest module add tailwindcss
9 | COPY nuxt.config.ts /home/user/vue-app/nuxt.config.ts
10 |
11 | # Move the Vue app to the home directory and remove the Vue directory
12 | RUN mv /home/user/vue-app/* /home/user/ && rm -rf /home/user/vue-app
13 |
--------------------------------------------------------------------------------
/sandbox-templates/vue-developer/e2b.toml:
--------------------------------------------------------------------------------
1 | # This is a config for E2B sandbox template.
2 | # You can use 'template_id' (4sgwufi7skp03emzvbwm) or 'template_name (vue-developer) from this config to spawn a sandbox:
3 |
4 | # Python SDK
5 | # from e2b import Sandbox
6 | # sandbox = Sandbox(template='vue-developer')
7 |
8 | # JS SDK
9 | # import { Sandbox } from 'e2b'
10 | # const sandbox = await Sandbox.create({ template: 'vue-developer' })
11 |
12 | team_id = "460355b3-4f64-48f9-9a16-4442817f79f5"
13 | start_cmd = "cd /home/user && npx nuxi dev"
14 | dockerfile = "e2b.Dockerfile"
15 | template_name = "vue-developer"
16 | template_id = "4sgwufi7skp03emzvbwm"
17 |
--------------------------------------------------------------------------------
/sandbox-templates/vue-developer/nuxt.config.ts:
--------------------------------------------------------------------------------
1 | // https://nuxt.com/docs/api/configuration/nuxt-config
2 | // @ts-ignore Ignored to pass Vercel deployment
3 | export default defineNuxtConfig({
4 | compatibilityDate: '2024-04-03',
5 | devtools: { enabled: false },
6 | modules: ['@nuxtjs/tailwindcss'],
7 | vite: {
8 | server: {
9 | hmr: {
10 | protocol: 'wss'
11 | }
12 | }
13 | }
14 | })
15 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'tailwindcss'
2 |
3 | const config = {
4 | darkMode: ['class'],
5 | content: [
6 | './pages/**/*.{ts,tsx}',
7 | './components/**/*.{ts,tsx}',
8 | './app/**/*.{ts,tsx}',
9 | './src/**/*.{ts,tsx}',
10 | ],
11 | prefix: '',
12 | theme: {
13 | container: {
14 | center: true,
15 | padding: '2rem',
16 | screens: {
17 | '2xl': '1400px',
18 | },
19 | },
20 | extend: {
21 | colors: {
22 | border: 'hsl(var(--border))',
23 | input: 'hsl(var(--input))',
24 | ring: 'hsl(var(--ring))',
25 | background: 'hsl(var(--background))',
26 | foreground: 'hsl(var(--foreground))',
27 | primary: {
28 | DEFAULT: 'hsl(var(--primary))',
29 | foreground: 'hsl(var(--primary-foreground))',
30 | },
31 | secondary: {
32 | DEFAULT: 'hsl(var(--secondary))',
33 | foreground: 'hsl(var(--secondary-foreground))',
34 | },
35 | destructive: {
36 | DEFAULT: 'hsl(var(--destructive))',
37 | foreground: 'hsl(var(--destructive-foreground))',
38 | },
39 | muted: {
40 | DEFAULT: 'hsl(var(--muted))',
41 | foreground: 'hsl(var(--muted-foreground))',
42 | },
43 | accent: {
44 | DEFAULT: 'hsl(var(--accent))',
45 | foreground: 'hsl(var(--accent-foreground))',
46 | },
47 | popover: {
48 | DEFAULT: 'hsl(var(--popover))',
49 | foreground: 'hsl(var(--popover-foreground))',
50 | },
51 | card: {
52 | DEFAULT: 'hsl(var(--card))',
53 | foreground: 'hsl(var(--card-foreground))',
54 | },
55 | chart: {
56 | '1': 'hsl(var(--chart-1))',
57 | '2': 'hsl(var(--chart-2))',
58 | '3': 'hsl(var(--chart-3))',
59 | '4': 'hsl(var(--chart-4))',
60 | '5': 'hsl(var(--chart-5))',
61 | },
62 | },
63 | borderRadius: {
64 | lg: 'var(--radius)',
65 | md: 'calc(var(--radius) - 2px)',
66 | sm: 'calc(var(--radius) - 4px)',
67 | },
68 | keyframes: {
69 | 'accordion-down': {
70 | from: {
71 | height: '0',
72 | },
73 | to: {
74 | height: 'var(--radix-accordion-content-height)',
75 | },
76 | },
77 | 'accordion-up': {
78 | from: {
79 | height: 'var(--radix-accordion-content-height)',
80 | },
81 | to: {
82 | height: '0',
83 | },
84 | },
85 | },
86 | animation: {
87 | 'accordion-down': 'accordion-down 0.2s ease-out',
88 | 'accordion-up': 'accordion-up 0.2s ease-out',
89 | },
90 | },
91 | },
92 | plugins: [require('tailwindcss-animate')],
93 | } satisfies Config
94 |
95 | export default config
96 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "dom.iterable", "esnext"],
4 | "allowJs": true,
5 | "skipLibCheck": true,
6 | "strict": true,
7 | "noEmit": true,
8 | "esModuleInterop": true,
9 | "module": "esnext",
10 | "moduleResolution": "bundler",
11 | "resolveJsonModule": true,
12 | "isolatedModules": true,
13 | "jsx": "preserve",
14 | "incremental": true,
15 | "plugins": [
16 | {
17 | "name": "next"
18 | }
19 | ],
20 | "paths": {
21 | "@/*": ["./*"]
22 | }
23 | },
24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
25 | "exclude": ["node_modules"]
26 | }
27 |
--------------------------------------------------------------------------------