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
+});