create-start-app 0.3.1 → 0.4.1

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 (166) hide show
  1. package/README.md +53 -9
  2. package/dist/add-ons.js +69 -0
  3. package/dist/cli.js +78 -0
  4. package/dist/constants.js +4 -0
  5. package/dist/create-app.js +371 -0
  6. package/dist/index.js +2 -347
  7. package/dist/mcp.js +169 -0
  8. package/dist/options.js +261 -0
  9. package/dist/{utils/getPackageManager.js → package-manager.js} +1 -0
  10. package/dist/types.js +1 -0
  11. package/images/mcp-configuration.png +0 -0
  12. package/package.json +8 -4
  13. package/src/add-ons.ts +156 -0
  14. package/src/cli.ts +114 -0
  15. package/src/constants.ts +7 -0
  16. package/src/create-app.ts +582 -0
  17. package/src/index.ts +2 -507
  18. package/src/mcp.ts +205 -0
  19. package/src/options.ts +309 -0
  20. package/src/{utils/getPackageManager.ts → package-manager.ts} +1 -0
  21. package/src/types.ts +30 -0
  22. package/templates/react/add-on/clerk/README.md +3 -0
  23. package/templates/react/add-on/clerk/assets/_dot_env.local.append +2 -0
  24. package/templates/react/add-on/clerk/assets/src/integrations/clerk/header-user.tsx +19 -0
  25. package/templates/react/add-on/clerk/assets/src/integrations/clerk/provider.tsx +18 -0
  26. package/templates/react/add-on/clerk/assets/src/routes/demo.clerk.tsx +20 -0
  27. package/templates/react/add-on/clerk/info.json +13 -0
  28. package/templates/react/add-on/clerk/package.json +5 -0
  29. package/templates/react/add-on/convex/README.md +4 -0
  30. package/templates/react/add-on/convex/assets/_dot_cursorrules.append +93 -0
  31. package/templates/react/add-on/convex/assets/_dot_env.local.append +3 -0
  32. package/templates/react/add-on/convex/assets/convex/products.ts +8 -0
  33. package/templates/react/add-on/convex/assets/convex/schema.ts +10 -0
  34. package/templates/react/add-on/convex/assets/src/integrations/convex/provider.tsx +20 -0
  35. package/templates/react/add-on/convex/assets/src/routes/demo.convex.tsx +33 -0
  36. package/templates/react/add-on/convex/info.json +13 -0
  37. package/templates/react/add-on/convex/package.json +6 -0
  38. package/templates/react/add-on/form/assets/src/routes/demo.form.tsx.ejs +62 -0
  39. package/templates/react/add-on/form/info.json +13 -0
  40. package/templates/react/add-on/form/package.json +5 -0
  41. package/templates/react/add-on/module-federation/assets/module-federation.config.js.ejs +31 -0
  42. package/templates/react/add-on/module-federation/assets/src/demo-mf-component.tsx +3 -0
  43. package/templates/react/add-on/module-federation/assets/src/demo-mf-self-contained.tsx +11 -0
  44. package/templates/react/add-on/module-federation/info.json +7 -0
  45. package/templates/react/add-on/module-federation/package.json +5 -0
  46. package/templates/react/add-on/netlify/README.md +11 -0
  47. package/templates/react/add-on/netlify/info.json +7 -0
  48. package/templates/react/add-on/sentry/assets/_dot_cursorrules.append +22 -0
  49. package/templates/react/add-on/sentry/assets/_dot_env.local.append +2 -0
  50. package/templates/react/add-on/sentry/assets/src/app/global-middleware.ts +25 -0
  51. package/templates/react/add-on/sentry/assets/src/routes/demo.sentry.testing.tsx +480 -0
  52. package/templates/react/add-on/sentry/info.json +14 -0
  53. package/templates/react/add-on/sentry/package.json +7 -0
  54. package/templates/react/add-on/shadcn/README.md +7 -0
  55. package/templates/react/add-on/shadcn/assets/_dot_cursorrules.append +7 -0
  56. package/templates/react/add-on/shadcn/info.json +11 -0
  57. package/templates/react/add-on/start/assets/app.config.ts +16 -0
  58. package/templates/react/add-on/start/assets/postcss.config.ts +5 -0
  59. package/templates/react/add-on/start/assets/src/api.ts +6 -0
  60. package/templates/react/add-on/start/assets/src/client.tsx +10 -0
  61. package/templates/react/add-on/start/assets/src/router.tsx.ejs +51 -0
  62. package/templates/react/add-on/start/assets/src/routes/api.demo-names.ts +11 -0
  63. package/templates/react/add-on/start/assets/src/routes/demo.start.api-request.tsx.ejs +33 -0
  64. package/templates/react/add-on/start/assets/src/routes/demo.start.server-funcs.tsx +49 -0
  65. package/templates/react/add-on/start/assets/src/ssr.tsx +12 -0
  66. package/templates/react/add-on/start/info.json +19 -0
  67. package/templates/react/add-on/start/package.json +14 -0
  68. package/templates/react/add-on/store/assets/src/lib/demo-store.ts +13 -0
  69. package/templates/react/add-on/store/assets/src/routes/demo.store.tsx.ejs +75 -0
  70. package/templates/react/add-on/store/info.json +13 -0
  71. package/templates/react/add-on/store/package.json +6 -0
  72. package/templates/react/add-on/tanstack-query/assets/src/integrations/tanstack-query/layout.tsx +5 -0
  73. package/templates/react/add-on/tanstack-query/assets/src/integrations/tanstack-query/provider.tsx +9 -0
  74. package/templates/react/add-on/tanstack-query/assets/src/routes/demo.tanstack-query.tsx.ejs +38 -0
  75. package/templates/react/add-on/tanstack-query/info.json +13 -0
  76. package/templates/react/add-on/tanstack-query/package.json +6 -0
  77. package/templates/{base → react/base}/README.md.ejs +10 -1
  78. package/templates/react/base/src/components/Header.tsx.ejs +25 -0
  79. package/templates/{base/tsconfig.json → react/base/tsconfig.json.ejs} +5 -1
  80. package/templates/react/base/vite.config.js.ejs +24 -0
  81. package/templates/{code-router → react/code-router}/src/main.tsx.ejs +18 -1
  82. package/templates/react/example/tanchat/README.md +37 -0
  83. package/templates/react/example/tanchat/assets/_dot_env.local.append +2 -0
  84. package/templates/react/example/tanchat/assets/src/components/demo.SettingsDialog.tsx +148 -0
  85. package/templates/react/example/tanchat/assets/src/demo.index.css +220 -0
  86. package/templates/react/example/tanchat/assets/src/routes/example.chat.tsx.ejs +375 -0
  87. package/templates/react/example/tanchat/assets/src/store/demo.hooks.ts +21 -0
  88. package/templates/react/example/tanchat/assets/src/store/demo.store.ts +133 -0
  89. package/templates/react/example/tanchat/assets/src/utils/demo.ai.ts +108 -0
  90. package/templates/react/example/tanchat/info.json +15 -0
  91. package/templates/react/example/tanchat/package.json +10 -0
  92. package/templates/{file-router → react/file-router}/src/main.tsx.ejs +1 -1
  93. package/templates/react/file-router/src/routes/__root.tsx.ejs +71 -0
  94. package/templates/solid/add-on/form/assets/src/routes/demo.form.tsx.ejs +148 -0
  95. package/templates/solid/add-on/form/info.json +13 -0
  96. package/templates/solid/add-on/form/package.json +5 -0
  97. package/templates/solid/add-on/module-federation/assets/module-federation.config.js.ejs +27 -0
  98. package/templates/solid/add-on/module-federation/assets/src/demo-mf-component.tsx +3 -0
  99. package/templates/solid/add-on/module-federation/assets/src/demo-mf-self-contained.tsx +9 -0
  100. package/templates/solid/add-on/module-federation/info.json +7 -0
  101. package/templates/solid/add-on/module-federation/package.json +5 -0
  102. package/templates/solid/add-on/sentry/assets/_dot_cursorrules.append +22 -0
  103. package/templates/solid/add-on/sentry/assets/_dot_env.local.append +2 -0
  104. package/templates/solid/add-on/sentry/assets/src/routes/demo.sentry.bad-event-handler.tsx +20 -0
  105. package/templates/solid/add-on/sentry/info.json +13 -0
  106. package/templates/solid/add-on/sentry/package.json +5 -0
  107. package/templates/solid/add-on/solid-ui/README.md +9 -0
  108. package/templates/solid/add-on/solid-ui/assets/src/lib/utils.ts +6 -0
  109. package/templates/solid/add-on/solid-ui/assets/src/styles.css +138 -0
  110. package/templates/solid/add-on/solid-ui/assets/ui.config.json +13 -0
  111. package/templates/solid/add-on/solid-ui/info.json +11 -0
  112. package/templates/solid/add-on/solid-ui/package.json +9 -0
  113. package/templates/solid/add-on/store/assets/src/lib/demo-store.ts +13 -0
  114. package/templates/solid/add-on/store/assets/src/routes/demo.store.tsx.ejs +77 -0
  115. package/templates/solid/add-on/store/info.json +13 -0
  116. package/templates/solid/add-on/store/package.json +6 -0
  117. package/templates/solid/add-on/tanstack-query/assets/src/integrations/tanstack-query/header-user.tsx +5 -0
  118. package/templates/solid/add-on/tanstack-query/assets/src/integrations/tanstack-query/provider.tsx +15 -0
  119. package/templates/solid/add-on/tanstack-query/assets/src/routes/demo.tanstack-query.tsx +30 -0
  120. package/templates/solid/add-on/tanstack-query/info.json +13 -0
  121. package/templates/solid/add-on/tanstack-query/package.json +6 -0
  122. package/templates/solid/base/README.md.ejs +200 -0
  123. package/templates/solid/base/_dot_cursorrules.append +35 -0
  124. package/templates/solid/base/_dot_gitignore +5 -0
  125. package/templates/solid/base/_dot_vscode/settings.json +11 -0
  126. package/templates/solid/base/index.html.ejs +20 -0
  127. package/templates/solid/base/package.json +22 -0
  128. package/templates/solid/base/package.ts.json +5 -0
  129. package/templates/solid/base/package.tw.json +6 -0
  130. package/templates/solid/base/public/favicon.ico +0 -0
  131. package/templates/solid/base/public/logo192.png +0 -0
  132. package/templates/solid/base/public/logo512.png +0 -0
  133. package/templates/solid/base/public/manifest.json +25 -0
  134. package/templates/solid/base/public/robots.txt +3 -0
  135. package/templates/solid/base/src/App.css +0 -0
  136. package/templates/solid/base/src/App.tsx.ejs +47 -0
  137. package/templates/solid/base/src/components/Header.tsx.ejs +26 -0
  138. package/templates/solid/base/src/logo.svg +120 -0
  139. package/templates/solid/base/src/styles.css.ejs +15 -0
  140. package/templates/solid/base/tsconfig.json.ejs +30 -0
  141. package/templates/solid/base/vite.config.js.ejs +22 -0
  142. package/templates/solid/code-router/src/main.tsx.ejs +69 -0
  143. package/templates/solid/file-router/package.fr.json +5 -0
  144. package/templates/solid/file-router/src/main.tsx.ejs +44 -0
  145. package/templates/solid/file-router/src/routes/__root.tsx.ejs +41 -0
  146. package/templates/solid/file-router/src/routes/index.tsx +43 -0
  147. package/templates/base/vite.config.js.ejs +0 -15
  148. package/templates/file-router/src/routes/__root.tsx +0 -11
  149. /package/templates/{base → react/base}/_dot_gitignore +0 -0
  150. /package/templates/{base → react/base}/_dot_vscode/settings.json +0 -0
  151. /package/templates/{base → react/base}/index.html.ejs +0 -0
  152. /package/templates/{base → react/base}/package.json +0 -0
  153. /package/templates/{base → react/base}/package.ts.json +0 -0
  154. /package/templates/{base → react/base}/package.tw.json +0 -0
  155. /package/templates/{base → react/base}/public/favicon.ico +0 -0
  156. /package/templates/{base → react/base}/public/logo192.png +0 -0
  157. /package/templates/{base → react/base}/public/logo512.png +0 -0
  158. /package/templates/{base → react/base}/public/manifest.json +0 -0
  159. /package/templates/{base → react/base}/public/robots.txt +0 -0
  160. /package/templates/{base → react/base}/src/App.css +0 -0
  161. /package/templates/{base → react/base}/src/App.test.tsx.ejs +0 -0
  162. /package/templates/{base → react/base}/src/App.tsx.ejs +0 -0
  163. /package/templates/{base → react/base}/src/logo.svg +0 -0
  164. /package/templates/{base → react/base}/src/reportWebVitals.ts.ejs +0 -0
  165. /package/templates/{base → react/base}/src/styles.css.ejs +0 -0
  166. /package/templates/{file-router → react/file-router}/package.fr.json +0 -0
@@ -0,0 +1,220 @@
1
+ @import "tailwindcss";
2
+ @import "highlight.js/styles/github-dark.css";
3
+
4
+ /* Custom scrollbar styles */
5
+ ::-webkit-scrollbar {
6
+ width: 8px;
7
+ }
8
+
9
+ ::-webkit-scrollbar-track {
10
+ background: transparent;
11
+ }
12
+
13
+ ::-webkit-scrollbar-thumb {
14
+ background-color: rgba(156, 163, 175, 0.5);
15
+ border-radius: 4px;
16
+ }
17
+
18
+ ::-webkit-scrollbar-thumb:hover {
19
+ background-color: rgba(156, 163, 175, 0.7);
20
+ }
21
+
22
+ /* Smooth transitions for dark mode */
23
+ html {
24
+ transition: background-color 0.3s ease;
25
+ }
26
+
27
+ /* Markdown content styles */
28
+ .prose {
29
+ max-width: none;
30
+ color: #e5e7eb; /* text-gray-200 */
31
+ }
32
+
33
+ /* .prose p {
34
+ margin-top: 1.25em;
35
+ margin-bottom: 1.25em;
36
+ } */
37
+
38
+ .prose code {
39
+ color: #e5e7eb;
40
+ background-color: rgba(31, 41, 55, 0.5);
41
+ padding: 0.2em 0.4em;
42
+ border-radius: 0.375rem;
43
+ font-size: 0.875em;
44
+ }
45
+
46
+ .prose pre {
47
+ background-color: rgba(31, 41, 55, 0.5);
48
+ border-radius: 0.5rem;
49
+ padding: 1rem;
50
+ margin: 1.25em 0;
51
+ overflow-x: auto;
52
+ }
53
+
54
+ .prose pre code {
55
+ background-color: transparent;
56
+ padding: 0;
57
+ border-radius: 0;
58
+ color: inherit;
59
+ }
60
+
61
+ .prose h1, .prose h2, .prose h3, .prose h4 {
62
+ color: #f9fafb; /* text-gray-50 */
63
+ /* margin-top: 2em; */
64
+ /* margin-bottom: 1em; */
65
+ }
66
+
67
+ .prose ul, .prose ol {
68
+ margin-top: 1.25em;
69
+ margin-bottom: 1.25em;
70
+ padding-left: 1.625em;
71
+ }
72
+
73
+ .prose li {
74
+ margin-top: 0.5em;
75
+ margin-bottom: 0.5em;
76
+ }
77
+
78
+ .prose blockquote {
79
+ border-left-color: #f97316; /* orange-500 */
80
+ background-color: rgba(249, 115, 22, 0.1);
81
+ padding: 1em;
82
+ margin: 1.25em 0;
83
+ border-radius: 0.5rem;
84
+ }
85
+
86
+ .prose hr {
87
+ border-color: rgba(249, 115, 22, 0.2);
88
+ margin: 2em 0;
89
+ }
90
+
91
+ .prose a {
92
+ color: #f97316; /* orange-500 */
93
+ text-decoration: underline;
94
+ text-decoration-thickness: 0.1em;
95
+ text-underline-offset: 0.2em;
96
+ }
97
+
98
+ .prose a:hover {
99
+ color: #fb923c; /* orange-400 */
100
+ }
101
+
102
+ .prose table {
103
+ width: 100%;
104
+ border-collapse: collapse;
105
+ margin: 1.25em 0;
106
+ }
107
+
108
+ .prose th, .prose td {
109
+ padding: 0.75em;
110
+ border: 1px solid rgba(249, 115, 22, 0.2);
111
+ }
112
+
113
+ .prose th {
114
+ background-color: rgba(249, 115, 22, 0.1);
115
+ font-weight: 600;
116
+ }
117
+
118
+ /* Message transition animations */
119
+ .message-enter {
120
+ opacity: 0;
121
+ transform: translateY(10px);
122
+ }
123
+
124
+ .message-enter-active {
125
+ opacity: 1;
126
+ transform: translateY(0);
127
+ transition: opacity 300ms, transform 300ms;
128
+ }
129
+
130
+ .message-exit {
131
+ opacity: 1;
132
+ }
133
+
134
+ .message-exit-active {
135
+ opacity: 0;
136
+ transition: opacity 300ms;
137
+ }
138
+
139
+ /* Add/update these styles to match AI formatting capabilities */
140
+ .prose h1 {
141
+ font-size: 2em;
142
+ /* margin-top: 1em; */
143
+ margin-bottom: 0.5em;
144
+ }
145
+
146
+ .prose h2 {
147
+ font-size: 1.5em;
148
+ margin-top: 1em;
149
+ margin-bottom: 0.5em;
150
+ }
151
+
152
+ .prose h3 {
153
+ font-size: 1.25em;
154
+ margin-top: 1em;
155
+ margin-bottom: 0.5em;
156
+ }
157
+
158
+ .prose ul {
159
+ list-style-type: disc;
160
+ padding-left: 1.5em;
161
+ }
162
+
163
+ .prose ol {
164
+ list-style-type: decimal;
165
+ padding-left: 1.5em;
166
+ }
167
+
168
+ .prose table {
169
+ width: 100%;
170
+ border-collapse: collapse;
171
+ margin: 1em 0;
172
+ }
173
+
174
+ .prose th,
175
+ .prose td {
176
+ border: 1px solid rgba(249, 115, 22, 0.2);
177
+ padding: 0.5em;
178
+ }
179
+
180
+ .prose th {
181
+ background-color: rgba(249, 115, 22, 0.1);
182
+ }
183
+
184
+ .prose strong {
185
+ color: #f9fafb; /* text-gray-50 */
186
+ font-weight: 600;
187
+ }
188
+
189
+ .prose em {
190
+ font-style: italic;
191
+ }
192
+
193
+ .prose blockquote {
194
+ border-left: 4px solid #f97316; /* orange-500 */
195
+ padding-left: 1em;
196
+ margin: 1em 0;
197
+ color: #d1d5db; /* text-gray-300 */
198
+ }
199
+
200
+ /* Ensure code blocks match the AI's formatting */
201
+ .prose code {
202
+ color: #e5e7eb;
203
+ background-color: rgba(31, 41, 55, 0.5);
204
+ padding: 0.2em 0.4em;
205
+ border-radius: 0.375rem;
206
+ font-size: 0.875em;
207
+ }
208
+
209
+ .prose pre {
210
+ background-color: rgba(31, 41, 55, 0.5);
211
+ border-radius: 0.5rem;
212
+ padding: 1rem;
213
+ margin: 1em 0;
214
+ }
215
+
216
+ .prose pre code {
217
+ background-color: transparent;
218
+ padding: 0;
219
+ border-radius: 0;
220
+ }
@@ -0,0 +1,375 @@
1
+ import { createFileRoute } from '@tanstack/react-router'
2
+ import { useEffect, useState, useRef } from 'react'
3
+ import { PlusCircle, MessageCircle, ChevronLeft, ChevronRight, Trash2, X, Menu, Send, Settings, User, LogOut, Edit2 } from 'lucide-react'
4
+ import ReactMarkdown from 'react-markdown'
5
+ import rehypeRaw from 'rehype-raw'
6
+ import rehypeSanitize from 'rehype-sanitize'
7
+ import rehypeHighlight from 'rehype-highlight'
8
+ import { SettingsDialog } from '../components/demo.SettingsDialog'
9
+ import { useAppState } from '../store/demo.hooks'
10
+ import { store } from '../store/demo.store'
11
+ import { genAIResponse, type Message } from '../utils/demo.ai'
12
+ import "../demo.index.css"
13
+
14
+ function Home() {
15
+ const {
16
+ conversations,
17
+ currentConversationId,
18
+ isLoading,
19
+ setCurrentConversationId,
20
+ addConversation,
21
+ deleteConversation,
22
+ updateConversationTitle,
23
+ addMessage,
24
+ setLoading,
25
+ getCurrentConversation,
26
+ getActivePrompt
27
+ } = useAppState()
28
+
29
+ const currentConversation = getCurrentConversation(store.state)
30
+ const messages = currentConversation?.messages || []
31
+
32
+ // Local state
33
+ const [input, setInput] = useState('')
34
+ const [editingChatId, setEditingChatId] = useState<string | null>(null)
35
+ const [isSettingsOpen, setIsSettingsOpen] = useState(false)
36
+ const [isDropdownOpen, setIsDropdownOpen] = useState(false)
37
+ const messagesContainerRef = useRef<HTMLDivElement>(null)
38
+
39
+ const scrollToBottom = () => {
40
+ if (messagesContainerRef.current) {
41
+ messagesContainerRef.current.scrollTop = messagesContainerRef.current.scrollHeight
42
+ }
43
+ }
44
+
45
+ // Scroll to bottom when messages change or loading state changes
46
+ useEffect(() => {
47
+ scrollToBottom()
48
+ }, [messages, isLoading])
49
+
50
+ const handleSubmit = async (e: React.FormEvent) => {
51
+ e.preventDefault()
52
+ if (!input.trim() || isLoading) return
53
+
54
+ const currentInput = input
55
+ setInput('') // Clear input early for better UX
56
+ setLoading(true)
57
+
58
+ try {
59
+ let conversationId = currentConversationId
60
+
61
+ // If no current conversation, create one
62
+ if (!conversationId) {
63
+ conversationId = Date.now().toString()
64
+ const newConversation = {
65
+ id: conversationId,
66
+ title: currentInput.trim().slice(0, 30),
67
+ messages: []
68
+ }
69
+ addConversation(newConversation)
70
+ }
71
+
72
+ const userMessage: Message = {
73
+ id: Date.now().toString(),
74
+ role: 'user' as const,
75
+ content: currentInput.trim(),
76
+ }
77
+
78
+ // Add user message
79
+ addMessage(conversationId, userMessage)
80
+
81
+ // Get active prompt
82
+ const activePrompt = getActivePrompt(store.state)
83
+ let systemPrompt
84
+ if (activePrompt) {
85
+ systemPrompt = {
86
+ value: activePrompt.content,
87
+ enabled: true
88
+ }
89
+ }
90
+
91
+ // Get AI response
92
+ const response = await genAIResponse({
93
+ data: {
94
+ messages: [...messages, userMessage],
95
+ systemPrompt
96
+ }
97
+ })
98
+
99
+ if (!response.text?.trim()) {
100
+ throw new Error('Received empty response from AI')
101
+ }
102
+
103
+ const assistantMessage: Message = {
104
+ id: (Date.now() + 1).toString(),
105
+ role: 'assistant' as const,
106
+ content: response.text
107
+ }
108
+
109
+ addMessage(conversationId, assistantMessage)
110
+ } catch (error) {
111
+ console.error('Error:', error)
112
+ const errorMessage: Message = {
113
+ id: (Date.now() + 1).toString(),
114
+ role: 'assistant' as const,
115
+ content: 'Sorry, I encountered an error processing your request.',
116
+ }
117
+ if (currentConversationId) {
118
+ addMessage(currentConversationId, errorMessage)
119
+ }
120
+ } finally {
121
+ setLoading(false)
122
+ }
123
+ }
124
+
125
+ const handleNewChat = () => {
126
+ const newConversation = {
127
+ id: Date.now().toString(),
128
+ title: 'New Chat',
129
+ messages: []
130
+ }
131
+ addConversation(newConversation)
132
+ }
133
+
134
+ const handleDeleteChat = (id: string) => {
135
+ deleteConversation(id)
136
+ }
137
+
138
+ const handleUpdateChatTitle = (id: string, title: string) => {
139
+ updateConversationTitle(id, title)
140
+ setEditingChatId(null)
141
+ }
142
+
143
+ // Handle input change
144
+ const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
145
+ setInput(e.target.value)
146
+ }
147
+
148
+ return (
149
+ <div className="relative flex h-[calc(100vh-32px)] bg-gray-900">
150
+ {/* Settings Button */}
151
+ <div className="absolute top-5 right-5 z-50">
152
+ <button
153
+ onClick={() => setIsSettingsOpen(true)}
154
+ className="w-10 h-10 rounded-full bg-gradient-to-r from-orange-500 to-red-600 flex items-center justify-center text-white hover:opacity-90 transition-opacity focus:outline-none focus:ring-2 focus:ring-orange-500"
155
+ >
156
+ <Settings className="w-5 h-5" />
157
+ </button>
158
+ </div>
159
+
160
+ {/* Sidebar */}
161
+ <div className="flex flex-col w-64 bg-gray-800 border-r border-gray-700">
162
+ <div className="p-4 border-b border-gray-700">
163
+ <button
164
+ onClick={handleNewChat}
165
+ className="flex items-center gap-2 px-3 py-2 text-sm font-medium text-white bg-gradient-to-r from-orange-500 to-red-600 rounded-lg hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-orange-500 w-full justify-center"
166
+ >
167
+ <PlusCircle className="w-4 h-4" />
168
+ New Chat
169
+ </button>
170
+ </div>
171
+
172
+ {/* Chat List */}
173
+ <div className="flex-1 overflow-y-auto">
174
+ {conversations.map((chat) => (
175
+ <div
176
+ key={chat.id}
177
+ className={`group flex items-center gap-3 px-3 py-2 cursor-pointer hover:bg-gray-700/50 ${
178
+ chat.id === currentConversationId ? 'bg-gray-700/50' : ''
179
+ }`}
180
+ onClick={() => setCurrentConversationId(chat.id)}
181
+ >
182
+ <MessageCircle className="w-4 h-4 text-gray-400" />
183
+ {editingChatId === chat.id ? (
184
+ <input
185
+ type="text"
186
+ value={chat.title}
187
+ onChange={(e) => handleUpdateChatTitle(chat.id, e.target.value)}
188
+ onBlur={() => setEditingChatId(null)}
189
+ onKeyDown={(e) => {
190
+ if (e.key === 'Enter') {
191
+ handleUpdateChatTitle(chat.id, chat.title)
192
+ }
193
+ }}
194
+ className="flex-1 bg-transparent text-sm text-white focus:outline-none"
195
+ autoFocus
196
+ />
197
+ ) : (
198
+ <span className="flex-1 text-sm text-gray-300 truncate">
199
+ {chat.title}
200
+ </span>
201
+ )}
202
+ <div className="hidden group-hover:flex items-center gap-1">
203
+ <button
204
+ onClick={(e) => {
205
+ e.stopPropagation()
206
+ setEditingChatId(chat.id)
207
+ }}
208
+ className="p-1 text-gray-400 hover:text-white"
209
+ >
210
+ <Edit2 className="w-3 h-3" />
211
+ </button>
212
+ <button
213
+ onClick={(e) => {
214
+ e.stopPropagation()
215
+ handleDeleteChat(chat.id)
216
+ }}
217
+ className="p-1 text-gray-400 hover:text-red-500"
218
+ >
219
+ <Trash2 className="w-3 h-3" />
220
+ </button>
221
+ </div>
222
+ </div>
223
+ ))}
224
+ </div>
225
+ </div>
226
+
227
+ {/* Main Content */}
228
+ <div className="flex-1 flex flex-col">
229
+ {currentConversationId ? (
230
+ <>
231
+ {/* Messages */}
232
+ <div ref={messagesContainerRef} className="flex-1 overflow-y-auto pb-24">
233
+ <div className="max-w-3xl mx-auto w-full px-4">
234
+ {messages.map((message) => (
235
+ <div
236
+ key={message.id}
237
+ className={`py-6 ${message.role === 'assistant'
238
+ ? 'bg-gradient-to-r from-orange-500/5 to-red-600/5'
239
+ : 'bg-transparent'
240
+ }`}
241
+ >
242
+ <div className="flex items-start gap-4 max-w-3xl mx-auto w-full">
243
+ {message.role === 'assistant' ? (
244
+ <div className="w-8 h-8 rounded-lg bg-gradient-to-r from-orange-500 to-red-600 mt-2 flex items-center justify-center text-sm font-medium text-white flex-shrink-0">
245
+ AI
246
+ </div>
247
+ ) : (
248
+ <div className="w-8 h-8 rounded-lg bg-gray-700 flex items-center justify-center text-sm font-medium text-white flex-shrink-0">
249
+ Y
250
+ </div>
251
+ )}
252
+ <div className="flex-1 min-w-0">
253
+ <ReactMarkdown
254
+ className="prose dark:prose-invert max-w-none"
255
+ rehypePlugins={[rehypeRaw, rehypeSanitize, rehypeHighlight]}
256
+ >
257
+ {message.content}
258
+ </ReactMarkdown>
259
+ </div>
260
+ </div>
261
+ </div>
262
+ ))}
263
+ {isLoading && (
264
+ <div className="py-6 bg-gradient-to-r from-orange-500/5 to-red-600/5">
265
+ <div className="flex items-start gap-4 max-w-3xl mx-auto w-full">
266
+ <div className="relative w-8 h-8 flex-shrink-0">
267
+ <div className="absolute inset-0 rounded-lg bg-gradient-to-r from-orange-500 via-red-500 to-orange-500 animate-[spin_2s_linear_infinite]"></div>
268
+ <div className="absolute inset-[2px] rounded-lg bg-gray-900 flex items-center justify-center">
269
+ <div className="relative w-full h-full rounded-lg bg-gradient-to-r from-orange-500 to-red-600 flex items-center justify-center">
270
+ <div className="absolute inset-0 rounded-lg bg-gradient-to-r from-orange-500 to-red-600 animate-pulse"></div>
271
+ <span className="relative z-10 text-sm font-medium text-white">AI</span>
272
+ </div>
273
+ </div>
274
+ </div>
275
+ <div className="flex items-center gap-3">
276
+ <div className="text-gray-400 font-medium text-lg">Thinking</div>
277
+ <div className="flex gap-2">
278
+ <div className="w-2 h-2 rounded-full bg-orange-500 animate-[bounce_0.8s_infinite]" style={{ animationDelay: '0ms' }}></div>
279
+ <div className="w-2 h-2 rounded-full bg-orange-500 animate-[bounce_0.8s_infinite]" style={{ animationDelay: '200ms' }}></div>
280
+ <div className="w-2 h-2 rounded-full bg-orange-500 animate-[bounce_0.8s_infinite]" style={{ animationDelay: '400ms' }}></div>
281
+ </div>
282
+ </div>
283
+ </div>
284
+ </div>
285
+ )}
286
+ </div>
287
+ </div>
288
+
289
+ {/* Input */}
290
+ <div className="absolute bottom-0 right-0 left-64 bg-gray-900/80 backdrop-blur-sm border-t border-orange-500/10">
291
+ <div className="max-w-3xl mx-auto w-full px-4 py-3">
292
+ <form onSubmit={handleSubmit}>
293
+ <div className="relative">
294
+ <textarea
295
+ value={input}
296
+ onChange={handleInputChange}
297
+ onKeyDown={(e) => {
298
+ if (e.key === 'Enter' && !e.shiftKey) {
299
+ e.preventDefault()
300
+ handleSubmit(e)
301
+ }
302
+ }}
303
+ placeholder="Type something clever (or don't, we won't judge)..."
304
+ className="w-full rounded-lg border border-orange-500/20 bg-gray-800/50 pl-4 pr-12 py-3 text-sm text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-orange-500/50 focus:border-transparent resize-none overflow-hidden shadow-lg"
305
+ rows={1}
306
+ style={{ minHeight: '44px', maxHeight: '200px' }}
307
+ onInput={(e) => {
308
+ const target = e.target as HTMLTextAreaElement;
309
+ target.style.height = 'auto';
310
+ target.style.height = Math.min(target.scrollHeight, 200) + 'px';
311
+ }}
312
+ />
313
+ <button
314
+ type="submit"
315
+ disabled={!input.trim() || isLoading}
316
+ className="absolute right-2 top-1/2 -translate-y-1/2 p-2 text-orange-500 hover:text-orange-400 disabled:text-gray-500 transition-colors focus:outline-none"
317
+ >
318
+ <Send className="w-4 h-4" />
319
+ </button>
320
+ </div>
321
+ </form>
322
+ </div>
323
+ </div>
324
+ </>
325
+ ) : (
326
+ <div className="flex-1 flex items-center justify-center px-4">
327
+ <div className="text-center max-w-3xl mx-auto w-full">
328
+ <h1 className="text-6xl font-bold mb-4 bg-gradient-to-r from-orange-500 to-red-600 text-transparent bg-clip-text uppercase">
329
+ <span className="text-white">TanStack</span> Chat
330
+ </h1>
331
+ <p className="text-gray-400 mb-6 w-2/3 mx-auto text-lg">
332
+ You can ask me about anything, I might or might not have a good answer, but you can still ask.
333
+ </p>
334
+ <form onSubmit={handleSubmit}>
335
+ <div className="relative max-w-xl mx-auto">
336
+ <textarea
337
+ value={input}
338
+ onChange={handleInputChange}
339
+ onKeyDown={(e) => {
340
+ if (e.key === 'Enter' && !e.shiftKey) {
341
+ e.preventDefault()
342
+ handleSubmit(e)
343
+ }
344
+ }}
345
+ placeholder="Type something clever (or don't, we won't judge)..."
346
+ className="w-full rounded-lg border border-orange-500/20 bg-gray-800/50 pl-4 pr-12 py-3 text-sm text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-orange-500/50 focus:border-transparent resize-none overflow-hidden"
347
+ rows={1}
348
+ style={{ minHeight: '88px' }}
349
+ />
350
+ <button
351
+ type="submit"
352
+ disabled={!input.trim() || isLoading}
353
+ className="absolute right-2 top-1/2 -translate-y-1/2 p-2 text-orange-500 hover:text-orange-400 disabled:text-gray-500 transition-colors focus:outline-none"
354
+ >
355
+ <Send className="w-4 h-4" />
356
+ </button>
357
+ </div>
358
+ </form>
359
+ </div>
360
+ </div>
361
+ )}
362
+ </div>
363
+
364
+ {/* Settings Dialog */}
365
+ <SettingsDialog
366
+ isOpen={isSettingsOpen}
367
+ onClose={() => setIsSettingsOpen(false)}
368
+ />
369
+ </div>
370
+ )
371
+ }
372
+
373
+ export const Route = createFileRoute('/')({
374
+ component: Home
375
+ })
@@ -0,0 +1,21 @@
1
+ import { useStore } from '@tanstack/react-store'
2
+ import { store, actions, selectors } from './demo.store'
3
+
4
+ export type { State, Prompt, Conversation } from './demo.store'
5
+
6
+ export function useAppState() {
7
+ const state = useStore(store)
8
+ return {
9
+ ...state,
10
+ ...actions,
11
+ ...selectors
12
+ }
13
+ }
14
+
15
+ export function useAppActions() {
16
+ return actions
17
+ }
18
+
19
+ export function useAppSelectors() {
20
+ return selectors
21
+ }