Deploy KLAW.CHAT v3.9 with Netlify proxy and feeds

mindtdilly committed Sep 29, 2026 at 10:25 UTC 9cd6e9a67521f9255addf3f3112a4282ddf29ad2
8 files changed +156
.gitignore new
+4
@@ -0,0 +1,4 @@
1 +node_modules
2 +dist
3 +.DS_Store
4 +.env
README.md
+14
@@ -0,0 +1,14 @@
1 +# KLAW.CHAT v3.9
2 +
3 +Hosted operator console for the KLaw feeds.
4 +
5 +- KONNWEI KDIAG / MAXOBD workflow notes
6 +- BLE GATT discovery guidance for OBD adapters
7 +- Local bridge, dashboard, HUD projection, rclone, and Atlas Copco claw coupling reference
8 +- Netlify function at `/api/chat` proxies Anthropic when `ANTHROPIC_API_KEY` is set
9 +
10 +The site does not connect to a vehicle. Scanner apps and a local bridge stay on the device.
11 +
12 +## Key
13 +
14 +Netlify site settings → Environment variables → `ANTHROPIC_API_KEY` → redeploy.
index.html new
+13
@@ -0,0 +1,13 @@
1 +<!DOCTYPE html>
2 +<html lang="en">
3 + <head>
4 + <meta charset="UTF-8" />
5 + <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
6 + <meta name="theme-color" content="#060A0D" />
7 + <title>KLAW.CHAT v3.9</title>
8 + </head>
9 + <body>
10 + <div id="root"></div>
11 + <script type="module" src="/src/main.jsx"></script>
12 + </body>
13 +</html>
netlify.toml new
+17
@@ -0,0 +1,17 @@
1 +[build]
2 + command = "npm run build"
3 + publish = "dist"
4 + functions = "netlify/functions"
5 +
6 +[functions]
7 + node_bundler = "esbuild"
8 +
9 +[[redirects]]
10 + from = "/api/*"
11 + to = "/.netlify/functions/:splat"
12 + status = 200
13 +
14 +[[redirects]]
15 + from = "/*"
16 + to = "/index.html"
17 + status = 200
netlify/functions/chat.js new
+71
@@ -0,0 +1,71 @@
1 +exports.handler = async function handler(event) {
2 + if (event.httpMethod === "OPTIONS") {
3 + return { statusCode: 204, headers: cors(), body: "" };
4 + }
5 + if (event.httpMethod !== "POST") {
6 + return json(405, { error: "Method not allowed" });
7 + }
8 +
9 + const key = process.env.ANTHROPIC_API_KEY;
10 + if (!key) {
11 + return json(503, {
12 + error: "ANTHROPIC_API_KEY is not set on this Netlify site.",
13 + hint: "Site settings → Environment variables → ANTHROPIC_API_KEY, then redeploy."
14 + });
15 + }
16 +
17 + let body;
18 + try {
19 + body = JSON.parse(event.body || "{}");
20 + } catch {
21 + return json(400, { error: "Invalid JSON body" });
22 + }
23 +
24 + const messages = Array.isArray(body.messages) ? body.messages.slice(-12) : [];
25 + const system = typeof body.system === "string" ? body.system.slice(0, 4000) : "You are a precise technical assistant.";
26 +
27 + try {
28 + const response = await fetch("https://api.anthropic.com/v1/messages", {
29 + method: "POST",
30 + headers: {
31 + "content-type": "application/json",
32 + "x-api-key": key,
33 + "anthropic-version": "2023-06-01"
34 + },
35 + body: JSON.stringify({
36 + model: "claude-sonnet-4-20250514",
37 + max_tokens: 1200,
38 + system,
39 + messages
40 + })
41 + });
42 +
43 + const text = await response.text();
44 + if (!response.ok) {
45 + return json(response.status, { error: "Anthropic request failed", detail: text.slice(0, 800) });
46 + }
47 + return {
48 + statusCode: 200,
49 + headers: { ...cors(), "content-type": "application/json" },
50 + body: text
51 + };
52 + } catch (error) {
53 + return json(502, { error: error.message || "Proxy failed" });
54 + }
55 +};
56 +
57 +function cors() {
58 + return {
59 + "access-control-allow-origin": "*",
60 + "access-control-allow-headers": "content-type",
61 + "access-control-allow-methods": "POST, OPTIONS"
62 + };
63 +}
64 +
65 +function json(statusCode, payload) {
66 + return {
67 + statusCode,
68 + headers: { ...cors(), "content-type": "application/json" },
69 + body: JSON.stringify(payload)
70 + };
71 +}
package.json new
+19
@@ -0,0 +1,19 @@
1 +{
2 + "name": "klaw-chat",
3 + "version": "3.9.0",
4 + "private": true,
5 + "type": "module",
6 + "scripts": {
7 + "dev": "vite",
8 + "build": "vite build",
9 + "preview": "vite preview"
10 + },
11 + "dependencies": {
12 + "react": "^18.3.1",
13 + "react-dom": "^18.3.1"
14 + },
15 + "devDependencies": {
16 + "@vitejs/plugin-react": "^4.3.4",
17 + "vite": "^6.0.7"
18 + }
19 +}
src/main.jsx new
+10
@@ -0,0 +1,10 @@
1 +import React from "react";
2 +import ReactDOM from "react-dom/client";
3 +import App from "./App.jsx";
4 +import "./styles.css";
5 +
6 +ReactDOM.createRoot(document.getElementById("root")).render(
7 + <React.StrictMode>
8 + <App />
9 + </React.StrictMode>
10 +);
vite.config.js new
+8
@@ -0,0 +1,8 @@
1 +import { defineConfig } from "vite";
2 +import react from "@vitejs/plugin-react";
3 +
4 +export default defineConfig({
5 + plugins: [react()],
6 + server: { port: 3000, host: true },
7 + build: { outDir: "dist", sourcemap: false }
8 +});