create-better-t-stack 2.30.0 → 2.32.0

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.
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "2.30.0",
3
+ "version": "2.32.0",
4
4
  "description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "Aman Varshney",
8
8
  "bin": {
9
- "create-better-t-stack": "dist/index.js"
9
+ "create-better-t-stack": "dist/cli.js"
10
10
  },
11
11
  "files": [
12
12
  "templates",
@@ -49,9 +49,16 @@
49
49
  "dev": "tsdown --watch",
50
50
  "check-types": "tsc --noEmit",
51
51
  "check": "biome check --write .",
52
- "test": "vitest run",
52
+ "test": "bun run build && vitest run",
53
+ "test:with-build": "bun run build && WITH_BUILD=1 vitest run",
53
54
  "prepublishOnly": "npm run build"
54
55
  },
56
+ "exports": {
57
+ ".": {
58
+ "types": "./dist/index.d.ts",
59
+ "import": "./dist/index.js"
60
+ }
61
+ },
55
62
  "dependencies": {
56
63
  "@clack/prompts": "^0.11.0",
57
64
  "consola": "^3.4.2",
@@ -64,12 +71,13 @@
64
71
  "picocolors": "^1.1.1",
65
72
  "trpc-cli": "^0.10.2",
66
73
  "ts-morph": "^26.0.0",
67
- "zod": "^4.0.15"
74
+ "zod": "^4.0.17"
68
75
  },
69
76
  "devDependencies": {
70
77
  "@types/fs-extra": "^11.0.4",
71
- "@types/node": "^24.2.0",
72
- "tsdown": "^0.13.3",
73
- "typescript": "^5.9.2"
78
+ "@types/node": "^24.2.1",
79
+ "tsdown": "^0.14.1",
80
+ "typescript": "^5.9.2",
81
+ "vitest": "^3.2.4"
74
82
  }
75
83
  }
@@ -21,6 +21,7 @@
21
21
  "!bts.jsonc",
22
22
  "!**/.expo",
23
23
  "!**/.wrangler",
24
+ "!**/wrangler.jsonc",
24
25
  "!**/.source"
25
26
  ]
26
27
  },
@@ -16,6 +16,7 @@
16
16
  "!bts.jsonc",
17
17
  "!**/.expo",
18
18
  "!**/.wrangler",
19
+ "!**/wrangler.jsonc",
19
20
  "!**/.source"
20
21
  ]
21
22
  }
@@ -14,7 +14,7 @@ import { createContext } from "./lib/context";
14
14
  import cors from "cors";
15
15
  import express from "express";
16
16
  {{#if (includes examples "ai")}}
17
- import { streamText } from "ai";
17
+ import { streamText, type UIMessage, convertToModelMessages } from "ai";
18
18
  import { google } from "@ai-sdk/google";
19
19
  {{/if}}
20
20
  {{#if auth}}
@@ -44,16 +44,16 @@ app.use(
44
44
  "/trpc",
45
45
  createExpressMiddleware({
46
46
  router: appRouter,
47
- createContext
47
+ createContext,
48
48
  })
49
49
  );
50
50
  {{/if}}
51
51
 
52
52
  {{#if (eq api "orpc")}}
53
53
  const handler = new RPCHandler(appRouter);
54
- app.use('/rpc{*path}', async (req, res, next) => {
54
+ app.use("/rpc{*path}", async (req, res, next) => {
55
55
  const { matched } = await handler.handle(req, res, {
56
- prefix: '/rpc',
56
+ prefix: "/rpc",
57
57
  {{#if auth}}
58
58
  context: await createContext({ req }),
59
59
  {{else}}
@@ -65,16 +65,16 @@ app.use('/rpc{*path}', async (req, res, next) => {
65
65
  });
66
66
  {{/if}}
67
67
 
68
- app.use(express.json())
68
+ app.use(express.json());
69
69
 
70
70
  {{#if (includes examples "ai")}}
71
71
  app.post("/ai", async (req, res) => {
72
- const { messages = [] } = req.body || {};
72
+ const { messages = [] } = (req.body || {}) as { messages: UIMessage[] };
73
73
  const result = streamText({
74
74
  model: google("gemini-1.5-flash"),
75
- messages,
75
+ messages: convertToModelMessages(messages),
76
76
  });
77
- result.pipeDataStreamToResponse(res);
77
+ result.pipeUIMessageStreamToResponse(res);
78
78
  });
79
79
  {{/if}}
80
80
 
@@ -85,4 +85,4 @@ app.get("/", (_req, res) => {
85
85
  const port = process.env.PORT || 3000;
86
86
  app.listen(port, () => {
87
87
  console.log(`Server is running on port ${port}`);
88
- });
88
+ });
@@ -19,8 +19,7 @@ import { createContext } from "./lib/context";
19
19
  {{/if}}
20
20
 
21
21
  {{#if (includes examples "ai")}}
22
- import type { FastifyRequest, FastifyReply } from "fastify";
23
- import { streamText, type Message } from "ai";
22
+ import { streamText, type UIMessage, convertToModelMessages } from "ai";
24
23
  import { google } from "@ai-sdk/google";
25
24
  {{/if}}
26
25
 
@@ -99,7 +98,7 @@ fastify.route({
99
98
  response.headers.forEach((value, key) => reply.header(key, value));
100
99
  reply.send(response.body ? await response.text() : null);
101
100
  } catch (error) {
102
- fastify.log.error("Authentication Error:", error);
101
+ fastify.log.error({ err: error }, "Authentication Error:");
103
102
  reply.status(500).send({
104
103
  error: "Internal authentication error",
105
104
  code: "AUTH_FAILURE"
@@ -125,26 +124,24 @@ fastify.register(fastifyTRPCPlugin, {
125
124
  {{#if (includes examples "ai")}}
126
125
  interface AiRequestBody {
127
126
  id?: string;
128
- messages: Message[];
127
+ messages: UIMessage[];
129
128
  }
130
129
 
131
130
  fastify.post('/ai', async function (request, reply) {
131
+ // there are some issues with the ai sdk and fastify, docs: https://ai-sdk.dev/cookbook/api-servers/fastify
132
132
  const { messages } = request.body as AiRequestBody;
133
133
  const result = streamText({
134
134
  model: google('gemini-1.5-flash'),
135
- messages,
135
+ messages: convertToModelMessages(messages),
136
136
  });
137
137
 
138
- reply.header('X-Vercel-AI-Data-Stream', 'v1');
139
- reply.header('Content-Type', 'text/plain; charset=utf-8');
140
-
141
- return reply.send(result.toDataStream());
138
+ return result.pipeUIMessageStreamToResponse(reply.raw);
142
139
  });
143
140
  {{/if}}
144
141
 
145
142
  fastify.get('/', async () => {
146
- return 'OK'
147
- })
143
+ return 'OK';
144
+ });
148
145
 
149
146
  fastify.listen({ port: 3000 }, (err) => {
150
147
  if (err) {
@@ -152,4 +149,4 @@ fastify.listen({ port: 3000 }, (err) => {
152
149
  process.exit(1);
153
150
  }
154
151
  console.log("Server running on port 3000");
155
- });
152
+ });
@@ -21,32 +21,33 @@ import { Hono } from "hono";
21
21
  import { cors } from "hono/cors";
22
22
  import { logger } from "hono/logger";
23
23
  {{#if (and (includes examples "ai") (or (eq runtime "bun") (eq runtime "node")))}}
24
- import { streamText } from "ai";
24
+ import { streamText, convertToModelMessages } from "ai";
25
25
  import { google } from "@ai-sdk/google";
26
- import { stream } from "hono/streaming";
27
26
  {{/if}}
28
27
  {{#if (and (includes examples "ai") (eq runtime "workers"))}}
29
- import { streamText } from "ai";
30
- import { stream } from "hono/streaming";
28
+ import { streamText, convertToModelMessages } from "ai";
31
29
  import { createGoogleGenerativeAI } from "@ai-sdk/google";
32
30
  {{/if}}
33
31
 
34
32
  const app = new Hono();
35
33
 
36
34
  app.use(logger());
37
- app.use("/*", cors({
38
- {{#if (or (eq runtime "bun") (eq runtime "node"))}}
39
- origin: process.env.CORS_ORIGIN || "",
40
- {{/if}}
41
- {{#if (eq runtime "workers")}}
42
- origin: env.CORS_ORIGIN || "",
43
- {{/if}}
44
- allowMethods: ["GET", "POST", "OPTIONS"],
45
- {{#if auth}}
46
- allowHeaders: ["Content-Type", "Authorization"],
47
- credentials: true,
48
- {{/if}}
49
- }));
35
+ app.use(
36
+ "/*",
37
+ cors({
38
+ {{#if (or (eq runtime "bun") (eq runtime "node"))}}
39
+ origin: process.env.CORS_ORIGIN || "",
40
+ {{/if}}
41
+ {{#if (eq runtime "workers")}}
42
+ origin: env.CORS_ORIGIN || "",
43
+ {{/if}}
44
+ allowMethods: ["GET", "POST", "OPTIONS"],
45
+ {{#if auth}}
46
+ allowHeaders: ["Content-Type", "Authorization"],
47
+ credentials: true,
48
+ {{/if}}
49
+ })
50
+ );
50
51
 
51
52
  {{#if auth}}
52
53
  app.on(["POST", "GET"], "/api/auth/**", (c) => auth.handler(c.req.raw));
@@ -69,44 +70,43 @@ app.use("/rpc/*", async (c, next) => {
69
70
  {{/if}}
70
71
 
71
72
  {{#if (eq api "trpc")}}
72
- app.use("/trpc/*", trpcServer({
73
- router: appRouter,
74
- createContext: (_opts, context) => {
75
- return createContext({ context });
76
- },
77
- }));
73
+ app.use(
74
+ "/trpc/*",
75
+ trpcServer({
76
+ router: appRouter,
77
+ createContext: (_opts, context) => {
78
+ return createContext({ context });
79
+ },
80
+ })
81
+ );
78
82
  {{/if}}
79
83
 
80
84
  {{#if (and (includes examples "ai") (or (eq runtime "bun") (eq runtime "node")))}}
81
85
  app.post("/ai", async (c) => {
82
86
  const body = await c.req.json();
83
- const messages = body.messages || [];
87
+ const uiMessages = body.messages || [];
84
88
  const result = streamText({
85
89
  model: google("gemini-1.5-flash"),
86
- messages,
90
+ messages: convertToModelMessages(uiMessages),
87
91
  });
88
92
 
89
- c.header("X-Vercel-AI-Data-Stream", "v1");
90
- c.header("Content-Type", "text/plain; charset=utf-8");
91
- return stream(c, (stream) => stream.pipe(result.toDataStream()));
93
+ return result.toUIMessageStreamResponse();
92
94
  });
93
95
  {{/if}}
94
96
 
95
97
  {{#if (and (includes examples "ai") (eq runtime "workers"))}}
96
98
  app.post("/ai", async (c) => {
97
99
  const body = await c.req.json();
98
- const messages = body.messages || [];
100
+ const uiMessages = body.messages || [];
99
101
  const google = createGoogleGenerativeAI({
100
102
  apiKey: env.GOOGLE_GENERATIVE_AI_API_KEY,
101
103
  });
102
104
  const result = streamText({
103
105
  model: google("gemini-1.5-flash"),
104
- messages,
106
+ messages: convertToModelMessages(uiMessages),
105
107
  });
106
108
 
107
- c.header("X-Vercel-AI-Data-Stream", "v1");
108
- c.header("Content-Type", "text/plain; charset=utf-8");
109
- return stream(c, (stream) => stream.pipe(result.toDataStream()));
109
+ return result.toUIMessageStreamResponse();
110
110
  });
111
111
  {{/if}}
112
112
 
@@ -117,17 +117,20 @@ app.get("/", (c) => {
117
117
  {{#if (eq runtime "node")}}
118
118
  import { serve } from "@hono/node-server";
119
119
 
120
- serve({
121
- fetch: app.fetch,
122
- port: 3000,
123
- }, (info) => {
124
- console.log(`Server is running on http://localhost:${info.port}`);
125
- });
120
+ serve(
121
+ {
122
+ fetch: app.fetch,
123
+ port: 3000,
124
+ },
125
+ (info) => {
126
+ console.log(`Server is running on http://localhost:${info.port}`);
127
+ }
128
+ );
126
129
  {{else}}
127
- {{#if (eq runtime "bun")}}
130
+ {{#if (eq runtime "bun")}}
128
131
  export default app;
129
- {{/if}}
130
- {{#if (eq runtime "workers")}}
132
+ {{/if}}
133
+ {{#if (eq runtime "workers")}}
131
134
  export default app;
132
- {{/if}}
133
135
  {{/if}}
136
+ {{/if}}
@@ -1,4 +1,4 @@
1
- import { useRef, useEffect } from "react";
1
+ import { useRef, useEffect, useState } from "react";
2
2
  import {
3
3
  View,
4
4
  Text,
@@ -9,11 +9,11 @@ import {
9
9
  Platform,
10
10
  } from "react-native";
11
11
  import { useChat } from "@ai-sdk/react";
12
+ import { DefaultChatTransport } from "ai";
12
13
  import { fetch as expoFetch } from "expo/fetch";
13
14
  import { Ionicons } from "@expo/vector-icons";
14
15
  import { Container } from "@/components/container";
15
16
 
16
- // Utility function to generate API URLs
17
17
  const generateAPIUrl = (relativePath: string) => {
18
18
  const serverUrl = process.env.EXPO_PUBLIC_SERVER_URL;
19
19
  if (!serverUrl) {
@@ -25,11 +25,13 @@ const generateAPIUrl = (relativePath: string) => {
25
25
  };
26
26
 
27
27
  export default function AIScreen() {
28
- const { messages, input, handleInputChange, handleSubmit, error } = useChat({
29
- fetch: expoFetch as unknown as typeof globalThis.fetch,
30
- api: generateAPIUrl('/ai'),
28
+ const [input, setInput] = useState("");
29
+ const { messages, error, sendMessage } = useChat({
30
+ transport: new DefaultChatTransport({
31
+ fetch: expoFetch as unknown as typeof globalThis.fetch,
32
+ api: generateAPIUrl('/ai'),
33
+ }),
31
34
  onError: error => console.error(error, 'AI Chat Error'),
32
- maxSteps: 5,
33
35
  });
34
36
 
35
37
  const scrollViewRef = useRef<ScrollView>(null);
@@ -39,8 +41,10 @@ export default function AIScreen() {
39
41
  }, [messages]);
40
42
 
41
43
  const onSubmit = () => {
42
- if (input.trim()) {
43
- handleSubmit();
44
+ const value = input.trim();
45
+ if (value) {
46
+ sendMessage({ text: value });
47
+ setInput("");
44
48
  }
45
49
  };
46
50
 
@@ -100,9 +104,28 @@ export default function AIScreen() {
100
104
  <Text className="text-sm font-semibold mb-1 text-foreground">
101
105
  {message.role === "user" ? "You" : "AI Assistant"}
102
106
  </Text>
103
- <Text className="text-foreground leading-relaxed">
104
- {message.content}
105
- </Text>
107
+ <View className="space-y-1">
108
+ {message.parts.map((part, i) => {
109
+ if (part.type === 'text') {
110
+ return (
111
+ <Text
112
+ key={`${message.id}-${i}`}
113
+ className="text-foreground leading-relaxed"
114
+ >
115
+ {part.text}
116
+ </Text>
117
+ );
118
+ }
119
+ return (
120
+ <Text
121
+ key={`${message.id}-${i}`}
122
+ className="text-foreground leading-relaxed"
123
+ >
124
+ {JSON.stringify(part)}
125
+ </Text>
126
+ );
127
+ })}
128
+ </View>
106
129
  </View>
107
130
  ))}
108
131
  </View>
@@ -113,21 +136,13 @@ export default function AIScreen() {
113
136
  <View className="flex-row items-end space-x-2">
114
137
  <TextInput
115
138
  value={input}
116
- onChange={(e) =>
117
- handleInputChange({
118
- ...e,
119
- target: {
120
- ...e.target,
121
- value: e.nativeEvent.text,
122
- },
123
- } as unknown as React.ChangeEvent<HTMLInputElement>)
124
- }
139
+ onChangeText={setInput}
125
140
  placeholder="Type your message..."
126
141
  placeholderTextColor="#6b7280"
127
142
  className="flex-1 border border-border rounded-md px-3 py-2 text-foreground bg-background min-h-[40px] max-h-[120px]"
128
143
  onSubmitEditing={(e) => {
129
- handleSubmit(e);
130
144
  e.preventDefault();
145
+ onSubmit();
131
146
  }}
132
147
  autoFocus={true}
133
148
  />
@@ -1,4 +1,4 @@
1
- import { useRef, useEffect } from "react";
1
+ import React, { useRef, useEffect, useState } from "react";
2
2
  import {
3
3
  View,
4
4
  Text,
@@ -9,6 +9,7 @@ import {
9
9
  Platform,
10
10
  } from "react-native";
11
11
  import { useChat } from "@ai-sdk/react";
12
+ import { DefaultChatTransport } from "ai";
12
13
  import { fetch as expoFetch } from "expo/fetch";
13
14
  import { Ionicons } from "@expo/vector-icons";
14
15
  import { StyleSheet, useUnistyles } from "react-native-unistyles";
@@ -18,21 +19,22 @@ const generateAPIUrl = (relativePath: string) => {
18
19
  const serverUrl = process.env.EXPO_PUBLIC_SERVER_URL;
19
20
  if (!serverUrl) {
20
21
  throw new Error(
21
- "EXPO_PUBLIC_SERVER_URL environment variable is not defined",
22
+ "EXPO_PUBLIC_SERVER_URL environment variable is not defined"
22
23
  );
23
24
  }
24
-
25
25
  const path = relativePath.startsWith("/") ? relativePath : `/${relativePath}`;
26
26
  return serverUrl.concat(path);
27
27
  };
28
28
 
29
29
  export default function AIScreen() {
30
30
  const { theme } = useUnistyles();
31
- const { messages, input, handleInputChange, handleSubmit, error } = useChat({
32
- fetch: expoFetch as unknown as typeof globalThis.fetch,
33
- api: generateAPIUrl("/ai"),
31
+ const [input, setInput] = useState("");
32
+ const { messages, error, sendMessage } = useChat({
33
+ transport: new DefaultChatTransport({
34
+ fetch: expoFetch as unknown as typeof globalThis.fetch,
35
+ api: generateAPIUrl("/ai"),
36
+ }),
34
37
  onError: (error) => console.error(error, "AI Chat Error"),
35
- maxSteps: 5,
36
38
  });
37
39
 
38
40
  const scrollViewRef = useRef<ScrollView>(null);
@@ -42,8 +44,10 @@ export default function AIScreen() {
42
44
  }, [messages]);
43
45
 
44
46
  const onSubmit = () => {
45
- if (input.trim()) {
46
- handleSubmit();
47
+ const value = input.trim();
48
+ if (value) {
49
+ sendMessage({ text: value });
50
+ setInput("");
47
51
  }
48
52
  };
49
53
 
@@ -100,7 +104,28 @@ export default function AIScreen() {
100
104
  <Text style={styles.messageRole}>
101
105
  {message.role === "user" ? "You" : "AI Assistant"}
102
106
  </Text>
103
- <Text style={styles.messageContent}>{message.content}</Text>
107
+ <View style={styles.messageContentWrapper}>
108
+ {message.parts.map((part, i) => {
109
+ if (part.type === "text") {
110
+ return (
111
+ <Text
112
+ key={`${message.id}-${i}`}
113
+ style={styles.messageContent}
114
+ >
115
+ {part.text}
116
+ </Text>
117
+ );
118
+ }
119
+ return (
120
+ <Text
121
+ key={`${message.id}-${i}`}
122
+ style={styles.messageContent}
123
+ >
124
+ {JSON.stringify(part)}
125
+ </Text>
126
+ );
127
+ })}
128
+ </View>
104
129
  </View>
105
130
  ))}
106
131
  </View>
@@ -111,21 +136,13 @@ export default function AIScreen() {
111
136
  <View style={styles.inputContainer}>
112
137
  <TextInput
113
138
  value={input}
114
- onChange={(e) =>
115
- handleInputChange({
116
- ...e,
117
- target: {
118
- ...e.target,
119
- value: e.nativeEvent.text,
120
- },
121
- } as unknown as React.ChangeEvent<HTMLInputElement>)
122
- }
139
+ onChangeText={setInput}
123
140
  placeholder="Type your message..."
124
141
  placeholderTextColor={theme.colors.border}
125
142
  style={styles.textInput}
126
143
  onSubmitEditing={(e) => {
127
- handleSubmit(e);
128
144
  e.preventDefault();
145
+ onSubmit();
129
146
  }}
130
147
  autoFocus={true}
131
148
  />
@@ -141,7 +158,9 @@ export default function AIScreen() {
141
158
  name="send"
142
159
  size={20}
143
160
  color={
144
- input.trim() ? theme.colors.background : theme.colors.border
161
+ input.trim()
162
+ ? theme.colors.background
163
+ : theme.colors.border
145
164
  }
146
165
  />
147
166
  </TouchableOpacity>
@@ -230,6 +249,9 @@ const styles = StyleSheet.create((theme) => ({
230
249
  marginBottom: theme.spacing.sm,
231
250
  color: theme.colors.typography,
232
251
  },
252
+ messageContentWrapper: {
253
+ gap: theme.spacing.xs,
254
+ },
233
255
  messageContent: {
234
256
  color: theme.colors.typography,
235
257
  lineHeight: 20,
@@ -276,4 +298,4 @@ const styles = StyleSheet.create((theme) => ({
276
298
  sendButtonDisabled: {
277
299
  backgroundColor: theme.colors.border,
278
300
  },
279
- }));
301
+ }));
@@ -0,0 +1,15 @@
1
+ import { google } from '@ai-sdk/google';
2
+ import { streamText, type UIMessage, convertToModelMessages } from 'ai';
3
+
4
+ export const maxDuration = 30;
5
+
6
+ export async function POST(req: Request) {
7
+ const { messages }: { messages: UIMessage[] } = await req.json();
8
+
9
+ const result = streamText({
10
+ model: google('gemini-2.0-flash'),
11
+ messages: convertToModelMessages(messages),
12
+ });
13
+
14
+ return result.toUIMessageStreamResponse();
15
+ }
@@ -1,20 +1,35 @@
1
1
  <script setup lang="ts">
2
- import { useChat } from '@ai-sdk/vue'
2
+ import { Chat } from '@ai-sdk/vue'
3
+ import { DefaultChatTransport } from 'ai'
3
4
  import { nextTick, ref, watch } from 'vue'
4
5
 
5
6
  const config = useRuntimeConfig()
6
7
  const serverUrl = config.public.serverURL
7
8
 
8
- const { messages, input, handleSubmit } = useChat({
9
- api: `${serverUrl}/ai`,
9
+ const input = ref('')
10
+ const chat = new Chat({
11
+ transport: new DefaultChatTransport({
12
+ api: `${serverUrl}/ai`,
13
+ }),
10
14
  })
11
15
 
12
16
  const messagesEndRef = ref<null | HTMLDivElement>(null)
13
17
 
14
- watch(messages, async () => {
15
- await nextTick()
16
- messagesEndRef.value?.scrollIntoView({ behavior: 'smooth' })
17
- })
18
+ watch(
19
+ () => chat.messages,
20
+ async () => {
21
+ await nextTick()
22
+ messagesEndRef.value?.scrollIntoView({ behavior: 'smooth' })
23
+ }
24
+ )
25
+
26
+ const handleSubmit = (e: Event) => {
27
+ e.preventDefault()
28
+ const text = input.value.trim()
29
+ if (!text) return
30
+ chat.sendMessage({ text })
31
+ input.value = ''
32
+ }
18
33
 
19
34
  function getMessageText(message: any) {
20
35
  return message.parts
@@ -27,11 +42,11 @@ function getMessageText(message: any) {
27
42
  <template>
28
43
  <div class="grid grid-rows-[1fr_auto] overflow-hidden w-full mx-auto p-4">
29
44
  <div class="overflow-y-auto space-y-4 pb-4">
30
- <div v-if="messages.length === 0" class="text-center text-muted-foreground mt-8">
45
+ <div v-if="chat.messages.length === 0" class="text-center text-muted-foreground mt-8">
31
46
  Ask me anything to get started!
32
47
  </div>
33
48
  <div
34
- v-for="message in messages"
49
+ v-for="message in chat.messages"
35
50
  :key="message.id"
36
51
  :class="[
37
52
  'p-3 rounded-lg',
@@ -39,14 +54,14 @@ function getMessageText(message: any) {
39
54
  ]"
40
55
  >
41
56
  <p class="text-sm font-semibold mb-1">
42
- {{ message.role === 'user' ? 'You' : 'AI Assistant' }}
57
+ \{{ message.role === 'user' ? 'You' : 'AI Assistant' }}
43
58
  </p>
44
- <div class="whitespace-pre-wrap">{{ getMessageText(message) }}</div>
59
+ <div class="whitespace-pre-wrap">\{{ getMessageText(message) }}</div>
45
60
  </div>
46
- <div ref="messagesEndRef" />
61
+ <div ref="messagesEndRef"></div>
47
62
  </div>
48
63
 
49
- <form @submit.prevent="handleSubmit" class="w-full flex items-center space-x-2 pt-2 border-t">
64
+ <form @submit="handleSubmit" class="w-full flex items-center space-x-2 pt-2 border-t">
50
65
  <UInput
51
66
  name="prompt"
52
67
  v-model="input"
@@ -60,4 +75,4 @@ function getMessageText(message: any) {
60
75
  </UButton>
61
76
  </form>
62
77
  </div>
63
- </template>
78
+ </template>