Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Copy this file to server/.env (or export the variables) before running the Node service.
OPENAI_API_KEY=replace-with-your-openai-api-key
PORT=5001
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
node_modules/
client/node_modules/
server/node_modules/
dist/
client/dist/
server/dist/
.env
server/.env
.DS_Store
133 changes: 132 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,132 @@
# beta
# Insight Nexus CRM

Insight Nexus CRM is an immersive, local-first Vite + React command center for predictive revenue intelligence.
It fuses data management, forecasting, risk analysis, AI advisory, and deep comparison workflows into one cinematic
experience powered by your private OpenAI key.

## Architecture

```
root
├── client # Vite + React + Tailwind front-end (TypeScript)
└── server # Node/Express API broker that talks to OpenAI Responses
```

The UI runs on Vite (port 5173) and proxies API calls to the Node service (port 5001 by default). All OpenAI traffic
is handled server-side so your key never touches the browser.

## Features

- **Data fabric ops** – upload CRM CSV/JSON/TXT intelligence, track freshness, and surface AI-generated coverage maps.
- **Prediction studio** – craft multi-dimensional business scenarios, attach documents, and receive structured forecasts,
risk posture, CRM plays, and AI advisory in one response.
- **Risk command** – monitor Atlas-generated risk matrices, mitigation sprints, and telemetry streams in a premium glassmorphism UI.
- **AI advisor** – chat with Atlas, the revenue copilot, for executive-ready guidance that remembers context across turns.
- **Collaboration hub** – upload two intelligence files for deep delta analysis with suggested actions, automation plays, and executive summaries.

## Prerequisites

- Node.js 18+
- npm 9+
- An OpenAI API key with access to the Responses API (e.g., `gpt-4.1-mini`)

## Setup

### Windows quick-start

If you're on Windows 11, the repository includes helper scripts:

1. Double-click `install.bat` to verify Node 18+/npm 9+ are available and install server/client dependencies. The installer
validates versions, skips modules that already exist, and aborts with a clear message if the project structure cannot be found.
2. Copy `server/.env.example` to `server/.env` and set `OPENAI_API_KEY=<your key>`.
3. Double-click `run.bat` to launch both the Node API broker and Vite client in separate terminals. The runner re-validates
Node/npm, ensures dependencies remain installed, and reminds you if the `.env` file is missing. It reuses the installer when
`node_modules` folders are absent so you always land in a bootable state.

> **Tip:** If you previously ran the legacy Flask version of this project, stop any lingering Python processes before using the
> new Node/Vite stack to avoid port conflicts. Running `python app.py` now prints guidance that points you back to the current
> architecture.

### Manual setup

1. **Install dependencies**

```bash
cd server && npm install
cd ../client && npm install
```

2. **Configure environment variables**

```bash
cp server/.env.example server/.env
# edit server/.env and set OPENAI_API_KEY=<your key>
```

The server respects `PORT` (default `5001`). Ensure it matches the proxy target in `client/vite.config.ts` if changed.

3. **Run the Node API broker**

```bash
cd server
npm run dev
```

4. **Run the Vite client**

In a second terminal:

```bash
cd client
npm run dev
```

5. Navigate to [http://localhost:5173](http://localhost:5173) to experience the Insight Nexus CRM suite. The UI automatically
detects whether the server is returning live OpenAI intelligence or mock data.

### Auto-runner behaviour

- `run.bat` opens two dedicated terminals named **Insight Nexus CRM - API** and **Insight Nexus CRM - Client**. Close those
windows (or press `CTRL+C` inside each) to stop the services.
- The runner only calls `install.bat` when `node_modules` is missing, so you can re-launch instantly once dependencies are in place.
- Both scripts exit early with actionable guidance if prerequisites are not met, helping diagnose Windows environment issues quickly.

## Usage workflow

- Start in **Command Center** to review real-time telemetry and AI advisories.
- Head to **Data Management** to ingest supporting docs and monitor Atlas automations.
- Launch predictions in **Predictive Strategy**; attach up to four documents to enrich outcomes.
- Use **Risk Command** to understand threat vectors and mitigation plays.
- Chat with **AI Advisor** for curated actions, then compare intelligence files inside **Collaboration Hub** for deep delta insights.

## Mock mode vs. live mode

The Node API gracefully handles environments without an OpenAI key:

- When `OPENAI_API_KEY` is present, Atlas streams genuine Responses API intelligence.
- When the key is missing or an API error occurs, the server returns cinematic mock data so the UI, installer, and auto-runner
still demonstrate the full workflow.
- Each API response includes a `source` field (`openai` or `mock`) and `/health` advertises the current mode.

This makes it safe to explore the UI immediately after cloning, even before wiring up credentials.

## Testing & linting

- Front-end: `cd client && npm run lint`
- Server: `cd server && npm run lint`

Both projects are TypeScript/ESM aware. Adjust scripts or integrate additional tooling as needed.

## Security

- Never expose your OpenAI key to the browser. Keep it in `server/.env` or your preferred secret manager.
- Do not commit `.env` files. Keys listed here are placeholders only.
- Consider adding request authentication before deploying beyond localhost.

## Troubleshooting

- If you encounter `Unable to generate insights` or `Unable to compare documents`, confirm the server console is running and that
`OPENAI_API_KEY` is valid.
- Update the `model` in `server/src/index.js` to match the tier available to your account if `gpt-4.1-mini` is unavailable.

Enjoy orchestrating a next-generation CRM experience powered by Atlas — now with Windows automation and offline-ready mock data.
14 changes: 14 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""Legacy launcher notice for Insight Nexus CRM.

This project no longer runs on Flask. To start the new experience:

1. Install Node.js 18+ and npm 9+.
2. Run install.bat (Windows) or install dependencies manually (see README).
3. Use run.bat or the npm scripts to launch the Node API broker and Vite client.
"""

if __name__ == '__main__':
raise SystemExit(
'Insight Nexus CRM now uses a Node/Express API and Vite React UI. '
'Double-click run.bat (Windows) or follow the README to launch the new stack.'
)
21 changes: 21 additions & 0 deletions client/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: [
'eslint:recommended',
'plugin:react-hooks/recommended',
'plugin:react-refresh/recommended',
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: { jsx: true },
ecmaVersion: 'latest',
sourceType: 'module',
},
plugins: ['@typescript-eslint', 'react-refresh'],
rules: {
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
},
};
13 changes: 13 additions & 0 deletions client/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Insight Nexus CRM</title>
</head>
<body class="bg-canvas text-white">
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
44 changes: 44 additions & 0 deletions client/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"name": "insight-crm-client",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"lint": "eslint \"src/**/*.{ts,tsx}\""
},
"dependencies": {
"@radix-ui/react-avatar": "^1.1.0",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.1",
"axios": "^1.7.7",
"framer-motion": "^11.3.16",
"lucide-react": "^0.446.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.52.0",
"react-router-dom": "^6.26.2",
"recharts": "^2.10.4",
"zod": "^3.23.8",
"@hookform/resolvers": "^3.9.0",
"react-dropzone": "^14.2.3"
},
"devDependencies": {
"@types/node": "^22.7.4",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^8.9.0",
"@typescript-eslint/parser": "^8.9.0",
"autoprefixer": "^10.4.20",
"eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.11",
"postcss": "^8.4.47",
"tailwindcss": "^3.4.13",
"typescript": "^5.6.2",
"vite": "^5.4.10"
}
}
6 changes: 6 additions & 0 deletions client/postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
80 changes: 80 additions & 0 deletions client/src/components/ChatPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { FormEvent, useMemo, useState } from 'react';
import { motion } from 'framer-motion';
import { Bot, Send } from 'lucide-react';
import { useChat } from '../hooks/useChat';

export const ChatPanel = () => {
const [message, setMessage] = useState('');
const { history, sendMessage, loading } = useChat();

const reversedHistory = useMemo(() => [...history].reverse(), [history]);

const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (!message.trim()) return;
await sendMessage(message.trim());
setMessage('');
};

return (
<div className="flex h-[30rem] flex-col rounded-3xl border border-white/5 bg-black/40">
<div className="flex items-center gap-3 border-b border-white/5 px-6 py-4">
<span className="flex h-10 w-10 items-center justify-center rounded-full bg-accent/20 text-accent">
<Bot className="h-5 w-5" />
</span>
<div>
<p className="text-sm font-semibold text-white">Atlas Copilot</p>
<p className="text-xs text-slate-400">Strategic CRM advisor</p>
</div>
</div>
<div className="flex-1 space-y-3 overflow-y-auto p-6">
{reversedHistory.map((entry, index) => (
<motion.div
key={`${entry.role}-${index}`}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className={`flex ${entry.role === 'user' ? 'justify-end' : 'justify-start'}`}
>
<div
className={`max-w-[70%] rounded-2xl border px-4 py-3 text-sm leading-relaxed shadow-lg ${
entry.role === 'user'
? 'border-accent/40 bg-accent/20 text-white'
: 'border-white/10 bg-white/5 text-slate-100'
}`}
>
{entry.content}
</div>
</motion.div>
))}
{loading && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="flex justify-start"
>
<div className="rounded-2xl border border-white/10 bg-white/5 px-4 py-2 text-xs text-slate-300">
Atlas is synthesizing...
</div>
</motion.div>
)}
</div>
<form onSubmit={handleSubmit} className="border-t border-white/5 p-4">
<div className="flex items-center gap-3 rounded-full border border-white/10 bg-white/5 px-4 py-2">
<input
className="flex-1 bg-transparent text-sm text-white placeholder:text-slate-500 focus:outline-none"
placeholder="Request playbooks, mitigate risk, predict outcomes..."
value={message}
onChange={(event) => setMessage(event.target.value)}
/>
<button
type="submit"
disabled={loading}
className="flex h-10 w-10 items-center justify-center rounded-full bg-accent text-white transition hover:bg-accent/80 disabled:opacity-50"
>
<Send className="h-4 w-4" />
</button>
</div>
</form>
</div>
);
};
27 changes: 27 additions & 0 deletions client/src/components/GradientCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { PropsWithChildren, ReactNode } from 'react';

interface GradientCardProps extends PropsWithChildren {
title: string;
subtitle?: string;
icon?: ReactNode;
emphasis?: 'accent' | 'neon';
}

export const GradientCard = ({ title, subtitle, icon, emphasis = 'accent', children }: GradientCardProps) => (
<div
className={`gradient-border relative overflow-hidden rounded-3xl border border-white/5 bg-slate-950/60 p-7 shadow-xl shadow-black/20 backdrop-blur-2xl transition ${
emphasis === 'accent' ? 'hover:border-accent/60' : 'hover:border-neon/60'
}`}
>
<div className="relative z-10 space-y-4">
<div className="flex items-start justify-between gap-4">
<div>
<p className="text-xs uppercase tracking-[0.35em] text-slate-400">{subtitle}</p>
<h3 className="text-2xl font-semibold text-white">{title}</h3>
</div>
{icon && <span className="text-accent">{icon}</span>}
</div>
<div className="space-y-4 text-sm text-slate-200">{children}</div>
</div>
</div>
);
18 changes: 18 additions & 0 deletions client/src/components/Layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { PropsWithChildren } from 'react';
import { Navigation } from './Navigation';

export const Layout = ({ children }: PropsWithChildren) => (
<div className="min-h-screen grid grid-cols-[280px_1fr] bg-canvas text-slate-100">
<aside className="relative border-r border-white/5 bg-slate-950/70 backdrop-blur-xl">
<div className="absolute inset-x-0 top-0 h-px bg-gradient-to-r from-accent/20 via-neon/20 to-transparent" />
<Navigation />
</aside>
<main className="relative overflow-y-auto">
<div className="absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-accent/20 to-neon/20" />
<div className="relative px-10 pb-12 pt-16">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top_right,rgba(75,107,251,0.12),transparent_55%)]" />
<div className="relative z-10 space-y-12">{children}</div>
</div>
</main>
</div>
);
Loading