ChatSettings.tsx7 matches
1819export default function Settings({ config, onUpdateConfig, onClose }: SettingsProps) {
20const [apiKey, setApiKey] = useState(config.anthropicApiKey);
21const [mcpServers, setMcpServers] = useState<MCPServer[]>(config.mcpServers);
22const [selectedModel, setSelectedModel] = useState(config.selectedModel);
37const handleSave = () => {
38onUpdateConfig({
39anthropicApiKey: apiKey,
40mcpServers: mcpServers,
41selectedModel: selectedModel,
141</div>
142143{/* API Key Section */}
144<div className="form-group">
145<label className="form-label">Anthropic API Key</label>
146<input
147type="password"
148value={apiKey}
149onChange={(e) => setApiKey(e.target.value)}
150placeholder="sk-ant-..."
151className="form-input"
152/>
153<div className="text-sm text-gray-600 mt-1">
154Get your API key from{" "}
155<a
156href="https://console.anthropic.com/"
1# Anthropic Streaming Chat with MCP
23A mobile-optimized single page chat application that uses the Anthropic Messages API with **real-time streaming** and MCP (Model Context Protocol) server support.
45Source: https://www.val.town/x/c15r/Chat
16- **⏹️ Stream control** - Stop streaming at any time with the stop button
17- Full-screen mobile-optimized chat interface
18- Direct client-side Anthropic API integration with server-side streaming proxy
19- **Model selection** - Choose between different Claude models (3.5 Sonnet, 3.5 Haiku, 3 Opus, etc.) without clearing chat history
20- Configurable MCP servers with localStorage persistence
6465The app stores configuration and chat history in localStorage:
66- `anthropic_api_key`: Your Anthropic API key
67- `selected_model`: The chosen Claude model (defaults to claude-3-5-sonnet-20241022)
68- `mcp_servers`: Array of configured MCP servers
69- `chat_messages`: Complete chat history (persists between page loads)
7071## API Endpoints
7273- `GET /` - Main application (serves frontend)
80811. Open the app at the provided URL
822. Click "Settings" in the footer to configure your Anthropic API key and select your preferred Claude model
833. Add/remove/toggle MCP servers as needed
844. Use the "Test" button next to each MCP server to verify connectivity (shows ✅ for success, ❌ for errors)
107- **Auto-scroll**: Messages automatically scroll to bottom during streaming
108- **Auto-resize**: Input field grows with content
109- **Error Handling**: Clear error messages for API issues with stream recovery
110- **Loading States**: Visual feedback during API calls and streaming
111- **Structured Responses**: MCP tool use and results are displayed in organized, collapsible sections
112- **Clean Interface**: Maximized chat area with no header, footer contains all controls
2021export interface AppConfig {
22anthropicApiKey: string;
23mcpServers: MCPServer[];
24selectedModel: string;
37export default function App() {
38const [config, setConfig] = useState<AppConfig>({
39anthropicApiKey: "",
40mcpServers: DEFAULT_MCP_SERVERS,
41selectedModel: "claude-3-5-sonnet-20241022",
46// Load config from localStorage on mount
47useEffect(() => {
48const savedApiKey = localStorage.getItem("anthropic_api_key");
49const savedMcpServers = localStorage.getItem("mcp_servers");
50const savedMessages = localStorage.getItem("chat_messages");
5253setConfig({
54anthropicApiKey: savedApiKey || "",
55mcpServers: savedMcpServers ? JSON.parse(savedMcpServers) : DEFAULT_MCP_SERVERS,
56selectedModel: savedModel || "claude-3-5-sonnet-20241022",
67}
6869// Show settings if no API key is configured
70if (!savedApiKey) {
71setShowSettings(true);
72}
75// Save config to localStorage when it changes
76useEffect(() => {
77if (config.anthropicApiKey) {
78localStorage.setItem("anthropic_api_key", config.anthropicApiKey);
79}
80localStorage.setItem("mcp_servers", JSON.stringify(config.mcpServers));
crm_OBUO_FARMSproducts.ts36 matches
6import type {
7CreateProductRequest, UpdateProductRequest, StockAdjustmentRequest,
8ApiResponse, Product, StockMovement
9} from "../../shared/types.ts";
1015try {
16const allProducts = await getAllProducts();
17return c.json<ApiResponse<Product[]>>({
18success: true,
19data: allProducts
21} catch (error) {
22console.error('Error fetching products:', error);
23return c.json<ApiResponse<null>>({
24success: false,
25error: "Failed to fetch products"
32try {
33const lowStockProducts = await getLowStockProducts();
34return c.json<ApiResponse<Product[]>>({
35success: true,
36data: lowStockProducts
38} catch (error) {
39console.error('Error fetching low stock products:', error);
40return c.json<ApiResponse<null>>({
41success: false,
42error: "Failed to fetch low stock products"
50const id = parseInt(c.req.param('id'));
51if (isNaN(id)) {
52return c.json<ApiResponse<null>>({
53success: false,
54error: "Invalid product ID"
58const product = await getProductById(id);
59if (!product) {
60return c.json<ApiResponse<null>>({
61success: false,
62error: "Product not found"
64}
65
66return c.json<ApiResponse<Product>>({
67success: true,
68data: product
70} catch (error) {
71console.error('Error fetching product:', error);
72return c.json<ApiResponse<null>>({
73success: false,
74error: "Failed to fetch product"
86for (const field of requiredFields) {
87if (productData[field as keyof CreateProductRequest] === undefined) {
88return c.json<ApiResponse<null>>({
89success: false,
90error: `Missing required field: ${field}`
94
95// Validate fish type
96if (!['Catfish', 'Tilapia'].includes(productData.fishType)) {
97return c.json<ApiResponse<null>>({
98success: false,
99error: "Invalid fish type. Must be 'Catfish' or 'Tilapia'"
100}, 400);
101}
103// Validate product form
104if (!['Fresh', 'Frozen', 'Live', 'Fillet', 'Smoked', 'Dried'].includes(productData.productForm)) {
105return c.json<ApiResponse<null>>({
106success: false,
107error: "Invalid product form"
111// Validate unit
112if (!['kg', 'pieces'].includes(productData.unit)) {
113return c.json<ApiResponse<null>>({
114success: false,
115error: "Invalid unit. Must be 'kg' or 'pieces'"
119// Validate stock levels
120if (productData.currentStock < 0 || productData.minStockLevel < 0 || productData.maxStockLevel < 0) {
121return c.json<ApiResponse<null>>({
122success: false,
123error: "Stock levels cannot be negative"
126
127if (productData.minStockLevel >= productData.maxStockLevel) {
128return c.json<ApiResponse<null>>({
129success: false,
130error: "Minimum stock level must be less than maximum stock level"
134const newProduct = await createProduct(productData);
135
136return c.json<ApiResponse<Product>>({
137success: true,
138data: newProduct
141} catch (error) {
142console.error('Error creating product:', error);
143return c.json<ApiResponse<null>>({
144success: false,
145error: error instanceof Error ? error.message : "Failed to create product"
153const id = parseInt(c.req.param('id'));
154if (isNaN(id)) {
155return c.json<ApiResponse<null>>({
156success: false,
157error: "Invalid product ID"
163
164if (!updatedProduct) {
165return c.json<ApiResponse<null>>({
166success: false,
167error: "Product not found"
169}
170
171return c.json<ApiResponse<Product>>({
172success: true,
173data: updatedProduct
175} catch (error) {
176console.error('Error updating product:', error);
177return c.json<ApiResponse<null>>({
178success: false,
179error: "Failed to update product"
187const id = parseInt(c.req.param('id'));
188if (isNaN(id)) {
189return c.json<ApiResponse<null>>({
190success: false,
191error: "Invalid product ID"
197// Validate required fields
198if (!adjustmentData.quantity || !adjustmentData.reason || !adjustmentData.movementType) {
199return c.json<ApiResponse<null>>({
200success: false,
201error: "Missing required fields: quantity, reason, movementType"
205// Validate movement type
206if (!['IN', 'OUT', 'ADJUSTMENT'].includes(adjustmentData.movementType)) {
207return c.json<ApiResponse<null>>({
208success: false,
209error: "Invalid movement type. Must be 'IN', 'OUT', or 'ADJUSTMENT'"
213// Validate quantity
214if (adjustmentData.quantity <= 0) {
215return c.json<ApiResponse<null>>({
216success: false,
217error: "Quantity must be greater than 0"
227const updatedProduct = await adjustStock(fullAdjustmentData);
228
229return c.json<ApiResponse<Product>>({
230success: true,
231data: updatedProduct
233} catch (error) {
234console.error('Error adjusting stock:', error);
235return c.json<ApiResponse<null>>({
236success: false,
237error: error instanceof Error ? error.message : "Failed to adjust stock"
245const id = parseInt(c.req.param('id'));
246if (isNaN(id)) {
247return c.json<ApiResponse<null>>({
248success: false,
249error: "Invalid product ID"
254const movements = await getStockMovements(id, limit);
255
256return c.json<ApiResponse<StockMovement[]>>({
257success: true,
258data: movements
260} catch (error) {
261console.error('Error fetching stock movements:', error);
262return c.json<ApiResponse<null>>({
263success: false,
264error: "Failed to fetch stock movements"
273const movements = await getStockMovements(undefined, limit);
274
275return c.json<ApiResponse<StockMovement[]>>({
276success: true,
277data: movements
279} catch (error) {
280console.error('Error fetching all stock movements:', error);
281return c.json<ApiResponse<null>>({
282success: false,
283error: "Failed to fetch stock movements"
295await resetAllStockToZero();
296
297return c.json<ApiResponse<{ message: string }>>({
298success: true,
299data: { message: "All product stock has been reset to zero" }
301} catch (error) {
302console.error('Error resetting stock:', error);
303return c.json<ApiResponse<null>>({
304success: false,
305error: "Failed to reset stock"
stevensDemosendDailyBrief.ts8 matches
9798export async function sendDailyBriefing(chatId?: string, today?: DateTime) {
99// Get API keys from environment
100const apiKey = Deno.env.get("ANTHROPIC_API_KEY");
101const telegramToken = Deno.env.get("TELEGRAM_TOKEN");
102106}
107108if (!apiKey) {
109console.error("Anthropic API key is not configured.");
110return;
111}
122123// Initialize Anthropic client
124const anthropic = new Anthropic({ apiKey });
125126// Initialize Telegram bot
162163// disabled title for now, it seemes unnecessary...
164// await bot.api.sendMessage(chatId, `*${title}*`, { parse_mode: "Markdown" });
165166// Then send the main content
169170if (content.length <= MAX_LENGTH) {
171await bot.api.sendMessage(chatId, content, { parse_mode: "Markdown" });
172// Store the briefing in chat history
173await storeChatMessage(
198// Send each chunk as a separate message and store in chat history
199for (const chunk of chunks) {
200await bot.api.sendMessage(chatId, chunk, { parse_mode: "Markdown" });
201// Store each chunk in chat history
202await storeChatMessage(
stevensDemoREADME.md1 match
53You'll need to set up some environment variables to make it run.
5455- `ANTHROPIC_API_KEY` for LLM calls
56- You'll need to follow [these instructions](https://docs.val.town/integrations/telegram/) to make a telegram bot, and set `TELEGRAM_TOKEN`. You'll also need to get a `TELEGRAM_CHAT_ID` in order to have the bot remember chat contents.
57- For the Google Calendar integration you'll need `GOOGLE_CALENDAR_ACCOUNT_ID` and `GOOGLE_CALENDAR_CALENDAR_ID`. See [these instuctions](https://www.val.town/v/stevekrouse/pipedream) for details.
stevensDemoREADME.md5 matches
8## Hono
910This app uses [Hono](https://hono.dev/) as the API framework. You can think of Hono as a replacement for [ExpressJS](https://expressjs.com/) that works in serverless environments like Val Town or Cloudflare Workers. If you come from Python or Ruby, Hono is also a lot like [Flask](https://github.com/pallets/flask) or [Sinatra](https://github.com/sinatra/sinatra), respectively.
1112## Serving assets to the frontend
20### `index.html`
2122The most complicated part of this backend API is serving index.html. In this app (like most apps) we serve it at the root, ie `GET /`.
2324We *bootstrap* `index.html` with some initial data from the server, so that it gets dynamically injected JSON data without having to make another round-trip request to the server to get that data on the frontend. This is a common pattern for client-side rendered apps.
2526## CRUD API Routes
2728This app has two CRUD API routes: for reading and inserting into the messages table. They both speak JSON, which is standard. They import their functions from `/backend/database/queries.ts`. These routes are called from the React app to refresh and update data.
2930## Errors
3132Hono and other API frameworks have a habit of swallowing up Errors. We turn off this default behavior by re-throwing errors, because we think most of the time you'll want to see the full stack trace instead of merely "Internal Server Error". You can customize how you want errors to appear.
stevensDemoNotebookView.tsx5 matches
8import { type Memory } from "../../shared/types.ts";
910const API_BASE = "/api/memories";
11const MEMORIES_PER_PAGE = 20;
1271setError(null);
72try {
73const response = await fetch(API_BASE);
74if (!response.ok) {
75throw new Error(`HTTP error! status: ${response.status}`);
100101try {
102const response = await fetch(API_BASE, {
103method: "POST",
104headers: { "Content-Type": "application/json" },
123124try {
125const response = await fetch(`${API_BASE}/${id}`, {
126method: "DELETE",
127});
155156try {
157const response = await fetch(`${API_BASE}/${editingMemory.id}`, {
158method: "PUT",
159headers: { "Content-Type": "application/json" },
stevensDemoindex.ts11 matches
18});
1920// --- API Routes for Memories ---
2122// GET /api/memories - Retrieve all memories
23app.get("/api/memories", async (c) => {
24const memories = await getAllMemories();
25return c.json(memories);
26});
2728// POST /api/memories - Create a new memory
29app.post("/api/memories", async (c) => {
30const body = await c.req.json<Omit<Memory, "id">>();
31if (!body.text) {
36});
3738// PUT /api/memories/:id - Update an existing memory
39app.put("/api/memories/:id", async (c) => {
40const id = c.req.param("id");
41const body = await c.req.json<Partial<Omit<Memory, "id">>>();
58});
5960// DELETE /api/memories/:id - Delete a memory
61app.delete("/api/memories/:id", async (c) => {
62const id = c.req.param("id");
63try {
75// --- Blob Image Serving Routes ---
7677// GET /api/images/:filename - Serve images from blob storage
78app.get("/api/images/:filename", async (c) => {
79const filename = c.req.param("filename");
80
stevensDemoindex.html2 matches
12type="image/svg+xml"
13/>
14<link rel="preconnect" href="https://fonts.googleapis.com" />
15<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
16<link
17href="https://fonts.googleapis.com/css2?family=Pixelify+Sans:wght@400..700&display=swap"
18rel="stylesheet"
19/>