@tanstack/cta-framework-solid 0.10.0-alpha.20
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/ADD-ON-AUTHORING.md +129 -0
- package/LICENSE +21 -0
- package/add-ons/form/assets/src/routes/demo.form.tsx.ejs +352 -0
- package/add-ons/form/info.json +16 -0
- package/add-ons/form/package.json +5 -0
- package/add-ons/module-federation/assets/module-federation.config.js.ejs +27 -0
- package/add-ons/module-federation/assets/src/demo-mf-component.tsx +3 -0
- package/add-ons/module-federation/assets/src/demo-mf-self-contained.tsx +9 -0
- package/add-ons/module-federation/info.json +8 -0
- package/add-ons/module-federation/package.json +5 -0
- package/add-ons/sentry/assets/_dot_cursorrules.append +22 -0
- package/add-ons/sentry/assets/_dot_env.local.append +2 -0
- package/add-ons/sentry/assets/src/routes/demo.sentry.bad-event-handler.tsx +20 -0
- package/add-ons/sentry/info.json +16 -0
- package/add-ons/sentry/package.json +5 -0
- package/add-ons/solid-ui/README.md +9 -0
- package/add-ons/solid-ui/assets/src/lib/utils.ts +6 -0
- package/add-ons/solid-ui/assets/src/styles.css +138 -0
- package/add-ons/solid-ui/assets/ui.config.json +13 -0
- package/add-ons/solid-ui/info.json +12 -0
- package/add-ons/solid-ui/package.json +9 -0
- package/add-ons/start/assets/app.config.ts.ejs +19 -0
- package/add-ons/start/assets/src/api.ts +6 -0
- package/add-ons/start/assets/src/client.tsx +7 -0
- package/add-ons/start/assets/src/router.tsx.ejs +24 -0
- package/add-ons/start/assets/src/routes/demo.start.server-funcs.tsx +49 -0
- package/add-ons/start/assets/src/ssr.tsx +12 -0
- package/add-ons/start/info.json +17 -0
- package/add-ons/start/package.json +12 -0
- package/add-ons/store/assets/src/lib/demo-store.ts +13 -0
- package/add-ons/store/assets/src/routes/demo.store.tsx.ejs +77 -0
- package/add-ons/store/info.json +16 -0
- package/add-ons/store/package.json +6 -0
- package/add-ons/t3env/README.md +16 -0
- package/add-ons/t3env/assets/src/env.ts +39 -0
- package/add-ons/t3env/info.json +8 -0
- package/add-ons/t3env/package.json +6 -0
- package/add-ons/tanstack-query/assets/src/integrations/tanstack-query/header-user.tsx +5 -0
- package/add-ons/tanstack-query/assets/src/integrations/tanstack-query/provider.tsx +15 -0
- package/add-ons/tanstack-query/assets/src/routes/demo.tanstack-query.tsx +24 -0
- package/add-ons/tanstack-query/info.json +28 -0
- package/add-ons/tanstack-query/package.json +6 -0
- package/dist/index.js +18 -0
- package/dist/types/index.d.ts +1 -0
- package/examples/tanchat/README.md +52 -0
- package/examples/tanchat/assets/ai-streaming-server/README.md +110 -0
- package/examples/tanchat/assets/ai-streaming-server/_dot_env.example +1 -0
- package/examples/tanchat/assets/ai-streaming-server/package.json +26 -0
- package/examples/tanchat/assets/ai-streaming-server/src/index.ts +102 -0
- package/examples/tanchat/assets/ai-streaming-server/tsconfig.json +15 -0
- package/examples/tanchat/assets/src/components/demo.SettingsDialog.tsx +149 -0
- package/examples/tanchat/assets/src/demo.index.css +227 -0
- package/examples/tanchat/assets/src/lib/demo-store.ts +13 -0
- package/examples/tanchat/assets/src/routes/example.chat.tsx +435 -0
- package/examples/tanchat/assets/src/store/demo.hooks.ts +17 -0
- package/examples/tanchat/assets/src/store/demo.store.ts +133 -0
- package/examples/tanchat/info.json +15 -0
- package/examples/tanchat/package.json +7 -0
- package/package.json +33 -0
- package/project/base/README.md.ejs +215 -0
- package/project/base/_dot_cursorrules.append +35 -0
- package/project/base/_dot_gitignore +5 -0
- package/project/base/_dot_vscode/settings.json.ejs +35 -0
- package/project/base/index.html.ejs +20 -0
- package/project/base/package.json +23 -0
- package/project/base/public/favicon.ico +0 -0
- package/project/base/public/logo192.png +0 -0
- package/project/base/public/logo512.png +0 -0
- package/project/base/public/manifest.json +25 -0
- package/project/base/public/robots.txt +3 -0
- package/project/base/src/App.css.ejs +38 -0
- package/project/base/src/App.tsx.ejs +34 -0
- package/project/base/src/components/Header.tsx.ejs +26 -0
- package/project/base/src/logo.svg +120 -0
- package/project/base/src/main.tsx.ejs +126 -0
- package/project/base/src/routes/__root.tsx.ejs +38 -0
- package/project/base/src/routes/index.tsx.ejs +41 -0
- package/project/base/src/styles.css.ejs +15 -0
- package/project/base/tsconfig.json.ejs +31 -0
- package/project/base/vite.config.js.ejs +22 -0
- package/project/packages.json +18 -0
- package/src/index.ts +26 -0
- package/tests/snapshots/solid/solid-cr-js-npm.json +22 -0
- package/tests/snapshots/solid/solid-cr-ts-npm.json +23 -0
- package/tests/snapshots/solid/solid-cr-ts-start-npm.json +27 -0
- package/tests/snapshots/solid/solid-fr-ts-npm.json +24 -0
- package/tests/snapshots/solid/solid-fr-ts-tw-npm.json +23 -0
- package/tests/solid.test.ts +119 -0
- package/tests/test-utilities.ts +44 -0
- package/toolchains/biome/assets/biome.json.ejs +31 -0
- package/toolchains/biome/info.json +8 -0
- package/toolchains/biome/package.json +10 -0
- package/toolchains/eslint/assets/_dot_prettierignore +3 -0
- package/toolchains/eslint/assets/eslint.config.js +5 -0
- package/toolchains/eslint/assets/prettier.config.js +10 -0
- package/toolchains/eslint/info.json +8 -0
- package/toolchains/eslint/package.json +11 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# TanStack Chat Application
|
|
2
|
+
|
|
3
|
+
Am example chat application built with TanStack Start, TanStack Store, and Claude AI.
|
|
4
|
+
|
|
5
|
+
## Sidecar service
|
|
6
|
+
|
|
7
|
+
This applicaton requires a sidecar microservice to be running. The server is located in the `ai-streaming-service` directory.
|
|
8
|
+
|
|
9
|
+
In that directory you should edit the `.env.local` file to add your Anthropic API key:
|
|
10
|
+
|
|
11
|
+
```env
|
|
12
|
+
ANTHROPIC_API_KEY=your_anthropic_api_key
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Then run the server:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
cd ai-streaming-service
|
|
19
|
+
npm install
|
|
20
|
+
npm run dev
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## ✨ Features
|
|
24
|
+
|
|
25
|
+
### AI Capabilities
|
|
26
|
+
|
|
27
|
+
- 🤖 Powered by Claude 3.5 Sonnet
|
|
28
|
+
- 📝 Rich markdown formatting with syntax highlighting
|
|
29
|
+
- 🎯 Customizable system prompts for tailored AI behavior
|
|
30
|
+
- 🔄 Real-time message updates and streaming responses (coming soon)
|
|
31
|
+
|
|
32
|
+
### User Experience
|
|
33
|
+
|
|
34
|
+
- 🎨 Modern UI with Tailwind CSS and Lucide icons
|
|
35
|
+
- 🔍 Conversation management and history
|
|
36
|
+
- 🔐 Secure API key management
|
|
37
|
+
- 📋 Markdown rendering with code highlighting
|
|
38
|
+
|
|
39
|
+
### Technical Features
|
|
40
|
+
|
|
41
|
+
- 📦 Centralized state management with TanStack Store
|
|
42
|
+
- 🔌 Extensible architecture for multiple AI providers
|
|
43
|
+
- 🛠️ TypeScript for type safety
|
|
44
|
+
|
|
45
|
+
## Architecture
|
|
46
|
+
|
|
47
|
+
### Tech Stack
|
|
48
|
+
|
|
49
|
+
- **Routing**: TanStack Router
|
|
50
|
+
- **State Management**: TanStack Store
|
|
51
|
+
- **Styling**: Tailwind CSS
|
|
52
|
+
- **AI Integration**: Anthropic's Claude API
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# AI Streaming Server
|
|
2
|
+
|
|
3
|
+
An Express server with TypeScript that provides a streaming API endpoint for Anthropic's Claude AI model.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
1. Clone the repository
|
|
8
|
+
2. Install dependencies:
|
|
9
|
+
```bash
|
|
10
|
+
npm install
|
|
11
|
+
```
|
|
12
|
+
3. Copy `.env.example` to `.env` and add your Anthropic API key:
|
|
13
|
+
```bash
|
|
14
|
+
cp .env.example .env
|
|
15
|
+
```
|
|
16
|
+
4. Edit `.env` and replace `your_api_key_here` with your actual Anthropic API key
|
|
17
|
+
|
|
18
|
+
## Development
|
|
19
|
+
|
|
20
|
+
Run in development mode with auto-reload:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm run dev
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Type checking:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm run typecheck
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Production
|
|
33
|
+
|
|
34
|
+
Build the TypeScript code:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm run build
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Run in production mode:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm start
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## API Usage
|
|
47
|
+
|
|
48
|
+
### POST /api/chat
|
|
49
|
+
|
|
50
|
+
Endpoint that accepts chat messages and returns a streaming response from Claude.
|
|
51
|
+
|
|
52
|
+
#### Request Body
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
interface ChatMessage {
|
|
56
|
+
role: "user" | "assistant";
|
|
57
|
+
content: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface ChatRequest {
|
|
61
|
+
messages: ChatMessage[];
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Example request:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"messages": [{ "role": "user", "content": "Hello, how are you?" }]
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
#### Response
|
|
74
|
+
|
|
75
|
+
The endpoint returns a Server-Sent Events (SSE) stream. Each event contains a chunk of the response from Claude.
|
|
76
|
+
|
|
77
|
+
Example usage with JavaScript/TypeScript:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
const response = await fetch("http://localhost:3000/api/chat", {
|
|
81
|
+
method: "POST",
|
|
82
|
+
headers: {
|
|
83
|
+
"Content-Type": "application/json",
|
|
84
|
+
},
|
|
85
|
+
body: JSON.stringify({
|
|
86
|
+
messages: [{ role: "user", content: "Hello, how are you?" }],
|
|
87
|
+
}),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const reader = response.body!.getReader();
|
|
91
|
+
const decoder = new TextDecoder();
|
|
92
|
+
|
|
93
|
+
while (true) {
|
|
94
|
+
const { value, done } = await reader.read();
|
|
95
|
+
if (done) break;
|
|
96
|
+
|
|
97
|
+
const chunk = decoder.decode(value);
|
|
98
|
+
const lines = chunk.split("\n");
|
|
99
|
+
|
|
100
|
+
for (const line of lines) {
|
|
101
|
+
if (line.startsWith("data: ")) {
|
|
102
|
+
const data = JSON.parse(line.slice(6));
|
|
103
|
+
if (data === "[DONE]") break;
|
|
104
|
+
|
|
105
|
+
// Handle the streaming response chunk
|
|
106
|
+
console.log(data);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ANTHROPIC_API_KEY=your_api_key_here
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ai-streaming-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Express server for streaming Anthropic AI chat responses",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"start": "node dist/index.js",
|
|
10
|
+
"dev": "tsx watch src/index.ts",
|
|
11
|
+
"typecheck": "tsc --noEmit"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@anthropic-ai/sdk": "^0.18.0",
|
|
15
|
+
"cors": "^2.8.5",
|
|
16
|
+
"dotenv": "^16.4.5",
|
|
17
|
+
"express": "^4.18.3"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/cors": "^2.8.17",
|
|
21
|
+
"@types/express": "^4.17.21",
|
|
22
|
+
"@types/node": "^20.11.24",
|
|
23
|
+
"tsx": "^4.7.1",
|
|
24
|
+
"typescript": "^5.3.3"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import express, { Request, Response } from 'express'
|
|
2
|
+
import cors from 'cors'
|
|
3
|
+
import dotenv from 'dotenv'
|
|
4
|
+
import Anthropic from '@anthropic-ai/sdk'
|
|
5
|
+
|
|
6
|
+
// Load environment variables
|
|
7
|
+
dotenv.config()
|
|
8
|
+
|
|
9
|
+
const app = express()
|
|
10
|
+
const port = process.env.PORT || 8080
|
|
11
|
+
|
|
12
|
+
// Middleware
|
|
13
|
+
app.use(cors())
|
|
14
|
+
app.use(express.json())
|
|
15
|
+
|
|
16
|
+
// Initialize Anthropic client
|
|
17
|
+
const anthropic = new Anthropic({
|
|
18
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
// Define types for the request body
|
|
22
|
+
interface ChatMessage {
|
|
23
|
+
role: 'user' | 'assistant'
|
|
24
|
+
content: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface ChatRequest {
|
|
28
|
+
messages: ChatMessage[]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Streaming chat endpoint
|
|
32
|
+
app.post(
|
|
33
|
+
'/api/chat',
|
|
34
|
+
async (req: Request<{}, {}, ChatRequest>, res: Response) => {
|
|
35
|
+
try {
|
|
36
|
+
const { messages } = req.body
|
|
37
|
+
|
|
38
|
+
if (!messages || !Array.isArray(messages)) {
|
|
39
|
+
return res.status(400).json({ error: 'Messages array is required' })
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Validate message format
|
|
43
|
+
const isValidMessages = messages.every(
|
|
44
|
+
(msg): msg is ChatMessage =>
|
|
45
|
+
typeof msg === 'object' &&
|
|
46
|
+
(msg.role === 'user' || msg.role === 'assistant') &&
|
|
47
|
+
typeof msg.content === 'string',
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
if (!isValidMessages) {
|
|
51
|
+
return res.status(400).json({
|
|
52
|
+
error:
|
|
53
|
+
"Invalid message format. Each message must have 'role' and 'content'",
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Set up SSE headers
|
|
58
|
+
res.setHeader('Content-Type', 'text/event-stream')
|
|
59
|
+
res.setHeader('Cache-Control', 'no-cache')
|
|
60
|
+
res.setHeader('Connection', 'keep-alive')
|
|
61
|
+
|
|
62
|
+
// Create the message stream
|
|
63
|
+
const stream = await anthropic.messages.create({
|
|
64
|
+
messages: messages.map((msg) => ({
|
|
65
|
+
role: msg.role,
|
|
66
|
+
content: msg.content,
|
|
67
|
+
})),
|
|
68
|
+
model: 'claude-3-opus-20240229',
|
|
69
|
+
max_tokens: 4096,
|
|
70
|
+
stream: true,
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
// Stream the response
|
|
74
|
+
for await (const chunk of stream) {
|
|
75
|
+
if (chunk.type === 'content_block_delta') {
|
|
76
|
+
res.write(`data: ${JSON.stringify(chunk)}\n\n`)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// End the stream
|
|
81
|
+
res.write('data: [DONE]\n\n')
|
|
82
|
+
res.end()
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error('Error:', error)
|
|
85
|
+
// If headers haven't been sent yet, send error response
|
|
86
|
+
if (!res.headersSent) {
|
|
87
|
+
res.status(500).json({ error: 'Internal server error' })
|
|
88
|
+
} else {
|
|
89
|
+
// If streaming has started, send error event
|
|
90
|
+
const errorMessage =
|
|
91
|
+
error instanceof Error ? error.message : 'Unknown error'
|
|
92
|
+
res.write(`data: ${JSON.stringify({ error: errorMessage })}\n\n`)
|
|
93
|
+
res.end()
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
// Start the server
|
|
100
|
+
app.listen(port, () => {
|
|
101
|
+
console.log(`Server is running on port ${port}`)
|
|
102
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"outDir": "dist",
|
|
10
|
+
"rootDir": "src",
|
|
11
|
+
"sourceMap": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*"],
|
|
14
|
+
"exclude": ["node_modules", "dist"]
|
|
15
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { PlusCircle, Trash2 } from 'lucide-solid'
|
|
2
|
+
import { useAppActions, useAppState } from '../store/demo.hooks'
|
|
3
|
+
import { createSignal } from 'solid-js'
|
|
4
|
+
|
|
5
|
+
interface SettingsDialogProps {
|
|
6
|
+
isOpen: boolean
|
|
7
|
+
onClose: () => void
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function SettingsDialog(props: SettingsDialogProps) {
|
|
11
|
+
const [promptForm, setPromptForm] = createSignal({ name: '', content: '' })
|
|
12
|
+
const [isAddingPrompt, setIsAddingPrompt] = createSignal(false)
|
|
13
|
+
const state = useAppState()
|
|
14
|
+
const actions = useAppActions();
|
|
15
|
+
|
|
16
|
+
const handleAddPrompt = () => {
|
|
17
|
+
if (!promptForm.name.trim() || !promptForm().content.trim()) return
|
|
18
|
+
actions.createPrompt(promptForm().name, promptForm().content)
|
|
19
|
+
setPromptForm({ name: '', content: '' })
|
|
20
|
+
setIsAddingPrompt(false)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const handleClose = () => {
|
|
24
|
+
props.onClose()
|
|
25
|
+
setIsAddingPrompt(false)
|
|
26
|
+
setPromptForm({ name: '', content: '' })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!props.isOpen) return null
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center" onClick={(e) => {
|
|
33
|
+
if (e.target === e.currentTarget) handleClose()
|
|
34
|
+
}}>
|
|
35
|
+
<div class="bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto" onClick={e => e.stopPropagation()}>
|
|
36
|
+
<div class="p-6">
|
|
37
|
+
<div class="flex items-center justify-between mb-4">
|
|
38
|
+
<h2 class="text-2xl font-semibold text-white">Settings</h2>
|
|
39
|
+
<button
|
|
40
|
+
onClick={handleClose}
|
|
41
|
+
class="text-gray-400 hover:text-white focus:outline-none"
|
|
42
|
+
>
|
|
43
|
+
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
44
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width={2} d="M6 18L18 6M6 6l12 12" />
|
|
45
|
+
</svg>
|
|
46
|
+
</button>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<div class="space-y-6">
|
|
50
|
+
{/* Prompts Management */}
|
|
51
|
+
<div class="space-y-2">
|
|
52
|
+
<div class="flex items-center justify-between mb-4">
|
|
53
|
+
<label class="block text-sm font-medium text-white">
|
|
54
|
+
System Prompts
|
|
55
|
+
</label>
|
|
56
|
+
<button
|
|
57
|
+
onClick={() => setIsAddingPrompt(true)}
|
|
58
|
+
class="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"
|
|
59
|
+
>
|
|
60
|
+
<PlusCircle class="w-4 h-4" />
|
|
61
|
+
Add Prompt
|
|
62
|
+
</button>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
{isAddingPrompt() && (
|
|
66
|
+
<div class="space-y-3 mb-4 p-3 bg-gray-700/50 rounded-lg">
|
|
67
|
+
<input
|
|
68
|
+
type="text"
|
|
69
|
+
value={promptForm().name}
|
|
70
|
+
onChange={(e) => setPromptForm(prev => ({ ...prev, name: e.target.value }))}
|
|
71
|
+
placeholder="Prompt name..."
|
|
72
|
+
class="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"
|
|
73
|
+
/>
|
|
74
|
+
<textarea
|
|
75
|
+
value={promptForm().content}
|
|
76
|
+
onChange={(e) => setPromptForm(prev => ({ ...prev, content: e.target.value }))}
|
|
77
|
+
placeholder="Enter prompt content..."
|
|
78
|
+
class="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"
|
|
79
|
+
/>
|
|
80
|
+
<div class="flex justify-end gap-2">
|
|
81
|
+
<button
|
|
82
|
+
onClick={() => setIsAddingPrompt(false)}
|
|
83
|
+
class="px-3 py-1.5 text-sm font-medium text-gray-300 hover:text-white focus:outline-none"
|
|
84
|
+
>
|
|
85
|
+
Cancel
|
|
86
|
+
</button>
|
|
87
|
+
<button
|
|
88
|
+
onClick={handleAddPrompt}
|
|
89
|
+
class="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"
|
|
90
|
+
>
|
|
91
|
+
Save Prompt
|
|
92
|
+
</button>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
)}
|
|
96
|
+
|
|
97
|
+
<div class="space-y-2">
|
|
98
|
+
{state().prompts.map((prompt) => (
|
|
99
|
+
<div class="flex items-center justify-between p-3 bg-gray-700/50 rounded-lg">
|
|
100
|
+
<div class="flex-1 min-w-0 mr-4">
|
|
101
|
+
<h4 class="text-sm font-medium text-white truncate">{prompt.name}</h4>
|
|
102
|
+
<p class="text-xs text-gray-400 truncate">{prompt.content}</p>
|
|
103
|
+
</div>
|
|
104
|
+
<div class="flex items-center gap-2">
|
|
105
|
+
<label class="relative inline-flex items-center cursor-pointer">
|
|
106
|
+
<input
|
|
107
|
+
type="checkbox"
|
|
108
|
+
class="sr-only peer"
|
|
109
|
+
checked={prompt.is_active}
|
|
110
|
+
onChange={() => actions.setPromptActive(prompt.id, !prompt.is_active)}
|
|
111
|
+
/>
|
|
112
|
+
<div class="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>
|
|
113
|
+
</label>
|
|
114
|
+
<button
|
|
115
|
+
onClick={() => actions.deletePrompt(prompt.id)}
|
|
116
|
+
class="p-1 text-gray-400 hover:text-red-500"
|
|
117
|
+
>
|
|
118
|
+
<Trash2 class="w-4 h-4" />
|
|
119
|
+
</button>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
))}
|
|
123
|
+
</div>
|
|
124
|
+
<p class="text-xs text-gray-400">
|
|
125
|
+
Create and manage custom system prompts. Only one prompt can be active at a time.
|
|
126
|
+
</p>
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<div class="mt-6 flex justify-end gap-3">
|
|
132
|
+
<button
|
|
133
|
+
onClick={handleClose}
|
|
134
|
+
class="px-4 py-2 text-sm font-medium text-gray-300 hover:text-white focus:outline-none"
|
|
135
|
+
>
|
|
136
|
+
Cancel
|
|
137
|
+
</button>
|
|
138
|
+
<button
|
|
139
|
+
onClick={handleClose}
|
|
140
|
+
class="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"
|
|
141
|
+
>
|
|
142
|
+
Close
|
|
143
|
+
</button>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
)
|
|
149
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
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,
|
|
62
|
+
.prose h2,
|
|
63
|
+
.prose h3,
|
|
64
|
+
.prose h4 {
|
|
65
|
+
color: #f9fafb; /* text-gray-50 */
|
|
66
|
+
/* margin-top: 2em; */
|
|
67
|
+
/* margin-bottom: 1em; */
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.prose ul,
|
|
71
|
+
.prose ol {
|
|
72
|
+
margin-top: 1.25em;
|
|
73
|
+
margin-bottom: 1.25em;
|
|
74
|
+
padding-left: 1.625em;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.prose li {
|
|
78
|
+
margin-top: 0.5em;
|
|
79
|
+
margin-bottom: 0.5em;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.prose blockquote {
|
|
83
|
+
border-left-color: #f97316; /* orange-500 */
|
|
84
|
+
background-color: rgba(249, 115, 22, 0.1);
|
|
85
|
+
padding: 1em;
|
|
86
|
+
margin: 1.25em 0;
|
|
87
|
+
border-radius: 0.5rem;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.prose hr {
|
|
91
|
+
border-color: rgba(249, 115, 22, 0.2);
|
|
92
|
+
margin: 2em 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.prose a {
|
|
96
|
+
color: #f97316; /* orange-500 */
|
|
97
|
+
text-decoration: underline;
|
|
98
|
+
text-decoration-thickness: 0.1em;
|
|
99
|
+
text-underline-offset: 0.2em;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.prose a:hover {
|
|
103
|
+
color: #fb923c; /* orange-400 */
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.prose table {
|
|
107
|
+
width: 100%;
|
|
108
|
+
border-collapse: collapse;
|
|
109
|
+
margin: 1.25em 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.prose th,
|
|
113
|
+
.prose td {
|
|
114
|
+
padding: 0.75em;
|
|
115
|
+
border: 1px solid rgba(249, 115, 22, 0.2);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.prose th {
|
|
119
|
+
background-color: rgba(249, 115, 22, 0.1);
|
|
120
|
+
font-weight: 600;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* Message transition animations */
|
|
124
|
+
.message-enter {
|
|
125
|
+
opacity: 0;
|
|
126
|
+
transform: translateY(10px);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.message-enter-active {
|
|
130
|
+
opacity: 1;
|
|
131
|
+
transform: translateY(0);
|
|
132
|
+
transition:
|
|
133
|
+
opacity 300ms,
|
|
134
|
+
transform 300ms;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.message-exit {
|
|
138
|
+
opacity: 1;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.message-exit-active {
|
|
142
|
+
opacity: 0;
|
|
143
|
+
transition: opacity 300ms;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* Add/update these styles to match AI formatting capabilities */
|
|
147
|
+
.prose h1 {
|
|
148
|
+
font-size: 2em;
|
|
149
|
+
/* margin-top: 1em; */
|
|
150
|
+
margin-bottom: 0.5em;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.prose h2 {
|
|
154
|
+
font-size: 1.5em;
|
|
155
|
+
margin-top: 1em;
|
|
156
|
+
margin-bottom: 0.5em;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.prose h3 {
|
|
160
|
+
font-size: 1.25em;
|
|
161
|
+
margin-top: 1em;
|
|
162
|
+
margin-bottom: 0.5em;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.prose ul {
|
|
166
|
+
list-style-type: disc;
|
|
167
|
+
padding-left: 1.5em;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.prose ol {
|
|
171
|
+
list-style-type: decimal;
|
|
172
|
+
padding-left: 1.5em;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.prose table {
|
|
176
|
+
width: 100%;
|
|
177
|
+
border-collapse: collapse;
|
|
178
|
+
margin: 1em 0;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.prose th,
|
|
182
|
+
.prose td {
|
|
183
|
+
border: 1px solid rgba(249, 115, 22, 0.2);
|
|
184
|
+
padding: 0.5em;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.prose th {
|
|
188
|
+
background-color: rgba(249, 115, 22, 0.1);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.prose strong {
|
|
192
|
+
color: #f9fafb; /* text-gray-50 */
|
|
193
|
+
font-weight: 600;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.prose em {
|
|
197
|
+
font-style: italic;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.prose blockquote {
|
|
201
|
+
border-left: 4px solid #f97316; /* orange-500 */
|
|
202
|
+
padding-left: 1em;
|
|
203
|
+
margin: 1em 0;
|
|
204
|
+
color: #d1d5db; /* text-gray-300 */
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/* Ensure code blocks match the AI's formatting */
|
|
208
|
+
.prose code {
|
|
209
|
+
color: #e5e7eb;
|
|
210
|
+
background-color: rgba(31, 41, 55, 0.5);
|
|
211
|
+
padding: 0.2em 0.4em;
|
|
212
|
+
border-radius: 0.375rem;
|
|
213
|
+
font-size: 0.875em;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.prose pre {
|
|
217
|
+
background-color: rgba(31, 41, 55, 0.5);
|
|
218
|
+
border-radius: 0.5rem;
|
|
219
|
+
padding: 1rem;
|
|
220
|
+
margin: 1em 0;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.prose pre code {
|
|
224
|
+
background-color: transparent;
|
|
225
|
+
padding: 0;
|
|
226
|
+
border-radius: 0;
|
|
227
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Derived, Store } from '@tanstack/store'
|
|
2
|
+
|
|
3
|
+
export const store = new Store({
|
|
4
|
+
firstName: 'Jane',
|
|
5
|
+
lastName: 'Smith',
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
export const fullName = new Derived({
|
|
9
|
+
fn: () => `${store.state.firstName} ${store.state.lastName}`,
|
|
10
|
+
deps: [store],
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
fullName.mount()
|