create-tsrouter-app 0.3.0-alpha.4 → 0.3.0-alpha.6

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 (65) hide show
  1. package/dist/add-ons.js +8 -4
  2. package/dist/create-app.js +4 -0
  3. package/dist/options.js +5 -10
  4. package/package.json +1 -1
  5. package/src/add-ons.ts +10 -2
  6. package/src/create-app.ts +8 -0
  7. package/src/options.ts +10 -13
  8. package/templates/react/add-on/clerk/info.json +1 -0
  9. package/templates/react/add-on/convex/info.json +1 -0
  10. package/templates/react/add-on/form/info.json +1 -0
  11. package/templates/react/add-on/module-federation/assets/module-federation.config.js.ejs +31 -0
  12. package/templates/react/add-on/module-federation/assets/src/demo-mf-component.tsx +3 -0
  13. package/templates/react/add-on/module-federation/assets/src/demo-mf-self-contained.tsx +11 -0
  14. package/templates/react/add-on/module-federation/info.json +7 -0
  15. package/templates/react/add-on/module-federation/package.json +5 -0
  16. package/templates/react/add-on/netlify/info.json +2 -1
  17. package/templates/react/add-on/sentry/info.json +1 -0
  18. package/templates/react/add-on/shadcn/info.json +1 -0
  19. package/templates/react/add-on/start/info.json +1 -0
  20. package/templates/react/add-on/store/info.json +1 -0
  21. package/templates/react/add-on/tanstack-query/info.json +1 -0
  22. package/templates/react/base/src/App.tsx.ejs +1 -1
  23. package/templates/react/base/vite.config.js.ejs +5 -4
  24. package/templates/react/example/tanchat/README.md +37 -0
  25. package/templates/react/example/tanchat/assets/.env.local.append +2 -0
  26. package/templates/react/example/tanchat/assets/src/components/demo.SettingsDialog.tsx +148 -0
  27. package/templates/react/example/tanchat/assets/src/demo.index.css +220 -0
  28. package/templates/react/example/tanchat/assets/src/routes/example.chat.tsx.ejs +375 -0
  29. package/templates/react/example/tanchat/assets/src/store/demo.hooks.ts +21 -0
  30. package/templates/react/example/tanchat/assets/src/store/demo.store.ts +133 -0
  31. package/templates/react/example/tanchat/assets/src/utils/demo.ai.ts +108 -0
  32. package/templates/react/example/tanchat/info.json +15 -0
  33. package/templates/react/example/tanchat/package.json +10 -0
  34. package/templates/solid/add-on/form/assets/src/routes/demo.form.tsx +136 -0
  35. package/templates/solid/add-on/form/info.json +13 -0
  36. package/templates/solid/add-on/form/package.json +5 -0
  37. package/templates/solid/add-on/module-federation/assets/module-federation.config.js.ejs +27 -0
  38. package/templates/solid/add-on/module-federation/assets/src/demo-mf-component.tsx +3 -0
  39. package/templates/solid/add-on/module-federation/assets/src/demo-mf-self-contained.tsx +9 -0
  40. package/templates/solid/add-on/module-federation/info.json +7 -0
  41. package/templates/solid/add-on/module-federation/package.json +5 -0
  42. package/templates/solid/add-on/sentry/assets/.cursorrules.append +22 -0
  43. package/templates/solid/add-on/sentry/assets/.env.local.append +2 -0
  44. package/templates/solid/add-on/sentry/assets/src/routes/demo.sentry.bad-event-handler.tsx +20 -0
  45. package/templates/solid/add-on/sentry/info.json +13 -0
  46. package/templates/solid/add-on/sentry/package.json +5 -0
  47. package/templates/solid/add-on/solid-ui/README.md +9 -0
  48. package/templates/solid/add-on/solid-ui/assets/src/lib/utils.ts +6 -0
  49. package/templates/solid/add-on/solid-ui/assets/src/styles.css +138 -0
  50. package/templates/solid/add-on/solid-ui/assets/ui.config.json +13 -0
  51. package/templates/solid/add-on/solid-ui/info.json +11 -0
  52. package/templates/solid/add-on/solid-ui/package.json +9 -0
  53. package/templates/solid/add-on/store/assets/src/routes/demo.store.page1.tsx +1 -1
  54. package/templates/solid/add-on/store/assets/src/routes/demo.store.page2.tsx +1 -1
  55. package/templates/solid/add-on/store/info.json +1 -0
  56. package/templates/solid/add-on/tanstack-query/info.json +1 -0
  57. package/templates/solid/base/.cursorrules +35 -0
  58. package/templates/solid/base/tsconfig.json.ejs +6 -1
  59. package/templates/solid/base/vite.config.js.ejs +12 -4
  60. package/templates/solid/file-router/src/main.tsx.ejs +17 -4
  61. package/templates/solid/file-router/src/routes/__root.tsx.ejs +4 -1
  62. package/templates/react/example/ai-chat/assets/.env.local.append +0 -2
  63. package/templates/react/example/ai-chat/assets/src/routes/example.ai-chat.tsx.ejs +0 -81
  64. package/templates/react/example/ai-chat/info.json +0 -27
  65. package/templates/react/example/ai-chat/package.json +0 -1
package/dist/add-ons.js CHANGED
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'node:url';
5
5
  function isDirectory(path) {
6
6
  return statSync(path).isDirectory();
7
7
  }
8
- export async function getAllAddOns(framework) {
8
+ export async function getAllAddOns(framework, template) {
9
9
  const addOns = [];
10
10
  for (const type of ['add-on', 'example']) {
11
11
  const addOnsBase = fileURLToPath(new URL(`../templates/${framework}/${type}`, import.meta.url));
@@ -15,6 +15,10 @@ export async function getAllAddOns(framework) {
15
15
  for (const dir of await readdirSync(addOnsBase).filter((file) => isDirectory(resolve(addOnsBase, file)))) {
16
16
  const filePath = resolve(addOnsBase, dir, 'info.json');
17
17
  const fileContent = await readFile(filePath, 'utf-8');
18
+ const info = JSON.parse(fileContent);
19
+ if (!info.templates.includes(template)) {
20
+ continue;
21
+ }
18
22
  let packageAdditions = {};
19
23
  if (existsSync(resolve(addOnsBase, dir, 'package.json'))) {
20
24
  packageAdditions = JSON.parse(await readFile(resolve(addOnsBase, dir, 'package.json'), 'utf-8'));
@@ -24,9 +28,9 @@ export async function getAllAddOns(framework) {
24
28
  readme = await readFile(resolve(addOnsBase, dir, 'README.md'), 'utf-8');
25
29
  }
26
30
  addOns.push({
31
+ ...info,
27
32
  id: dir,
28
33
  type,
29
- ...JSON.parse(fileContent),
30
34
  directory: resolve(addOnsBase, dir),
31
35
  packageAdditions,
32
36
  readme,
@@ -36,9 +40,9 @@ export async function getAllAddOns(framework) {
36
40
  return addOns;
37
41
  }
38
42
  // Turn the list of chosen add-on IDs into a final list of add-ons by resolving dependencies
39
- export async function finalizeAddOns(framework, chosenAddOnIDs) {
43
+ export async function finalizeAddOns(framework, template, chosenAddOnIDs) {
40
44
  const finalAddOnIDs = new Set(chosenAddOnIDs);
41
- const addOns = await getAllAddOns(framework);
45
+ const addOns = await getAllAddOns(framework, template);
42
46
  for (const addOnID of finalAddOnIDs) {
43
47
  const addOn = addOns.find((a) => a.id === addOnID);
44
48
  if (!addOn) {
@@ -176,6 +176,10 @@ export async function createApp(options) {
176
176
  await mkdir(resolve(targetDir, 'src/routes'), { recursive: true });
177
177
  await mkdir(resolve(targetDir, 'src/components'), { recursive: true });
178
178
  }
179
+ // Check for a .cursorrules file
180
+ if (existsSync(resolve(templateDirBase, '.cursorrules'))) {
181
+ await copyFile(resolve(templateDirBase, '.cursorrules'), resolve(targetDir, '.cursorrules'));
182
+ }
179
183
  // Copy in Vite and Tailwind config and CSS
180
184
  if (!options.tailwind) {
181
185
  await copyFiles(templateDirBase, ['./src/App.css']);
package/dist/options.js CHANGED
@@ -173,10 +173,10 @@ export async function promptForOptions(cliOptions) {
173
173
  options.packageManager = cliOptions.packageManager;
174
174
  }
175
175
  // Select any add-ons
176
- const allAddOns = await getAllAddOns(options.framework);
176
+ const allAddOns = await getAllAddOns(options.framework, options.mode);
177
177
  const addOns = allAddOns.filter((addOn) => addOn.type === 'add-on');
178
178
  let selectedAddOns = [];
179
- if (options.mode === FILE_ROUTER && cliOptions.addOns && addOns.length > 0) {
179
+ if (options.typescript && cliOptions.addOns && addOns.length > 0) {
180
180
  const value = await multiselect({
181
181
  message: 'What add-ons would you like for your project:',
182
182
  options: addOns.map((addOn) => ({
@@ -186,7 +186,7 @@ export async function promptForOptions(cliOptions) {
186
186
  })),
187
187
  required: false,
188
188
  });
189
- if (isCancel(selectedAddOns)) {
189
+ if (isCancel(value)) {
190
190
  cancel('Operation cancelled.');
191
191
  process.exit(0);
192
192
  }
@@ -195,9 +195,7 @@ export async function promptForOptions(cliOptions) {
195
195
  // Select any examples
196
196
  const examples = allAddOns.filter((addOn) => addOn.type === 'example');
197
197
  let selectedExamples = [];
198
- if (options.mode === FILE_ROUTER &&
199
- cliOptions.addOns &&
200
- examples.length > 0) {
198
+ if (options.typescript && cliOptions.addOns && examples.length > 0) {
201
199
  const value = await multiselect({
202
200
  message: 'Would you like any examples?',
203
201
  options: examples.map((addOn) => ({
@@ -214,10 +212,7 @@ export async function promptForOptions(cliOptions) {
214
212
  selectedExamples = value;
215
213
  }
216
214
  if (selectedAddOns.length > 0 || selectedExamples.length > 0) {
217
- options.chosenAddOns = await finalizeAddOns(options.framework, [
218
- ...selectedAddOns,
219
- ...selectedExamples,
220
- ]);
215
+ options.chosenAddOns = await finalizeAddOns(options.framework, options.mode, [...selectedAddOns, ...selectedExamples]);
221
216
  options.tailwind = true;
222
217
  }
223
218
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-tsrouter-app",
3
- "version": "0.3.0-alpha.4",
3
+ "version": "0.3.0-alpha.6",
4
4
  "description": "Tanstack Application Builder",
5
5
  "bin": "./dist/index.js",
6
6
  "type": "module",
package/src/add-ons.ts CHANGED
@@ -34,6 +34,7 @@ export type AddOn = {
34
34
  name: string
35
35
  description: string
36
36
  link: string
37
+ templates: Array<string>
37
38
  main?: Array<{
38
39
  imports: Array<string>
39
40
  initialize: Array<string>
@@ -78,6 +79,7 @@ function isDirectory(path: string): boolean {
78
79
 
79
80
  export async function getAllAddOns(
80
81
  framework: Framework,
82
+ template: string,
81
83
  ): Promise<Array<AddOn>> {
82
84
  const addOns: Array<AddOn> = []
83
85
 
@@ -95,6 +97,11 @@ export async function getAllAddOns(
95
97
  )) {
96
98
  const filePath = resolve(addOnsBase, dir, 'info.json')
97
99
  const fileContent = await readFile(filePath, 'utf-8')
100
+ const info = JSON.parse(fileContent)
101
+
102
+ if (!info.templates.includes(template)) {
103
+ continue
104
+ }
98
105
 
99
106
  let packageAdditions: Record<string, string> = {}
100
107
  if (existsSync(resolve(addOnsBase, dir, 'package.json'))) {
@@ -109,9 +116,9 @@ export async function getAllAddOns(
109
116
  }
110
117
 
111
118
  addOns.push({
119
+ ...info,
112
120
  id: dir,
113
121
  type,
114
- ...JSON.parse(fileContent),
115
122
  directory: resolve(addOnsBase, dir),
116
123
  packageAdditions,
117
124
  readme,
@@ -125,11 +132,12 @@ export async function getAllAddOns(
125
132
  // Turn the list of chosen add-on IDs into a final list of add-ons by resolving dependencies
126
133
  export async function finalizeAddOns(
127
134
  framework: Framework,
135
+ template: string,
128
136
  chosenAddOnIDs: Array<string>,
129
137
  ): Promise<Array<AddOn>> {
130
138
  const finalAddOnIDs = new Set(chosenAddOnIDs)
131
139
 
132
- const addOns = await getAllAddOns(framework)
140
+ const addOns = await getAllAddOns(framework, template)
133
141
 
134
142
  for (const addOnID of finalAddOnIDs) {
135
143
  const addOn = addOns.find((a) => a.id === addOnID)
package/src/create-app.ts CHANGED
@@ -273,6 +273,14 @@ export async function createApp(options: Required<Options>) {
273
273
  await mkdir(resolve(targetDir, 'src/components'), { recursive: true })
274
274
  }
275
275
 
276
+ // Check for a .cursorrules file
277
+ if (existsSync(resolve(templateDirBase, '.cursorrules'))) {
278
+ await copyFile(
279
+ resolve(templateDirBase, '.cursorrules'),
280
+ resolve(targetDir, '.cursorrules'),
281
+ )
282
+ }
283
+
276
284
  // Copy in Vite and Tailwind config and CSS
277
285
  if (!options.tailwind) {
278
286
  await copyFiles(templateDirBase, ['./src/App.css'])
package/src/options.ts CHANGED
@@ -203,10 +203,10 @@ export async function promptForOptions(
203
203
  }
204
204
 
205
205
  // Select any add-ons
206
- const allAddOns = await getAllAddOns(options.framework)
206
+ const allAddOns = await getAllAddOns(options.framework, options.mode)
207
207
  const addOns = allAddOns.filter((addOn) => addOn.type === 'add-on')
208
208
  let selectedAddOns: Array<string> = []
209
- if (options.mode === FILE_ROUTER && cliOptions.addOns && addOns.length > 0) {
209
+ if (options.typescript && cliOptions.addOns && addOns.length > 0) {
210
210
  const value = await multiselect({
211
211
  message: 'What add-ons would you like for your project:',
212
212
  options: addOns.map((addOn) => ({
@@ -217,21 +217,17 @@ export async function promptForOptions(
217
217
  required: false,
218
218
  })
219
219
 
220
- if (isCancel(selectedAddOns)) {
220
+ if (isCancel(value)) {
221
221
  cancel('Operation cancelled.')
222
222
  process.exit(0)
223
223
  }
224
- selectedAddOns = value as Array<string>
224
+ selectedAddOns = value
225
225
  }
226
226
 
227
227
  // Select any examples
228
228
  const examples = allAddOns.filter((addOn) => addOn.type === 'example')
229
229
  let selectedExamples: Array<string> = []
230
- if (
231
- options.mode === FILE_ROUTER &&
232
- cliOptions.addOns &&
233
- examples.length > 0
234
- ) {
230
+ if (options.typescript && cliOptions.addOns && examples.length > 0) {
235
231
  const value = await multiselect({
236
232
  message: 'Would you like any examples?',
237
233
  options: examples.map((addOn) => ({
@@ -250,10 +246,11 @@ export async function promptForOptions(
250
246
  }
251
247
 
252
248
  if (selectedAddOns.length > 0 || selectedExamples.length > 0) {
253
- options.chosenAddOns = await finalizeAddOns(options.framework, [
254
- ...selectedAddOns,
255
- ...selectedExamples,
256
- ])
249
+ options.chosenAddOns = await finalizeAddOns(
250
+ options.framework,
251
+ options.mode,
252
+ [...selectedAddOns, ...selectedExamples],
253
+ )
257
254
  options.tailwind = true
258
255
  } else {
259
256
  options.chosenAddOns = []
@@ -2,6 +2,7 @@
2
2
  "name": "Clerk",
3
3
  "description": "Add Clerk authentication to your application.",
4
4
  "phase": "add-on",
5
+ "templates": ["file-router"],
5
6
  "link": "https://clerk.com",
6
7
  "main": {
7
8
  "imports": ["import { ClerkProvider } from '@clerk/clerk-react'"],
@@ -3,6 +3,7 @@
3
3
  "description": "Add the Convex database to your application.",
4
4
  "link": "https://convex.dev",
5
5
  "phase": "add-on",
6
+ "templates": ["file-router"],
6
7
  "main": {
7
8
  "imports": [
8
9
  "import { ConvexProvider } from 'convex/react'",
@@ -2,6 +2,7 @@
2
2
  "name": "Form",
3
3
  "description": "TansStack Form",
4
4
  "phase": "add-on",
5
+ "templates": ["file-router"],
5
6
  "link": "https://tanstack.com/form/latest",
6
7
  "routes": [
7
8
  {
@@ -0,0 +1,31 @@
1
+ import packageJSON from './package.json'
2
+
3
+ function remoteConfig(name, url) {
4
+ return {
5
+ type: 'module',
6
+ name,
7
+ entry: url,
8
+ entryGlobalName: 'remote',
9
+ shareScope: 'default',
10
+ }
11
+ }
12
+
13
+ export default {
14
+ filename: 'remoteEntry.js',
15
+ name: '<%= projectName %>',
16
+ exposes: {
17
+ './DemoMfComponent': './src/demo-mf-component.tsx',
18
+ './DemoMfSelfContained': './src/demo-mf-self-contained.tsx',
19
+ },
20
+ remotes: {},
21
+ shared: {
22
+ react: {
23
+ singleton: true,
24
+ requiredVersion: packageJSON.dependencies.react,
25
+ },
26
+ 'react-dom': {
27
+ singleton: true,
28
+ requiredVersion: packageJSON.dependencies['react-dom'],
29
+ },
30
+ },
31
+ }
@@ -0,0 +1,3 @@
1
+ export function DemoMfComponent() {
2
+ return <div>Demo Mf Component</div>
3
+ }
@@ -0,0 +1,11 @@
1
+ import React from 'react'
2
+ import ReactDOM from 'react-dom/client'
3
+
4
+ function App() {
5
+ return <div>Hello from self-contained module federation</div>
6
+ }
7
+
8
+ export function DemoMfSelfContained(rootElement: HTMLElement) {
9
+ const root = ReactDOM.createRoot(rootElement)
10
+ root.render(<App />)
11
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "Module Federation",
3
+ "description": "Module Federation",
4
+ "phase": "add-on",
5
+ "templates": ["code-router"],
6
+ "link": "https://module-federation.io/"
7
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "dependencies": {
3
+ "@module-federation/vite": "^1.1.9"
4
+ }
5
+ }
@@ -2,5 +2,6 @@
2
2
  "name": "Netlify",
3
3
  "description": "Netlify deployment setup",
4
4
  "link": "https://docs.netlify.com",
5
- "phase": "add-on"
5
+ "phase": "add-on",
6
+ "templates": ["file-router", "code-router"]
6
7
  }
@@ -3,6 +3,7 @@
3
3
  "phase": "setup",
4
4
  "description": "Add Sentry for error monitoring and crash reporting (requires Start).",
5
5
  "link": "https://sentry.com/",
6
+ "templates": ["file-router"],
6
7
  "routes": [
7
8
  {
8
9
  "url": "/demo/sentry/bad-server-func",
@@ -2,6 +2,7 @@
2
2
  "name": "Shadcn",
3
3
  "description": "Add Shadcn UI to your application.",
4
4
  "phase": "add-on",
5
+ "templates": ["file-router", "code-router"],
5
6
  "link": "https://ui.shadcn.com/",
6
7
  "command": {
7
8
  "command": "npx",
@@ -3,6 +3,7 @@
3
3
  "phase": "setup",
4
4
  "description": "Add TanStack Start for SSR, API endpoints, and more.",
5
5
  "link": "https://tanstack.com/start/latest",
6
+ "templates": ["file-router"],
6
7
  "warning": "TanStack Start is not yet at 1.0 and may change significantly or not be compatible with other add-ons.",
7
8
  "routes": [
8
9
  {
@@ -3,6 +3,7 @@
3
3
  "description": "Add TanStack Store to your application.",
4
4
  "phase": "add-on",
5
5
  "link": "https://tanstack.com/store/latest",
6
+ "templates": ["file-router"],
6
7
  "routes": [
7
8
  {
8
9
  "url": "/demo/store/page1",
@@ -2,6 +2,7 @@
2
2
  "name": "TanStack Query",
3
3
  "description": "Integrate TanStack Query into your application.",
4
4
  "phase": "add-on",
5
+ "templates": ["file-router"],
5
6
  "link": "https://tanstack.com/query/latest",
6
7
  "main": {
7
8
  "imports": [
@@ -1,5 +1,5 @@
1
1
  <% if (fileRouter) { %>
2
- import { createFileRoute } from "@tanstack/solid-router";
2
+ import { createFileRoute } from "@tanstack/react-router";
3
3
  import logo from "../logo.svg";<% if (!tailwind) { %>
4
4
  import "../App.css";
5
5
  <% } %>
@@ -2,14 +2,15 @@ import { defineConfig } from "vite";
2
2
  import viteReact from "@vitejs/plugin-react";<% if (tailwind) { %>
3
3
  import tailwindcss from "@tailwindcss/vite";
4
4
  <% } %><%if (fileRouter) { %>
5
- import { TanStackRouterVite } from "@tanstack/router-plugin/vite";
6
- <% } %><% if (addOnEnabled.shadcn) { %>
7
- import { resolve } from "path";
5
+ import { TanStackRouterVite } from "@tanstack/router-plugin/vite";<% } %><% if (addOnEnabled['module-federation']) { %>
6
+ import {federation} from "@module-federation/vite";<% } %><% if (addOnEnabled.shadcn) { %>
7
+ import { resolve } from "path";<% } %><% if (addOnEnabled['module-federation']) { %>
8
+ import federationConfig from "./module-federation.config.js";
8
9
  <% } %>
9
10
 
10
11
  // https://vitejs.dev/config/
11
12
  export default defineConfig({
12
- plugins: [<% if(fileRouter) { %>TanStackRouterVite(), <% } %>viteReact()<% if (tailwind) { %>, tailwindcss()<% } %>],
13
+ plugins: [<% if(fileRouter) { %>TanStackRouterVite(), <% } %>viteReact()<% if (tailwind) { %>, tailwindcss()<% } %><% if (addOnEnabled['module-federation']) { %>, federation(federationConfig)<% } %>],
13
14
  test: {
14
15
  globals: true,
15
16
  environment: "jsdom",
@@ -0,0 +1,37 @@
1
+ # TanStack Chat Application
2
+
3
+ Am example chat application built with TanStack Start, TanStack Store, and Claude AI.
4
+
5
+ ## .env Updates
6
+
7
+ ```env
8
+ ANTHROPIC_API_KEY=your_anthropic_api_key
9
+ ```
10
+
11
+ ## ✨ Features
12
+
13
+ ### AI Capabilities
14
+ - 🤖 Powered by Claude 3.5 Sonnet
15
+ - 📝 Rich markdown formatting with syntax highlighting
16
+ - 🎯 Customizable system prompts for tailored AI behavior
17
+ - 🔄 Real-time message updates and streaming responses (coming soon)
18
+
19
+ ### User Experience
20
+ - 🎨 Modern UI with Tailwind CSS and Lucide icons
21
+ - 🔍 Conversation management and history
22
+ - 🔐 Secure API key management
23
+ - 📋 Markdown rendering with code highlighting
24
+
25
+ ### Technical Features
26
+ - 📦 Centralized state management with TanStack Store
27
+ - 🔌 Extensible architecture for multiple AI providers
28
+ - 🛠️ TypeScript for type safety
29
+
30
+ ## Architecture
31
+
32
+ ### Tech Stack
33
+ - **Frontend Framework**: TanStack Start
34
+ - **Routing**: TanStack Router
35
+ - **State Management**: TanStack Store
36
+ - **Styling**: Tailwind CSS
37
+ - **AI Integration**: Anthropic's Claude API
@@ -0,0 +1,2 @@
1
+ # Add your Anthropic API key
2
+ ANTHROPIC_API_KEY=
@@ -0,0 +1,148 @@
1
+ import { useState } from 'react'
2
+ import { PlusCircle, Trash2 } from 'lucide-react'
3
+ import { useAppState } from '../store/demo.hooks'
4
+
5
+ interface SettingsDialogProps {
6
+ isOpen: boolean
7
+ onClose: () => void
8
+ }
9
+
10
+ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
11
+ const [promptForm, setPromptForm] = useState({ name: '', content: '' })
12
+ const [isAddingPrompt, setIsAddingPrompt] = useState(false)
13
+ const { prompts, createPrompt, deletePrompt, setPromptActive } = useAppState()
14
+
15
+ const handleAddPrompt = () => {
16
+ if (!promptForm.name.trim() || !promptForm.content.trim()) return
17
+ createPrompt(promptForm.name, promptForm.content)
18
+ setPromptForm({ name: '', content: '' })
19
+ setIsAddingPrompt(false)
20
+ }
21
+
22
+ const handleClose = () => {
23
+ onClose()
24
+ setIsAddingPrompt(false)
25
+ setPromptForm({ name: '', content: '' })
26
+ }
27
+
28
+ if (!isOpen) return null
29
+
30
+ return (
31
+ <div className="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center" onClick={(e) => {
32
+ if (e.target === e.currentTarget) handleClose()
33
+ }}>
34
+ <div className="bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto" onClick={e => e.stopPropagation()}>
35
+ <div className="p-6">
36
+ <div className="flex items-center justify-between mb-4">
37
+ <h2 className="text-2xl font-semibold text-white">Settings</h2>
38
+ <button
39
+ onClick={handleClose}
40
+ className="text-gray-400 hover:text-white focus:outline-none"
41
+ >
42
+ <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
43
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
44
+ </svg>
45
+ </button>
46
+ </div>
47
+
48
+ <div className="space-y-6">
49
+ {/* Prompts Management */}
50
+ <div className="space-y-2">
51
+ <div className="flex items-center justify-between mb-4">
52
+ <label className="block text-sm font-medium text-white">
53
+ System Prompts
54
+ </label>
55
+ <button
56
+ onClick={() => setIsAddingPrompt(true)}
57
+ className="flex items-center gap-2 px-3 py-1.5 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"
58
+ >
59
+ <PlusCircle className="w-4 h-4" />
60
+ Add Prompt
61
+ </button>
62
+ </div>
63
+
64
+ {isAddingPrompt && (
65
+ <div className="space-y-3 mb-4 p-3 bg-gray-700/50 rounded-lg">
66
+ <input
67
+ type="text"
68
+ value={promptForm.name}
69
+ onChange={(e) => setPromptForm(prev => ({ ...prev, name: e.target.value }))}
70
+ placeholder="Prompt name..."
71
+ className="w-full px-3 py-2 text-sm text-white bg-gray-700 rounded-lg border border-gray-600 focus:border-orange-500 focus:ring-1 focus:ring-orange-500"
72
+ />
73
+ <textarea
74
+ value={promptForm.content}
75
+ onChange={(e) => setPromptForm(prev => ({ ...prev, content: e.target.value }))}
76
+ placeholder="Enter prompt content..."
77
+ className="w-full h-32 px-3 py-2 text-sm text-white bg-gray-700 rounded-lg border border-gray-600 focus:border-orange-500 focus:ring-1 focus:ring-orange-500"
78
+ />
79
+ <div className="flex justify-end gap-2">
80
+ <button
81
+ onClick={() => setIsAddingPrompt(false)}
82
+ className="px-3 py-1.5 text-sm font-medium text-gray-300 hover:text-white focus:outline-none"
83
+ >
84
+ Cancel
85
+ </button>
86
+ <button
87
+ onClick={handleAddPrompt}
88
+ className="px-3 py-1.5 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"
89
+ >
90
+ Save Prompt
91
+ </button>
92
+ </div>
93
+ </div>
94
+ )}
95
+
96
+ <div className="space-y-2">
97
+ {prompts.map((prompt) => (
98
+ <div key={prompt.id} className="flex items-center justify-between p-3 bg-gray-700/50 rounded-lg">
99
+ <div className="flex-1 min-w-0 mr-4">
100
+ <h4 className="text-sm font-medium text-white truncate">{prompt.name}</h4>
101
+ <p className="text-xs text-gray-400 truncate">{prompt.content}</p>
102
+ </div>
103
+ <div className="flex items-center gap-2">
104
+ <label className="relative inline-flex items-center cursor-pointer">
105
+ <input
106
+ type="checkbox"
107
+ className="sr-only peer"
108
+ checked={prompt.is_active}
109
+ onChange={() => setPromptActive(prompt.id, !prompt.is_active)}
110
+ />
111
+ <div className="w-11 h-6 bg-gray-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-orange-500"></div>
112
+ </label>
113
+ <button
114
+ onClick={() => deletePrompt(prompt.id)}
115
+ className="p-1 text-gray-400 hover:text-red-500"
116
+ >
117
+ <Trash2 className="w-4 h-4" />
118
+ </button>
119
+ </div>
120
+ </div>
121
+ ))}
122
+ </div>
123
+ <p className="text-xs text-gray-400">
124
+ Create and manage custom system prompts. Only one prompt can be active at a time.
125
+ </p>
126
+ </div>
127
+
128
+ </div>
129
+
130
+ <div className="mt-6 flex justify-end gap-3">
131
+ <button
132
+ onClick={handleClose}
133
+ className="px-4 py-2 text-sm font-medium text-gray-300 hover:text-white focus:outline-none"
134
+ >
135
+ Cancel
136
+ </button>
137
+ <button
138
+ onClick={handleClose}
139
+ className="px-4 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"
140
+ >
141
+ Close
142
+ </button>
143
+ </div>
144
+ </div>
145
+ </div>
146
+ </div>
147
+ )
148
+ }