diff --git a/agents/k8s/reminder-service-deployment.yaml b/agents/k8s/reminder-service-deployment.yaml index 2157429..c814340 100644 --- a/agents/k8s/reminder-service-deployment.yaml +++ b/agents/k8s/reminder-service-deployment.yaml @@ -60,7 +60,10 @@ spec: - name: PERSONAL_NUM value: "+14085505485" - name: GROUP_URL - value: "https://chat.whatsapp.com/FXO0SCLA8DpJwClNNWdhhx" + valueFrom: + configMapKeyRef: + name: whatsapp-config + key: default-group-url volumeMounts: - name: app-volume mountPath: /app diff --git a/agents/k8s/trip-service-deployment.yaml b/agents/k8s/trip-service-deployment.yaml index 674a8f4..970aa4a 100644 --- a/agents/k8s/trip-service-deployment.yaml +++ b/agents/k8s/trip-service-deployment.yaml @@ -61,7 +61,10 @@ spec: - name: PERSONAL_NUM value: "+14085505485" - name: GROUP_URL - value: "https://chat.whatsapp.com/FXO0SCLA8DpJwClNNWdhhx" + valueFrom: + configMapKeyRef: + name: whatsapp-config + key: trip-group-url - name: ANTHROPIC_API_KEY valueFrom: secretKeyRef: diff --git a/agents/k8s/weather-cronjob.yaml b/agents/k8s/weather-cronjob.yaml index 8f4b46b..576029e 100644 --- a/agents/k8s/weather-cronjob.yaml +++ b/agents/k8s/weather-cronjob.yaml @@ -19,6 +19,12 @@ spec: containers: - name: weather-reporter image: node:18-alpine + env: + - name: GROUP_URL + valueFrom: + configMapKeyRef: + name: whatsapp-config + key: default-group-url command: - "node" - "-e" @@ -28,7 +34,7 @@ spec: const ZIP = '16046'; const GATEWAY = 'http://whatsapp-gateway.ai-agents.svc.cluster.local:5001/send-message'; - const GROUP = 'https://chat.whatsapp.com/FXO0SCLA8DpJwClNNWdhhx'; + const GROUP = process.env.GROUP_URL || 'https://chat.whatsapp.com/0Txfg7iZBbvIuvTbTqqgAX'; const PERSONAL = '+14085505485'; function weatherEmoji(desc) { diff --git a/agents/k8s/whatsapp-config.yaml b/agents/k8s/whatsapp-config.yaml new file mode 100644 index 0000000..a05569a --- /dev/null +++ b/agents/k8s/whatsapp-config.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: whatsapp-config + namespace: ai-agents +data: + trip-group-url: "https://chat.whatsapp.com/HN2z93yhXPyEJPuoib3dws" + default-group-url: "https://chat.whatsapp.com/0Txfg7iZBbvIuvTbTqqgAX" diff --git a/interface/hq-dashboard/app/api/config/whatsapp/route.js b/interface/hq-dashboard/app/api/config/whatsapp/route.js new file mode 100644 index 0000000..d0605f7 --- /dev/null +++ b/interface/hq-dashboard/app/api/config/whatsapp/route.js @@ -0,0 +1,36 @@ +import { NextResponse } from 'next/server'; +import fs from 'fs'; +import yaml from 'js-yaml'; + +const CONFIG_PATH = 'c:/Users/sunde/proxmox/agentic-os/agents/k8s/whatsapp-config.yaml'; + +export async function GET() { + try { + const fileContents = fs.readFileSync(CONFIG_PATH, 'utf8'); + const doc = yaml.load(fileContents); + return NextResponse.json({ success: true, data: doc.data }); + } catch (e) { + return NextResponse.json({ success: false, error: e.message }, { status: 500 }); + } +} + +export async function PUT(req) { + try { + const body = await req.json(); + const { defaultGroupUrl, tripGroupUrl } = body; + + const fileContents = fs.readFileSync(CONFIG_PATH, 'utf8'); + const doc = yaml.load(fileContents); + + if (!doc.data) doc.data = {}; + if (defaultGroupUrl) doc.data['default-group-url'] = defaultGroupUrl; + if (tripGroupUrl) doc.data['trip-group-url'] = tripGroupUrl; + + const newYaml = yaml.dump(doc, { lineWidth: -1 }); + fs.writeFileSync(CONFIG_PATH, newYaml, 'utf8'); + + return NextResponse.json({ success: true, data: doc.data }); + } catch (e) { + return NextResponse.json({ success: false, error: e.message }, { status: 500 }); + } +} diff --git a/interface/hq-dashboard/app/page.jsx b/interface/hq-dashboard/app/page.jsx index 2f48c8d..0e65126 100644 --- a/interface/hq-dashboard/app/page.jsx +++ b/interface/hq-dashboard/app/page.jsx @@ -98,9 +98,52 @@ export default function Page() { const [isSavingTrip, setIsSavingTrip] = useState(false); const [isTestingTrip, setIsTestingTrip] = useState(false); + // Config State + const [configData, setConfigData] = useState({ defaultGroupUrl: "", tripGroupUrl: "" }); + const [isSavingConfig, setIsSavingConfig] = useState(false); + const [configLoaded, setConfigLoaded] = useState(false); + const messagesEndRef = useRef(null); const terminalEndRef = useRef(null); + useEffect(() => { + if (activeTab === "config" && !configLoaded) { + fetch('/api/config/whatsapp') + .then(res => res.json()) + .then(data => { + if (data.success) { + setConfigData({ + defaultGroupUrl: data.data['default-group-url'] || "", + tripGroupUrl: data.data['trip-group-url'] || "" + }); + setConfigLoaded(true); + } + }) + .catch(console.error); + } + }, [activeTab, configLoaded]); + + const handleSaveConfig = async () => { + setIsSavingConfig(true); + try { + const res = await fetch('/api/config/whatsapp', { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(configData) + }); + const data = await res.json(); + if (data.success) { + alert("Configuration saved successfully to Kubernetes manifest!"); + } else { + alert("Failed to save: " + data.error); + } + } catch (e) { + alert("Error saving config: " + e.message); + } finally { + setIsSavingConfig(false); + } + }; + useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages, isClawTyping]); @@ -690,6 +733,20 @@ export default function Page() { Workflows & Logs + + @@ -1297,6 +1354,79 @@ export default function Page() { )} + {/* TAB: CONFIG */} + {activeTab === "config" && ( +
+
+
+ +

WhatsApp Routing Configuration

+
+ +

+ Manage the WhatsApp group invite links used by the various Agentic OS microservices. + Changes here are written directly to the Kubernetes whatsapp-config.yaml ConfigMap. +

+ +
+ {/* Default Group */} +
+ + setConfigData({...configData, defaultGroupUrl: e.target.value})} + placeholder="https://chat.whatsapp.com/..." + style={{ + width: "100%", padding: "12px", borderRadius: "8px", + background: "rgba(0,0,0,0.2)", border: "1px solid var(--border-color)", + color: "var(--text-primary)", fontSize: "14px", fontFamily: "monospace" + }} + /> + + Used by Reminder Service, Weather CronJob, and general notifications. + +
+ + {/* Trip Group */} +
+ + setConfigData({...configData, tripGroupUrl: e.target.value})} + placeholder="https://chat.whatsapp.com/..." + style={{ + width: "100%", padding: "12px", borderRadius: "8px", + background: "rgba(0,0,0,0.2)", border: "1px solid var(--border-color)", + color: "var(--text-primary)", fontSize: "14px", fontFamily: "monospace" + }} + /> + + Used exclusively by the Trip Service to isolate travel insights. + +
+ + {/* Save Button */} +
+ +
+
+
+
+ )} + {/* TAB 5: WORKFLOW MONITOR */} {activeTab === "logs" && (
diff --git a/interface/hq-dashboard/package-lock.json b/interface/hq-dashboard/package-lock.json index 12e5f36..adecfaa 100644 --- a/interface/hq-dashboard/package-lock.json +++ b/interface/hq-dashboard/package-lock.json @@ -6,6 +6,7 @@ "": { "name": "agentic-os-hq-dashboard", "dependencies": { + "js-yaml": "^4.2.0", "lucide-react": "^0.300.0", "next": "14.2.18", "react": "18.3.1", @@ -178,6 +179,12 @@ "tslib": "^2.4.0" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -227,6 +234,28 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "license": "MIT" }, + "node_modules/js-yaml": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", diff --git a/interface/hq-dashboard/package.json b/interface/hq-dashboard/package.json index c1d7b2b..4a6d2dd 100644 --- a/interface/hq-dashboard/package.json +++ b/interface/hq-dashboard/package.json @@ -7,9 +7,10 @@ "start": "next start" }, "dependencies": { + "js-yaml": "^4.2.0", + "lucide-react": "^0.300.0", "next": "14.2.18", "react": "18.3.1", - "react-dom": "18.3.1", - "lucide-react": "^0.300.0" + "react-dom": "18.3.1" } } diff --git a/trip-service/server.js b/trip-service/server.js index 28699db..fbb8f14 100644 --- a/trip-service/server.js +++ b/trip-service/server.js @@ -13,7 +13,7 @@ const DATA_FILE = process.env.DATA_FILE || '/data/trips.json'; const GATEWAY_URL = process.env.GATEWAY_URL || 'http://whatsapp-gateway.ai-agents.svc.cluster.local:5001/send-message'; const OLLAMA_URL = process.env.OLLAMA_URL || 'http://ollama.ai-agents.svc.cluster.local:11434/api/generate'; const PERSONAL_NUM = process.env.PERSONAL_NUM || '+14085505485'; -const GROUP_URL = process.env.GROUP_URL || 'https://chat.whatsapp.com/FXO0SCLA8DpJwClNNWdhhx'; +const GROUP_URL = process.env.GROUP_URL || 'https://chat.whatsapp.com/HN2z93yhXPyEJPuoib3dws'; const PORT = parseInt(process.env.PORT) || 6002; // ── Persistence ──────────────────────────────────────────────────────────────