create-tsrouter-app 0.2.0 → 0.3.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dist/add-ons.js +53 -0
  2. package/dist/cli.js +51 -0
  3. package/dist/constants.js +2 -0
  4. package/dist/create-app.js +285 -0
  5. package/dist/index.js +2 -347
  6. package/dist/options.js +231 -0
  7. package/dist/types.js +1 -0
  8. package/package.json +4 -3
  9. package/src/add-ons.ts +141 -0
  10. package/src/cli.ts +74 -0
  11. package/src/constants.ts +2 -0
  12. package/src/create-app.ts +445 -0
  13. package/src/index.ts +2 -507
  14. package/src/options.ts +264 -0
  15. package/src/types.ts +24 -0
  16. package/templates/add-on/clerk/README.md +3 -0
  17. package/templates/add-on/clerk/assets/.env.local.append +2 -0
  18. package/templates/add-on/clerk/assets/src/routes/demo.clerk.tsx +20 -0
  19. package/templates/add-on/clerk/info.json +28 -0
  20. package/templates/add-on/clerk/package.json +5 -0
  21. package/templates/add-on/convex/README.md +4 -0
  22. package/templates/add-on/convex/assets/.cursorrules.append +93 -0
  23. package/templates/add-on/convex/assets/.env.local.append +3 -0
  24. package/templates/add-on/convex/assets/convex/products.ts +8 -0
  25. package/templates/add-on/convex/assets/convex/schema.ts +10 -0
  26. package/templates/add-on/convex/assets/src/routes/demo.convex.tsx +33 -0
  27. package/templates/add-on/convex/info.json +27 -0
  28. package/templates/add-on/convex/package.json +6 -0
  29. package/templates/add-on/form/assets/src/routes/demo.form.tsx +50 -0
  30. package/templates/add-on/form/info.json +12 -0
  31. package/templates/add-on/form/package.json +5 -0
  32. package/templates/add-on/netlify/README.md +11 -0
  33. package/templates/add-on/netlify/info.json +6 -0
  34. package/templates/add-on/react-query/assets/src/routes/demo.react-query.tsx +30 -0
  35. package/templates/add-on/react-query/info.json +30 -0
  36. package/templates/add-on/react-query/package.json +6 -0
  37. package/templates/add-on/sentry/assets/.cursorrules +22 -0
  38. package/templates/add-on/sentry/assets/.env.local.append +2 -0
  39. package/templates/add-on/sentry/assets/src/routes/demo.sentry.bad-server-func.tsx +29 -0
  40. package/templates/add-on/sentry/info.json +13 -0
  41. package/templates/add-on/sentry/package.json +5 -0
  42. package/templates/add-on/shadcn/README.md +7 -0
  43. package/templates/add-on/shadcn/info.json +10 -0
  44. package/templates/add-on/start/assets/app.config.ts +16 -0
  45. package/templates/add-on/start/assets/postcss.config.ts +5 -0
  46. package/templates/add-on/start/assets/src/api.ts +6 -0
  47. package/templates/add-on/start/assets/src/client.tsx +10 -0
  48. package/templates/add-on/start/assets/src/router.tsx.ejs +34 -0
  49. package/templates/add-on/start/assets/src/routes/api.demo-names.ts +11 -0
  50. package/templates/add-on/start/assets/src/routes/demo.start.api-request.tsx.ejs +33 -0
  51. package/templates/add-on/start/assets/src/routes/demo.start.server-funcs.tsx +49 -0
  52. package/templates/add-on/start/assets/src/ssr.tsx +12 -0
  53. package/templates/add-on/start/info.json +18 -0
  54. package/templates/add-on/start/package.json +14 -0
  55. package/templates/add-on/store/assets/src/lib/demo-store.ts +5 -0
  56. package/templates/add-on/store/assets/src/routes/demo.store.page1.tsx +30 -0
  57. package/templates/add-on/store/assets/src/routes/demo.store.page2.tsx +30 -0
  58. package/templates/add-on/store/info.json +16 -0
  59. package/templates/add-on/store/package.json +6 -0
  60. package/templates/base/README.md.ejs +9 -0
  61. package/templates/base/package.json +1 -0
  62. package/templates/base/{tsconfig.json → tsconfig.json.ejs} +5 -1
  63. package/templates/base/vite.config.js.ejs +8 -0
  64. package/templates/example/ai-chat/assets/.env.local.append +2 -0
  65. package/templates/example/ai-chat/assets/src/routes/example.ai-chat.tsx.ejs +81 -0
  66. package/templates/example/ai-chat/info.json +27 -0
  67. package/templates/example/ai-chat/package.json +1 -0
  68. package/templates/file-router/src/components/Header.tsx.ejs +27 -0
  69. package/templates/file-router/src/routes/__root.tsx.ejs +80 -0
  70. package/templates/file-router/src/routes/__root.tsx +0 -11
  71. /package/dist/{utils/getPackageManager.js → package-manager.js} +0 -0
  72. /package/src/{utils/getPackageManager.ts → package-manager.ts} +0 -0
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "React Query",
3
+ "description": "Integrate TanStack Query into your application.",
4
+ "phase": "add-on",
5
+ "link": "https://tanstack.com/query/latest",
6
+ "main": {
7
+ "imports": [
8
+ "import { QueryClient, QueryClientProvider } from '@tanstack/react-query'"
9
+ ],
10
+ "initialize": ["const queryClient = new QueryClient()"],
11
+ "providers": [
12
+ {
13
+ "open": "<QueryClientProvider client={queryClient}>",
14
+ "close": "</QueryClientProvider>"
15
+ }
16
+ ]
17
+ },
18
+ "layout": {
19
+ "imports": [
20
+ "import { ReactQueryDevtools } from '@tanstack/react-query-devtools'"
21
+ ],
22
+ "jsx": "<ReactQueryDevtools buttonPosition='bottom-right' />"
23
+ },
24
+ "routes": [
25
+ {
26
+ "url": "/demo/react-query",
27
+ "name": "React Query"
28
+ }
29
+ ]
30
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "dependencies": {
3
+ "@tanstack/react-query": "^5.66.5",
4
+ "@tanstack/react-query-devtools": "^5.66.5"
5
+ }
6
+ }
@@ -0,0 +1,22 @@
1
+ We use Sentry for watching for errors in our deployed application, as well as for instrumentation of our application.
2
+
3
+ ## Error collection
4
+
5
+ Error collection is automatic and configured in `src/router.tsx`.
6
+
7
+ ## Instrumentation
8
+
9
+ We want our server functions intstrumented. So if you see a function name like `createServerFn`, you can instrument it with Sentry. You'll need to import `Sentry`:
10
+
11
+ ```tsx
12
+ import * as Sentry from '@sentry/browser'
13
+ ```
14
+
15
+ And then wrap the implementation of the server function with `Sentry.startSpan`, liks so:
16
+
17
+ ```tsx
18
+ Sentry.startSpan({ name: 'Requesting all the pokemon' }, async () => {
19
+ // Some lengthy operation here
20
+ await fetch('https://api.pokemon.com/data/')
21
+ })
22
+ ```
@@ -0,0 +1,2 @@
1
+ # Your Sentry DSN (from your Sentry account)
2
+ VITE_SENTRY_DSN=
@@ -0,0 +1,29 @@
1
+ import * as fs from 'node:fs/promises'
2
+ import { createFileRoute } from '@tanstack/react-router'
3
+ import { createServerFn } from '@tanstack/start'
4
+
5
+ export const Route = createFileRoute('/demo/sentry/bad-server-func')({
6
+ component: RouteComponent,
7
+ })
8
+
9
+ const badServerFunc = createServerFn({
10
+ method: 'GET',
11
+ }).handler(async () => {
12
+ await fs.readFile('./doesnt-exist', 'utf-8')
13
+ return true
14
+ })
15
+
16
+ function RouteComponent() {
17
+ return (
18
+ <div className="p-4">
19
+ <button
20
+ onClick={async () => {
21
+ await badServerFunc()
22
+ }}
23
+ className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
24
+ >
25
+ I Don't Work, And That Should Fire an Error to Sentry
26
+ </button>
27
+ </div>
28
+ )
29
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "Sentry",
3
+ "phase": "setup",
4
+ "description": "Add Sentry for error monitoring and crash reporting (requires Start).",
5
+ "link": "https://sentry.com/",
6
+ "routes": [
7
+ {
8
+ "url": "/demo/sentry/bad-server-func",
9
+ "name": "Sentry"
10
+ }
11
+ ],
12
+ "dependsOn": ["start"]
13
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "dependencies": {
3
+ "@sentry/react": "^8.50.0"
4
+ }
5
+ }
@@ -0,0 +1,7 @@
1
+ ## Shadcn
2
+
3
+ Add components using the canary version of [Shadcn](https://ui.shadcn.com/).
4
+
5
+ ```bash
6
+ pnpx shadcn@canary add button
7
+ ```
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "Shadcn",
3
+ "description": "Add Shadcn UI to your application.",
4
+ "phase": "add-on",
5
+ "link": "https://ui.shadcn.com/",
6
+ "command": {
7
+ "command": "npx",
8
+ "args": ["shadcn@canary", "init", "-d", "-s"]
9
+ }
10
+ }
@@ -0,0 +1,16 @@
1
+ import { defineConfig } from '@tanstack/start/config'
2
+ import viteTsConfigPaths from 'vite-tsconfig-paths'
3
+
4
+ export default defineConfig({
5
+ tsr: {
6
+ appDirectory: 'src',
7
+ },
8
+ vite: {
9
+ plugins: [
10
+ // this is the plugin that enables path aliases
11
+ viteTsConfigPaths({
12
+ projects: ['./tsconfig.json'],
13
+ }),
14
+ ],
15
+ },
16
+ })
@@ -0,0 +1,5 @@
1
+ export default {
2
+ plugins: {
3
+ "@tailwindcss/postcss": {},
4
+ },
5
+ };
@@ -0,0 +1,6 @@
1
+ import {
2
+ createStartAPIHandler,
3
+ defaultAPIFileRouteHandler,
4
+ } from '@tanstack/start/api'
5
+
6
+ export default createStartAPIHandler(defaultAPIFileRouteHandler)
@@ -0,0 +1,10 @@
1
+ import { hydrateRoot } from 'react-dom/client'
2
+ import { StartClient } from '@tanstack/start'
3
+
4
+ import { createRouter } from './router'
5
+
6
+ const router = createRouter({
7
+ scrollRestoration: true,
8
+ })
9
+
10
+ hydrateRoot(document!, <StartClient router={router} />)
@@ -0,0 +1,34 @@
1
+ import { createRouter as createTanstackRouter } from '@tanstack/react-router'<% if (addOnEnabled.sentry) { %>
2
+ import * as Sentry from '@sentry/react'
3
+ <% } %>
4
+
5
+ // Import the generated route tree
6
+ import { routeTree } from './routeTree.gen'
7
+
8
+ import './styles.css'
9
+
10
+ // Create a new router instance
11
+ export const createRouter = () => {
12
+ const router = createTanstackRouter({ routeTree })
13
+ <% if (addOnEnabled.sentry) { %>
14
+ Sentry.init({
15
+ dsn: import.meta.env.VITE_SENTRY_DSN,
16
+ integrations: [Sentry.replayIntegration(), Sentry.tanstackRouterBrowserTracingIntegration(router)],
17
+
18
+ // Setting a sample rate is required for sending performance data.
19
+ // We recommend adjusting this value in production, or using tracesSampler
20
+ // for finer control.
21
+ tracesSampleRate: 1.0,
22
+ replaysSessionSampleRate: 1.0, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production.
23
+ replaysOnErrorSampleRate: 1.0,
24
+ });
25
+ <% } %>
26
+ return router;
27
+ }
28
+
29
+ // Register the router instance for type safety
30
+ declare module '@tanstack/react-router' {
31
+ interface Register {
32
+ router: ReturnType<typeof createRouter>
33
+ }
34
+ }
@@ -0,0 +1,11 @@
1
+ import { createAPIFileRoute } from '@tanstack/start/api'
2
+
3
+ export const APIRoute = createAPIFileRoute('/demo/start/api/names')({
4
+ GET: async ({ request }) => {
5
+ return new Response(JSON.stringify(['Alice', 'Bob', 'Charlie']), {
6
+ headers: {
7
+ 'Content-Type': 'application/json',
8
+ },
9
+ })
10
+ },
11
+ })
@@ -0,0 +1,33 @@
1
+ <% if (addOnEnabled['react-query']) { %>
2
+ import { useQuery } from '@tanstack/react-query'
3
+ <% } else { %>
4
+ import { useEffect, useState } from 'react'
5
+ <% } %>
6
+ import { createFileRoute } from '@tanstack/react-router'
7
+
8
+ function getNames() {
9
+ return fetch('/api/demo-names').then((res) => res.json())
10
+ }
11
+
12
+ export const Route = createFileRoute('/demo/start/api-request')({
13
+ component: Home,
14
+ })
15
+
16
+ function Home() {
17
+ <% if (addOnEnabled['react-query']) { %>
18
+ const { data: names = [] } = useQuery({
19
+ queryKey: ['names'],
20
+ queryFn: getNames,
21
+ })
22
+ <% } else { %>
23
+ const [names, setNames] = useState<Array<string>>([])
24
+ useEffect(() => {
25
+ getNames().then(setNames)
26
+ }, [])
27
+ <% } %>
28
+ return (
29
+ <div className="p-4">
30
+ <div>{names.join(', ')}</div>
31
+ </div>
32
+ )
33
+ }
@@ -0,0 +1,49 @@
1
+ import * as fs from 'fs'
2
+ import { createFileRoute, useRouter } from '@tanstack/react-router'
3
+ import { createServerFn } from '@tanstack/start'
4
+
5
+ const filePath = 'count.txt'
6
+
7
+ async function readCount() {
8
+ return parseInt(
9
+ await fs.promises.readFile(filePath, 'utf-8').catch(() => '0'),
10
+ )
11
+ }
12
+
13
+ const getCount = createServerFn({
14
+ method: 'GET',
15
+ }).handler(() => {
16
+ return readCount()
17
+ })
18
+
19
+ const updateCount = createServerFn({ method: 'POST' })
20
+ .validator((d: number) => d)
21
+ .handler(async ({ data }) => {
22
+ const count = await readCount()
23
+ await fs.promises.writeFile(filePath, `${count + data}`)
24
+ })
25
+
26
+ export const Route = createFileRoute('/demo/start/server-funcs')({
27
+ component: Home,
28
+ loader: async () => await getCount(),
29
+ })
30
+
31
+ function Home() {
32
+ const router = useRouter()
33
+ const state = Route.useLoaderData()
34
+
35
+ return (
36
+ <div className="p-4">
37
+ <button
38
+ onClick={() => {
39
+ updateCount({ data: 1 }).then(() => {
40
+ router.invalidate()
41
+ })
42
+ }}
43
+ className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
44
+ >
45
+ Add 1 to {state}?
46
+ </button>
47
+ </div>
48
+ )
49
+ }
@@ -0,0 +1,12 @@
1
+ import {
2
+ createStartHandler,
3
+ defaultStreamHandler,
4
+ } from '@tanstack/start/server'
5
+ import { getRouterManifest } from '@tanstack/start/router-manifest'
6
+
7
+ import { createRouter } from './router'
8
+
9
+ export default createStartHandler({
10
+ createRouter,
11
+ getRouterManifest,
12
+ })(defaultStreamHandler)
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "Start",
3
+ "phase": "setup",
4
+ "description": "Add TanStack Start for SSR, API endpoints, and more.",
5
+ "link": "https://tanstack.com/start/latest",
6
+ "warning": "TanStack Start is not yet at 1.0 and may change significantly or not be compatible with other add-ons.",
7
+ "routes": [
8
+ {
9
+ "url": "/demo/start/server-funcs",
10
+ "name": "Start - Server Functions"
11
+ },
12
+ {
13
+ "url": "/demo/start/api-request",
14
+ "name": "Start - API Request"
15
+ }
16
+ ],
17
+ "shadcnComponents": ["button", "input"]
18
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "scripts": {
3
+ "dev": "vinxi dev",
4
+ "build": "vinxi build",
5
+ "start": "vinxi start"
6
+ },
7
+ "dependencies": {
8
+ "@tailwindcss/postcss": "^4.0.7",
9
+ "@tanstack/start": "^1.106.0",
10
+ "postcss": "^8.5.2",
11
+ "vinxi": "^0.5.3",
12
+ "vite-tsconfig-paths": "^5.1.4"
13
+ }
14
+ }
@@ -0,0 +1,5 @@
1
+ import { Store } from '@tanstack/store'
2
+
3
+ export const store = new Store({
4
+ count: 0,
5
+ })
@@ -0,0 +1,30 @@
1
+ import { createFileRoute } from '@tanstack/react-router'
2
+ import { useStore } from '@tanstack/react-store'
3
+
4
+ import { store } from '../lib/demo-store'
5
+
6
+ export const Route = createFileRoute('/demo/store/page1')({
7
+ component: App,
8
+ })
9
+
10
+ function App() {
11
+ const count = useStore(store, (state) => state.count)
12
+
13
+ return (
14
+ <div className="p-4 flex flex-col gap-2">
15
+ <p className="text-2xl">Global Count: {count}</p>
16
+ <button
17
+ onClick={() => {
18
+ store.setState((state) => ({
19
+ count: state.count + 1,
20
+ }))
21
+ }}
22
+ className="mt-4 inline-flex items-center px-6 py-3 border border-transparent shadow-sm text-base font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
23
+ >
24
+ Increment count
25
+ </button>
26
+ </div>
27
+ )
28
+ }
29
+
30
+ export default App
@@ -0,0 +1,30 @@
1
+ import { createFileRoute } from '@tanstack/react-router'
2
+ import { useStore } from '@tanstack/react-store'
3
+
4
+ import { store } from '../lib/demo-store'
5
+
6
+ export const Route = createFileRoute('/demo/store/page2')({
7
+ component: App,
8
+ })
9
+
10
+ function App() {
11
+ const count = useStore(store, (state) => state.count)
12
+
13
+ return (
14
+ <div className="p-4 flex flex-col gap-2">
15
+ <p className="text-2xl">Global Count: {count}</p>
16
+ <button
17
+ onClick={() => {
18
+ store.setState((state) => ({
19
+ count: state.count + 10,
20
+ }))
21
+ }}
22
+ className="mt-4 inline-flex items-center px-6 py-3 border border-transparent shadow-sm text-base font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
23
+ >
24
+ Increment count by 10
25
+ </button>
26
+ </div>
27
+ )
28
+ }
29
+
30
+ export default App
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "Store",
3
+ "description": "Add TanStack Store to your application.",
4
+ "phase": "add-on",
5
+ "link": "https://tanstack.com/store/latest",
6
+ "routes": [
7
+ {
8
+ "url": "/demo/store/page1",
9
+ "name": "Store Pg 1"
10
+ },
11
+ {
12
+ "url": "/demo/store/page2",
13
+ "name": "Store Pg 2"
14
+ }
15
+ ]
16
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "dependencies": {
3
+ "@tanstack/store": "^0.7.0",
4
+ "@tanstack/react-store": "^0.7.0"
5
+ }
6
+ }
@@ -31,6 +31,11 @@ This project uses [Tailwind CSS](https://tailwindcss.com/) for styling.
31
31
  <% } else { %>
32
32
  This project uses CSS for styling.
33
33
  <% } %>
34
+
35
+ <% for(const addon of addOns.filter(addon => addon.readme)) { %>
36
+ <%- addon.readme %>
37
+ <% } %>
38
+
34
39
  ## Routing
35
40
  <% if (fileRouter) { %>This project uses [TanStack Router](https://tanstack.com/router). The initial setup is a file based router. Which means that the routes are managed as fiels in `src/routes`.<% } else { %>This project uses [TanStack Router](https://tanstack.com/router). The initial setup is a code based router. Which means that the routes are defined in code (in the `./src/main.<%= jsx %>` file). If you like you can also use a file based routing setup by following the [File Based Routing](https://tanstack.com/router/latest/docs/framework/react/guide/file-based-routing) guide.<% } %>
36
41
 
@@ -525,6 +530,10 @@ Once we've created the derived store we can use it in the `App` component just l
525
530
 
526
531
  You can find out everything you need to know on how to use TanStack Store in the [TanStack Store documentation](https://tanstack.com/store/latest).
527
532
 
533
+ # Demo files
534
+
535
+ Files prefixed with `demo` can be safely deleted. They are there to provide a starting point for you to play around with the features you've installed.
536
+
528
537
  # Learn More
529
538
 
530
539
  You can learn more about all of the offerings from TanStack in the [TanStack documentation](https://tanstack.com).
@@ -15,6 +15,7 @@
15
15
  "react-dom": "^19.0.0"
16
16
  },
17
17
  "devDependencies": {
18
+ "@testing-library/dom": "^10.4.0",
18
19
  "@testing-library/react": "^16.2.0",
19
20
  "@types/react": "^19.0.8",
20
21
  "@types/react-dom": "^19.0.3",
@@ -19,6 +19,10 @@
19
19
  "noUnusedLocals": true,
20
20
  "noUnusedParameters": true,
21
21
  "noFallthroughCasesInSwitch": true,
22
- "noUncheckedSideEffectImports": true
22
+ "noUncheckedSideEffectImports": true<% if (addOnEnabled.shadcn) { %>,
23
+ "baseUrl": ".",
24
+ "paths": {
25
+ "@/*": ["./src/*"],
26
+ }<% } %>
23
27
  }
24
28
  }
@@ -3,6 +3,8 @@ import viteReact from "@vitejs/plugin-react";<% if (tailwind) { %>
3
3
  import tailwindcss from "@tailwindcss/vite";
4
4
  <% } %><%if (fileRouter) { %>
5
5
  import { TanStackRouterVite } from "@tanstack/router-plugin/vite";
6
+ <% } %><% if (addOnEnabled.shadcn) { %>
7
+ import { resolve } from "path";
6
8
  <% } %>
7
9
 
8
10
  // https://vitejs.dev/config/
@@ -12,4 +14,10 @@ export default defineConfig({
12
14
  globals: true,
13
15
  environment: "jsdom",
14
16
  },
17
+ <% if (addOnEnabled.shadcn) { %> resolve: {
18
+ alias: {
19
+ '@': resolve(__dirname, './src'),
20
+ },
21
+ },
22
+ <% } %>
15
23
  });
@@ -0,0 +1,2 @@
1
+ # Add your OPENAI API key
2
+ OPENAI_API_KEY=
@@ -0,0 +1,81 @@
1
+ import { useState } from 'react'
2
+ import { createFileRoute } from '@tanstack/react-router'
3
+ import { createServerFn } from '@tanstack/start'
4
+
5
+ import type { ChangeEvent, FormEvent } from 'react'
6
+
7
+ type Message = {
8
+ id: string
9
+ role: 'user' | 'assistant'
10
+ content: string
11
+ }
12
+
13
+ export const Route = createFileRoute('/example/ai-chat')({
14
+ component: AIChat,
15
+ })
16
+
17
+ const chat = createServerFn({ method: 'POST' })
18
+ .validator((data: unknown) => {
19
+ return data as Array<Message>
20
+ })
21
+ .handler(({ data }) => {
22
+ return fetch('https://api.openai.com/v1/chat/completions', {
23
+ method: 'POST',
24
+ headers: {
25
+ Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
26
+ 'Content-Type': 'application/json',
27
+ },
28
+ body: JSON.stringify({
29
+ model: 'gpt-3.5-turbo',
30
+ system: `<%= variables.ai_chat_system %>`,
31
+ messages: data,
32
+ }),
33
+ }).then((response) => {
34
+ return response.json()
35
+ })
36
+ })
37
+
38
+ function AIChat() {
39
+ const [input, setInput] = useState('')
40
+ const [messages, setMessages] = useState<Array<Message>>([])
41
+
42
+ const handleSubmit = async (e: FormEvent) => {
43
+ e.preventDefault()
44
+ const newMessage: Message = {
45
+ id: Date.now().toString(),
46
+ role: 'user',
47
+ content: input,
48
+ }
49
+ const newMessages = [...messages, newMessage]
50
+ setMessages(newMessages)
51
+ setInput('')
52
+
53
+ const out = await chat({ data: newMessages })
54
+ setMessages([...newMessages, out.choices[0].message])
55
+ }
56
+
57
+ const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
58
+ setInput(e.target.value)
59
+ }
60
+
61
+ return (
62
+ <div className="flex flex-col py-10 px-10 text-2xl">
63
+ <h1><%= variables.ai_chat_title %></h1>
64
+ {messages.map((m) => (
65
+ <div key={m.id} className="whitespace-pre-wrap">
66
+ {m.role === 'user' ? 'User: ' : 'AI: '}
67
+ {m.content}
68
+ </div>
69
+ ))}
70
+
71
+ <form onSubmit={handleSubmit} className="fixed bottom-0 mb-10 w-full">
72
+ <input
73
+ className="w-3/4 text-3xl px-4 py-2 border rounded-md"
74
+ value={input}
75
+ placeholder="Ask me a question..."
76
+ onChange={handleInputChange}
77
+ />
78
+ </form>
79
+ </div>
80
+ )
81
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "AI Chat",
3
+ "description": "An example OpenAI chat integration.",
4
+ "phase": "example",
5
+ "link": "https://sdk.vercel.ai/docs/introduction",
6
+ "routes": [
7
+ {
8
+ "url": "/example/ai-chat",
9
+ "name": "AI Chat"
10
+ }
11
+ ],
12
+ "dependsOn": ["start", "shadcn"],
13
+ "variables": [
14
+ {
15
+ "name": "ai_chat_title",
16
+ "default": "Your Amazing AI Chat",
17
+ "description": "The title of the AI chat.",
18
+ "type": "string"
19
+ },
20
+ {
21
+ "name": "ai_chat_system",
22
+ "default": "You are a helpful assistant.",
23
+ "description": "The system prompt of the AI chat.",
24
+ "type": "string"
25
+ }
26
+ ]
27
+ }
@@ -0,0 +1 @@
1
+ {}