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.
- package/dist/add-ons.js +8 -4
- package/dist/create-app.js +4 -0
- package/dist/options.js +5 -10
- package/package.json +1 -1
- package/src/add-ons.ts +10 -2
- package/src/create-app.ts +8 -0
- package/src/options.ts +10 -13
- package/templates/react/add-on/clerk/info.json +1 -0
- package/templates/react/add-on/convex/info.json +1 -0
- package/templates/react/add-on/form/info.json +1 -0
- package/templates/react/add-on/module-federation/assets/module-federation.config.js.ejs +31 -0
- package/templates/react/add-on/module-federation/assets/src/demo-mf-component.tsx +3 -0
- package/templates/react/add-on/module-federation/assets/src/demo-mf-self-contained.tsx +11 -0
- package/templates/react/add-on/module-federation/info.json +7 -0
- package/templates/react/add-on/module-federation/package.json +5 -0
- package/templates/react/add-on/netlify/info.json +2 -1
- package/templates/react/add-on/sentry/info.json +1 -0
- package/templates/react/add-on/shadcn/info.json +1 -0
- package/templates/react/add-on/start/info.json +1 -0
- package/templates/react/add-on/store/info.json +1 -0
- package/templates/react/add-on/tanstack-query/info.json +1 -0
- package/templates/react/base/src/App.tsx.ejs +1 -1
- package/templates/react/base/vite.config.js.ejs +5 -4
- package/templates/react/example/tanchat/README.md +37 -0
- package/templates/react/example/tanchat/assets/.env.local.append +2 -0
- package/templates/react/example/tanchat/assets/src/components/demo.SettingsDialog.tsx +148 -0
- package/templates/react/example/tanchat/assets/src/demo.index.css +220 -0
- package/templates/react/example/tanchat/assets/src/routes/example.chat.tsx.ejs +375 -0
- package/templates/react/example/tanchat/assets/src/store/demo.hooks.ts +21 -0
- package/templates/react/example/tanchat/assets/src/store/demo.store.ts +133 -0
- package/templates/react/example/tanchat/assets/src/utils/demo.ai.ts +108 -0
- package/templates/react/example/tanchat/info.json +15 -0
- package/templates/react/example/tanchat/package.json +10 -0
- package/templates/solid/add-on/form/assets/src/routes/demo.form.tsx +136 -0
- package/templates/solid/add-on/form/info.json +13 -0
- package/templates/solid/add-on/form/package.json +5 -0
- package/templates/solid/add-on/module-federation/assets/module-federation.config.js.ejs +27 -0
- package/templates/solid/add-on/module-federation/assets/src/demo-mf-component.tsx +3 -0
- package/templates/solid/add-on/module-federation/assets/src/demo-mf-self-contained.tsx +9 -0
- package/templates/solid/add-on/module-federation/info.json +7 -0
- package/templates/solid/add-on/module-federation/package.json +5 -0
- package/templates/solid/add-on/sentry/assets/.cursorrules.append +22 -0
- package/templates/solid/add-on/sentry/assets/.env.local.append +2 -0
- package/templates/solid/add-on/sentry/assets/src/routes/demo.sentry.bad-event-handler.tsx +20 -0
- package/templates/solid/add-on/sentry/info.json +13 -0
- package/templates/solid/add-on/sentry/package.json +5 -0
- package/templates/solid/add-on/solid-ui/README.md +9 -0
- package/templates/solid/add-on/solid-ui/assets/src/lib/utils.ts +6 -0
- package/templates/solid/add-on/solid-ui/assets/src/styles.css +138 -0
- package/templates/solid/add-on/solid-ui/assets/ui.config.json +13 -0
- package/templates/solid/add-on/solid-ui/info.json +11 -0
- package/templates/solid/add-on/solid-ui/package.json +9 -0
- package/templates/solid/add-on/store/assets/src/routes/demo.store.page1.tsx +1 -1
- package/templates/solid/add-on/store/assets/src/routes/demo.store.page2.tsx +1 -1
- package/templates/solid/add-on/store/info.json +1 -0
- package/templates/solid/add-on/tanstack-query/info.json +1 -0
- package/templates/solid/base/.cursorrules +35 -0
- package/templates/solid/base/tsconfig.json.ejs +6 -1
- package/templates/solid/base/vite.config.js.ejs +12 -4
- package/templates/solid/file-router/src/main.tsx.ejs +17 -4
- package/templates/solid/file-router/src/routes/__root.tsx.ejs +4 -1
- package/templates/react/example/ai-chat/assets/.env.local.append +0 -2
- package/templates/react/example/ai-chat/assets/src/routes/example.ai-chat.tsx.ejs +0 -81
- package/templates/react/example/ai-chat/info.json +0 -27
- 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) {
|
package/dist/create-app.js
CHANGED
|
@@ -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.
|
|
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(
|
|
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.
|
|
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
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.
|
|
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(
|
|
220
|
+
if (isCancel(value)) {
|
|
221
221
|
cancel('Operation cancelled.')
|
|
222
222
|
process.exit(0)
|
|
223
223
|
}
|
|
224
|
-
selectedAddOns = value
|
|
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(
|
|
254
|
-
|
|
255
|
-
|
|
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 = []
|
|
@@ -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,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
|
+
}
|
|
@@ -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
|
{
|
|
@@ -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
|
-
|
|
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,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
|
+
}
|