feat: split AI Input Agent into Copy/Type Agent submenu with settings

Renames the sidebar entry to "AI Copy/Type Agent" with Copy Agent, Type
Agent, and Settings sub-pages. Settings shows the server's LAN IP
address (via a new /api/ai/network-info endpoint) since it changes
depending on which office the app is running in.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 16:40:24 -04:00
parent 802dbedbfb
commit 1190bc0494
6 changed files with 198 additions and 7 deletions

View File

@@ -1,5 +1,6 @@
import express, { Request, Response } from "express";
import fs from "fs";
import os from "os";
import path from "path";
import { storage } from "../storage";
import { classifyInternalChat } from "../ai/internal-chat-graph";
@@ -14,6 +15,24 @@ function getChatHistoryPath(userId: number): string {
const router = express.Router();
// GET /api/ai/network-info — local LAN IP address(es) of this server
router.get("/network-info", async (req: Request, res: Response): Promise<any> => {
try {
const interfaces = os.networkInterfaces();
const addresses: string[] = [];
for (const name of Object.keys(interfaces)) {
for (const iface of interfaces[name] ?? []) {
if (iface.family === "IPv4" && !iface.internal) {
addresses.push(iface.address);
}
}
}
return res.status(200).json({ ipAddress: addresses[0] ?? null, addresses });
} catch (err) {
return res.status(500).json({ error: "Failed to determine network info", details: String(err) });
}
});
// GET /api/ai/settings
router.get("/settings", async (req: Request, res: Response): Promise<any> => {
try {

View File

@@ -30,7 +30,9 @@ const CloudStoragePage = lazy(() => import("./pages/cloud-storage-page"));
const JobMonitorPage = lazy(() => import("./pages/job-monitor-page"));
const ChartPage = lazy(() => import("./pages/chart-page"));
const DentalEducationPage = lazy(() => import("./pages/dental-education-page"));
const AiInputAgentPage = lazy(() => import("./pages/ai-input-agent-page"));
const AiCopyAgentPage = lazy(() => import("./pages/ai-copy-agent-page"));
const AiTypeAgentPage = lazy(() => import("./pages/ai-type-agent-page"));
const AiAgentSettingsPage = lazy(() => import("./pages/ai-agent-settings-page"));
const DentalShoppingSearchTagPage = lazy(() => import("./pages/dental-shopping-search-tag-page"));
const DentalShoppingLoginInfoPage = lazy(() => import("./pages/dental-shopping-login-info-page"));
const ActivationPage = lazy(() => import("./pages/activation-page"));
@@ -67,7 +69,10 @@ function Router() {
<ProtectedRoute path="/reports" component={() => <ReportsPage />} />
<ProtectedRoute path="/cloud-storage" component={() => <CloudStoragePage />} />
<ProtectedRoute path="/dental-education" component={() => <DentalEducationPage />} />
<ProtectedRoute path="/ai-input-agent" component={() => <AiInputAgentPage />} />
<ProtectedRoute path="/ai-input-agent/copy-agent" component={() => <AiCopyAgentPage />} />
<ProtectedRoute path="/ai-input-agent/type-agent" component={() => <AiTypeAgentPage />} />
<ProtectedRoute path="/ai-input-agent/settings" component={() => <AiAgentSettingsPage />} />
<ProtectedRoute path="/ai-input-agent" component={() => <Redirect to="/ai-input-agent/copy-agent" />} />
<ProtectedRoute path="/dental-shopping/search-tag" component={() => <DentalShoppingSearchTagPage />} />
<ProtectedRoute path="/dental-shopping/login-info" component={() => <DentalShoppingLoginInfoPage />} />
<ProtectedRoute path="/activation" component={() => <ActivationPage />} adminOnly />

View File

@@ -35,6 +35,8 @@ import {
ShoppingCart,
Search,
KeyRound,
Copy,
Keyboard,
} from "lucide-react";
import { cn } from "@/lib/utils";
import { useMemo, useState, useEffect } from "react";
@@ -68,6 +70,7 @@ export function Sidebar() {
if (location.startsWith("/chart")) s.add("/chart");
if (location.startsWith("/settings")) s.add("/settings");
if (location.startsWith("/dental-shopping")) s.add("/dental-shopping");
if (location.startsWith("/ai-input-agent")) s.add("/ai-input-agent");
return s;
});
@@ -81,6 +84,9 @@ export function Sidebar() {
if (location.startsWith("/dental-shopping")) {
setExpandedPaths((prev) => new Set([...prev, "/dental-shopping"]));
}
if (location.startsWith("/ai-input-agent")) {
setExpandedPaths((prev) => new Set([...prev, "/ai-input-agent"]));
}
}, [location]);
const togglePath = (path: string) => {
@@ -177,9 +183,26 @@ export function Sidebar() {
icon: <GraduationCap className="h-5 w-5 text-lime-600" />,
},
{
name: "AI Input Agent",
name: "AI Copy/Type Agent",
path: "/ai-input-agent",
icon: <Bot className="h-5 w-5 text-violet-500" />,
children: [
{
name: "Copy Agent",
path: "/ai-input-agent/copy-agent",
icon: <Copy className="h-4 w-4 text-violet-400" />,
},
{
name: "Type Agent",
path: "/ai-input-agent/type-agent",
icon: <Keyboard className="h-4 w-4 text-violet-400" />,
},
{
name: "Settings",
path: "/ai-input-agent/settings",
icon: <Settings className="h-4 w-4 text-violet-400" />,
},
],
},
{
name: "AI Dental Shopping",

View File

@@ -0,0 +1,63 @@
import { useQuery } from "@tanstack/react-query";
import { apiRequest } from "@/lib/queryClient";
import { Settings as SettingsIcon, Network, Loader2 } from "lucide-react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
interface NetworkInfo {
ipAddress: string | null;
addresses: string[];
}
export default function AiAgentSettingsPage() {
const { data: networkInfo, isLoading } = useQuery<NetworkInfo>({
queryKey: ["/api/ai/network-info"],
queryFn: async () => {
const res = await apiRequest("GET", "/api/ai/network-info");
return res.json();
},
});
return (
<div className="max-w-3xl mx-auto px-4 py-10 space-y-6">
<div className="text-center space-y-3">
<div className="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-violet-100 mb-2">
<SettingsIcon className="h-8 w-8 text-violet-600" />
</div>
<h1 className="text-3xl font-bold tracking-tight">Copy/Type Agent Settings</h1>
<p className="text-muted-foreground text-base max-w-xl mx-auto">
Configuration options for the Copy Agent and Type Agent will appear here.
</p>
<span className="inline-block bg-amber-100 text-amber-700 text-xs font-semibold px-3 py-1 rounded-full border border-amber-200">
Coming Soon
</span>
</div>
<Card>
<CardHeader className="pb-2">
<CardTitle className="flex items-center gap-2 text-base">
<Network className="h-5 w-5 text-violet-500" />
IP Address
</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
{isLoading ? (
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Loader2 className="h-4 w-4 animate-spin" />
Detecting IP address...
</div>
) : networkInfo?.ipAddress ? (
<p className="text-2xl font-mono font-semibold tracking-wide">
{networkInfo.ipAddress}
</p>
) : (
<p className="text-sm text-muted-foreground">Unable to detect an IP address.</p>
)}
<p className="text-xs text-muted-foreground">
This is the app's current network address. It changes depending on which office
the app is running in.
</p>
</CardContent>
</Card>
</div>
);
}

View File

@@ -0,0 +1,81 @@
import { Copy, FileCheck, CreditCard, Shield, Zap } from "lucide-react";
import { Card, CardContent } from "@/components/ui/card";
const CAPABILITIES = [
{
icon: <Shield className="h-5 w-5 text-teal-600" />,
title: "Eligibility Results",
description:
"Automatically read insurance eligibility information — coverage status, plan details, deductibles, and co-pays — from the insurance portal.",
},
{
icon: <FileCheck className="h-5 w-5 text-blue-600" />,
title: "Claim Information",
description:
"Extract claim numbers, claim status updates, and denial reasons from the insurance portal so they can be reused elsewhere without manual re-entry.",
},
{
icon: <CreditCard className="h-5 w-5 text-indigo-600" />,
title: "Insurance Payments",
description:
"Capture ERA / EOB payment details — amounts, adjustments, patient responsibility — straight from the source document or portal.",
},
];
export default function AiCopyAgentPage() {
return (
<div className="max-w-3xl mx-auto px-4 py-10 space-y-10">
{/* Hero */}
<div className="text-center space-y-3">
<div className="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-violet-100 mb-2">
<Copy className="h-8 w-8 text-violet-600" />
</div>
<h1 className="text-3xl font-bold tracking-tight">Copy Agent</h1>
<p className="text-muted-foreground text-base max-w-xl mx-auto">
An AI agent reads and copies data including images and brings them to this app
automatically. No manual input required.
</p>
<span className="inline-block bg-amber-100 text-amber-700 text-xs font-semibold px-3 py-1 rounded-full border border-amber-200">
Coming Soon
</span>
</div>
{/* What it will copy */}
<div className="space-y-3">
<h2 className="text-base font-semibold">What it will copy</h2>
<div className="grid gap-4 sm:grid-cols-3">
{CAPABILITIES.map((cap) => (
<Card key={cap.title}>
<CardContent className="py-5 space-y-2">
<div className="flex items-center gap-2">
{cap.icon}
<span className="text-sm font-medium">{cap.title}</span>
</div>
<p className="text-xs text-muted-foreground leading-relaxed">
{cap.description}
</p>
</CardContent>
</Card>
))}
</div>
</div>
{/* Why */}
<Card className="border-violet-200 bg-violet-50/50">
<CardContent className="py-5 flex items-start gap-3">
<Zap className="h-5 w-5 text-violet-500 mt-0.5 flex-shrink-0" />
<div className="space-y-1">
<p className="text-sm font-medium text-violet-900">Why this matters</p>
<p className="text-sm text-violet-700 leading-relaxed">
Staff currently check eligibility or look up a claim on an insurance portal, then
manually copy every value into this app. This agent eliminates that step entirely
one click pulls the data straight in.
</p>
</div>
</CardContent>
</Card>
</div>
);
}

View File

@@ -1,4 +1,4 @@
import { Bot, Keyboard, FileCheck, CreditCard, Shield, Zap, ArrowRight } from "lucide-react";
import { Keyboard, FileCheck, CreditCard, Shield, Zap, ArrowRight } from "lucide-react";
import { Card, CardContent } from "@/components/ui/card";
const SOFTWARE_TARGETS = [
@@ -29,16 +29,16 @@ const CAPABILITIES = [
},
];
export default function AiInputAgentPage() {
export default function AiTypeAgentPage() {
return (
<div className="max-w-3xl mx-auto px-4 py-10 space-y-10">
{/* Hero */}
<div className="text-center space-y-3">
<div className="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-violet-100 mb-2">
<Bot className="h-8 w-8 text-violet-600" />
<Keyboard className="h-8 w-8 text-violet-600" />
</div>
<h1 className="text-3xl font-bold tracking-tight">AI Input Agent</h1>
<h1 className="text-3xl font-bold tracking-tight">Type Agent</h1>
<p className="text-muted-foreground text-base max-w-xl mx-auto">
An AI-powered agent that reads data from this app and types it directly into your
current dental management software no copy-paste, no manual re-entry.