Skip to content

Commit aeea778

Browse files
authored
Adding TanStack Chat Sample (#18)
* bug: wrong router import. Set to react-router. * Adding sample for TanStack chat * Adding readme.md
1 parent 219c14d commit aeea778

File tree

11 files changed

+1069
-1
lines changed

11 files changed

+1069
-1
lines changed

templates/react/base/src/App.tsx.ejs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<% if (fileRouter) { %>
2-
import { createFileRoute } from "@tanstack/solid-router";
2+
import { createFileRoute } from "@tanstack/react-router";
33
import logo from "../logo.svg";<% if (!tailwind) { %>
44
import "../App.css";
55
<% } %>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Add your Anthropic API key
2+
ANTHROPIC_API_KEY=
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

0 commit comments

Comments
 (0)