Skip to content

Commit 816e16d

Browse files
authored
Redesign (#59)
* added menu dropdown * sign in btn reset * updates to the layout * refinissimo * sizes * chatbox changes * updated file handing * design changes * design tokens, added back spinner * visual nicities * improved chat ux * api key input password * added url bar, make it more round * tooltips everywhere * fixes vercel build * added artifact history * added undo * share sheet * improves reactivity around recent changes * css grid * auto grid * mobile responsiveness * mobile-friendly sidebar * new chat reset chatinput * error handling + retry * grammar * improved imput ux, minor changes * updated design tokens * form overflow fix * refreshed sign in screen * moved sign in design around * google auth sign-in * updated logo, wording * logo component * added dark theme * tooltip theming * added light theme switch * removed unnecessary files/dependencies, added prettierrc * removed unused deps, code import sort * prettify * publish, url shortener * format select.tsx * publish sandbox extended timeout * publish button changes * fixes vercel build * pass e2b api key to publish * posthog capture on url publish * undo rebrand (for now) * next neutral logo * file conventions * renamed files * sandbox endpoint server action * renamed file structure * work on types * work on types and files * renamed side to preview * improved chat input error ui * fixes invalid property in svg * changed tooltip duration * flipped dark/light switch * new chat > clear chat * added tooltip for profile menu * added disclaimer * undo favicon * improved contrast ratio * added multi-file picker changed artifact view * changed display of loading states * duplicated loading state to the chat input * added toast * copy button and fixes preview streaming * fixes tooltip on copybutton * copy-button moved props order * changed share link > copy url * added display name to copy button * improved reactive data structures in preview * moved loader to more prominent place * chat input e2b star * moved llm settings to chat input * llm settings picker * moved toggle to navbar * sandbox api endpoint * updated preview.png * publish > deploy * lint, o1 rate limit env var --------- Co-authored-by: Mish Ushakov <[email protected]>
1 parent 88ad984 commit 816e16d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+3008
-2097
lines changed

.prettierrc

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"singleQuote": true,
3+
"semi": false,
4+
"plugins": ["@trivago/prettier-plugin-sort-imports"]
5+
}

app/actions/publish.ts

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
'use server'
2+
3+
import { Sandbox } from '@e2b/code-interpreter'
4+
import { kv } from '@vercel/kv'
5+
import { customAlphabet } from 'nanoid'
6+
7+
const nanoid = customAlphabet('1234567890abcdef', 7)
8+
const sandboxTimeout = 3 * 60 * 60 * 1000 // 3 hours
9+
10+
export async function publish(
11+
url: string,
12+
sbxId: string,
13+
apiKey: string | undefined,
14+
) {
15+
if (process.env.KV_REST_API_URL && process.env.KV_REST_API_TOKEN) {
16+
const id = nanoid()
17+
await kv.set(`fragment:${id}`, url)
18+
await Sandbox.setTimeout(sbxId, sandboxTimeout, { apiKey })
19+
20+
return {
21+
url: process.env.VERCEL_URL
22+
? `https://${process.env.VERCEL_URL}/s/${id}`
23+
: `/s/${id}`,
24+
}
25+
}
26+
27+
return {
28+
url,
29+
}
30+
}

app/api/chat-o1/route.ts

+31-17
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,51 @@
1-
import {
2-
streamObject,
3-
LanguageModel,
4-
CoreMessage,
5-
generateText,
6-
} from 'ai'
7-
8-
import ratelimit from '@/lib/ratelimit'
9-
import { Templates, templatesToPrompt } from '@/lib/templates'
10-
import { getModelClient, getDefaultMode } from '@/lib/models'
1+
import { getModelClient } from '@/lib/models'
112
import { LLMModel, LLMModelConfig } from '@/lib/models'
3+
import { toPrompt } from '@/lib/prompt'
4+
import ratelimit, { Duration } from '@/lib/ratelimit'
125
import { artifactSchema as schema } from '@/lib/schema'
6+
import { Templates, templatesToPrompt } from '@/lib/templates'
137
import { openai } from '@ai-sdk/openai'
14-
import { toPrompt } from '@/lib/prompt'
8+
import { streamObject, LanguageModel, CoreMessage, generateText } from 'ai'
159

1610
export const maxDuration = 60
1711

18-
const rateLimitMaxRequests = 10
19-
const ratelimitWindow = '1d'
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'
2018

2119
export async function POST(req: Request) {
22-
const limit = await ratelimit(req.headers.get('x-forwarded-for'), rateLimitMaxRequests, ratelimitWindow)
20+
const limit = await ratelimit(
21+
req.headers.get('x-forwarded-for'),
22+
rateLimitMaxRequests,
23+
ratelimitWindow,
24+
)
2325
if (limit) {
2426
return new Response('You have reached your request limit for the day.', {
2527
status: 429,
2628
headers: {
2729
'X-RateLimit-Limit': limit.amount.toString(),
2830
'X-RateLimit-Remaining': limit.remaining.toString(),
29-
'X-RateLimit-Reset': limit.reset.toString()
30-
}
31+
'X-RateLimit-Reset': limit.reset.toString(),
32+
},
3133
})
3234
}
3335

34-
const { messages, userID, template, model, config }: { messages: CoreMessage[], userID: string, template: Templates, model: LLMModel, config: LLMModelConfig } = await req.json()
36+
const {
37+
messages,
38+
userID,
39+
template,
40+
model,
41+
config,
42+
}: {
43+
messages: CoreMessage[]
44+
userID: string
45+
template: Templates
46+
model: LLMModel
47+
config: LLMModelConfig
48+
} = await req.json()
3549
console.log('userID', userID)
3650
// console.log('template', template)
3751
console.log('model', model)

app/api/chat/route.ts

+30-15
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,50 @@
1-
import {
2-
streamObject,
3-
LanguageModel,
4-
CoreMessage,
5-
} from 'ai'
6-
7-
import ratelimit, { Duration } from '@/lib/ratelimit'
8-
import { Templates, templatesToPrompt } from '@/lib/templates'
91
import { getModelClient, getDefaultMode } from '@/lib/models'
102
import { LLMModel, LLMModelConfig } from '@/lib/models'
11-
import { artifactSchema as schema } from '@/lib/schema'
123
import { toPrompt } from '@/lib/prompt'
4+
import ratelimit, { Duration } from '@/lib/ratelimit'
5+
import { artifactSchema as schema } from '@/lib/schema'
6+
import { Templates } from '@/lib/templates'
7+
import { streamObject, LanguageModel, CoreMessage } from 'ai'
138

149
export const maxDuration = 60
1510

16-
const rateLimitMaxRequests = process.env.RATE_LIMIT_MAX_REQUESTS ? parseInt(process.env.RATE_LIMIT_MAX_REQUESTS) : 10
17-
const ratelimitWindow = process.env.RATE_LIMIT_WINDOW ? process.env.RATE_LIMIT_WINDOW as Duration : '1d'
11+
const rateLimitMaxRequests = process.env.RATE_LIMIT_MAX_REQUESTS
12+
? parseInt(process.env.RATE_LIMIT_MAX_REQUESTS)
13+
: 10
14+
const ratelimitWindow = process.env.RATE_LIMIT_WINDOW
15+
? (process.env.RATE_LIMIT_WINDOW as Duration)
16+
: '1d'
1817

1918
export async function POST(req: Request) {
20-
const limit = await ratelimit(req.headers.get('x-forwarded-for'), rateLimitMaxRequests, ratelimitWindow)
19+
const limit = await ratelimit(
20+
req.headers.get('x-forwarded-for'),
21+
rateLimitMaxRequests,
22+
ratelimitWindow,
23+
)
2124
if (limit) {
2225
return new Response('You have reached your request limit for the day.', {
2326
status: 429,
2427
headers: {
2528
'X-RateLimit-Limit': limit.amount.toString(),
2629
'X-RateLimit-Remaining': limit.remaining.toString(),
27-
'X-RateLimit-Reset': limit.reset.toString()
28-
}
30+
'X-RateLimit-Reset': limit.reset.toString(),
31+
},
2932
})
3033
}
3134

32-
const { messages, userID, template, model, config }: { messages: CoreMessage[], userID: string, template: Templates, model: LLMModel, config: LLMModelConfig } = await req.json()
35+
const {
36+
messages,
37+
userID,
38+
template,
39+
model,
40+
config,
41+
}: {
42+
messages: CoreMessage[]
43+
userID: string
44+
template: Templates
45+
model: LLMModel
46+
config: LLMModelConfig
47+
} = await req.json()
3348
console.log('userID', userID)
3449
// console.log('template', template)
3550
console.log('model', model)

app/api/sandbox/route.ts

+44-28
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,18 @@
11
import { ArtifactSchema } from '@/lib/schema'
2-
import { Sandbox, CodeInterpreter, Execution, Result, ExecutionError } from "@e2b/code-interpreter";
3-
import { TemplateId } from '@/lib/templates';
2+
import { ExecutionResultInterpreter, ExecutionResultWeb } from '@/lib/types'
3+
import { Sandbox, CodeInterpreter } from '@e2b/code-interpreter'
44

55
const sandboxTimeout = 10 * 60 * 1000 // 10 minute in ms
66

77
export const maxDuration = 60
88

9-
export type ExecutionResult = {
10-
template: TemplateId
11-
stdout: string[]
12-
stderr: string[]
13-
runtimeError?: ExecutionError
14-
cellResults: Result[]
15-
url: string
16-
}
17-
189
export async function POST(req: Request) {
19-
const { artifact, userID, apiKey }: { artifact: ArtifactSchema, userID: string, apiKey: string } = await req.json()
10+
const {
11+
artifact,
12+
userID,
13+
apiKey,
14+
}: { artifact: ArtifactSchema; userID: string; apiKey?: string } =
15+
await req.json()
2016
console.log('artifact', artifact)
2117
console.log('userID', userID)
2218
console.log('apiKey', apiKey)
@@ -25,21 +21,33 @@ export async function POST(req: Request) {
2521

2622
// Create a interpreter or a sandbox
2723
if (artifact.template === 'code-interpreter-multilang') {
28-
sbx = await CodeInterpreter.create({ metadata: { template: artifact.template, userID: userID }, timeoutMs: sandboxTimeout, apiKey })
24+
sbx = await CodeInterpreter.create({
25+
metadata: { template: artifact.template, userID: userID },
26+
timeoutMs: sandboxTimeout,
27+
apiKey,
28+
})
2929
console.log('Created code interpreter', sbx.sandboxID)
3030
} else {
31-
sbx = await Sandbox.create(artifact.template, { metadata: { template: artifact.template, userID: userID }, timeoutMs: sandboxTimeout, apiKey })
31+
sbx = await Sandbox.create(artifact.template, {
32+
metadata: { template: artifact.template, userID: userID },
33+
timeoutMs: sandboxTimeout,
34+
apiKey,
35+
})
3236
console.log('Created sandbox', sbx.sandboxID)
3337
}
3438

3539
// Install packages
3640
if (artifact.has_additional_dependencies) {
3741
if (sbx instanceof CodeInterpreter) {
3842
await sbx.notebook.execCell(artifact.install_dependencies_command)
39-
console.log(`Installed dependencies: ${artifact.additional_dependencies.join(', ')} in code interpreter ${sbx.sandboxID}`)
43+
console.log(
44+
`Installed dependencies: ${artifact.additional_dependencies.join(', ')} in code interpreter ${sbx.sandboxID}`,
45+
)
4046
} else if (sbx instanceof Sandbox) {
4147
await sbx.commands.run(artifact.install_dependencies_command)
42-
console.log(`Installed dependencies: ${artifact.additional_dependencies.join(', ')} in sandbox ${sbx.sandboxID}`)
48+
console.log(
49+
`Installed dependencies: ${artifact.additional_dependencies.join(', ')} in sandbox ${sbx.sandboxID}`,
50+
)
4351
}
4452
}
4553

@@ -56,19 +64,27 @@ export async function POST(req: Request) {
5664

5765
// Execute code or return a URL to the running sandbox
5866
if (artifact.template === 'code-interpreter-multilang') {
59-
const result = await (sbx as CodeInterpreter).notebook.execCell(artifact.code || '')
67+
const result = await (sbx as CodeInterpreter).notebook.execCell(
68+
artifact.code || '',
69+
)
6070
await (sbx as CodeInterpreter).close()
61-
return new Response(JSON.stringify({
62-
template: artifact.template,
63-
stdout: result.logs.stdout,
64-
stderr: result.logs.stderr,
65-
runtimeError: result.error,
66-
cellResults: result.results,
67-
}))
71+
return new Response(
72+
JSON.stringify({
73+
sbxId: sbx?.sandboxID,
74+
template: artifact.template,
75+
stdout: result.logs.stdout,
76+
stderr: result.logs.stderr,
77+
runtimeError: result.error,
78+
cellResults: result.results,
79+
} as ExecutionResultInterpreter),
80+
)
6881
} else {
69-
return new Response(JSON.stringify({
70-
template: artifact.template,
71-
url: `https://${sbx?.getHost(artifact.port || 80)}`
72-
}))
82+
return new Response(
83+
JSON.stringify({
84+
sbxId: sbx?.sandboxID,
85+
template: artifact.template,
86+
url: `https://${sbx?.getHost(artifact.port || 80)}`,
87+
} as ExecutionResultWeb),
88+
)
7389
}
7490
}

app/globals.css

+29-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,37 @@
1-
@import './xterm.css';
2-
31
@tailwind base;
42
@tailwind components;
53
@tailwind utilities;
64

75
@layer base {
86
: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 {
935
--background: 240, 6%, 10%;
1036
--foreground: 0 0% 98%;
1137
--card: 240 10% 3.9%;
@@ -24,13 +50,12 @@
2450
--destructive-foreground: 0 0% 98%;
2551
--border: 270, 2%, 19%;
2652
--input: 240 3.7% 15.9%;
27-
--ring: 240 4.9% 83.9%;
53+
--ring: 0, 0%, 100%, 0.1;
2854
--chart-1: 220 70% 50%;
2955
--chart-2: 160 60% 45%;
3056
--chart-3: 30 80% 55%;
3157
--chart-4: 280 65% 60%;
3258
--chart-5: 340 75% 55%;
33-
--radius: 0.5rem;
3459
}
3560
}
3661

app/layout.tsx

+17-7
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,35 @@
1+
import './globals.css'
2+
import { PostHogProvider, ThemeProvider } from './providers'
3+
import { Toaster } from '@/components/ui/toaster'
14
import type { Metadata } from 'next'
25
import { Inter } from 'next/font/google'
3-
import { PostHogProvider } from './providers'
4-
import './globals.css'
56

67
const inter = Inter({ subsets: ['latin'] })
78

89
export const metadata: Metadata = {
9-
title: 'AI Artifacts by E2B',
10-
description: 'About Hackable open-source version of Anthropic\'s AI Artifacts chat',
10+
title: 'Artifacts by E2B',
11+
description:
12+
"About Hackable open-source version of Anthropic's AI Artifacts chat",
1113
}
1214

1315
export default function RootLayout({
1416
children,
1517
}: Readonly<{
16-
children: React.ReactNode;
18+
children: React.ReactNode
1719
}>) {
1820
return (
19-
<html lang="en">
21+
<html lang="en" suppressHydrationWarning>
2022
<PostHogProvider>
2123
<body className={inter.className}>
22-
{children}
24+
<ThemeProvider
25+
attribute="class"
26+
defaultTheme="dark"
27+
enableSystem
28+
disableTransitionOnChange
29+
>
30+
{children}
31+
</ThemeProvider>
32+
<Toaster />
2333
</body>
2434
</PostHogProvider>
2535
</html>

0 commit comments

Comments
 (0)