pranav8tripathi@gmail.com commited on
Commit
222ab06
·
1 Parent(s): 3e291b7
.dockerignore ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ node_modules
2
+ npm-debug.log
3
+ build
4
+ .git
5
+ .gitignore
6
+ README.md
7
+ .DS_Store
8
+ .env
9
+ .env.local
10
+ .env.development.local
11
+ .env.test.local
12
+ .env.production.local
13
+ .vscode
14
+ .idea
15
+ *.md
Dockerfile ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Build stage
2
+ FROM node:18-alpine as build
3
+
4
+ # Set working directory
5
+ WORKDIR /app
6
+
7
+ # Copy package files
8
+ COPY package*.json ./
9
+
10
+ # Install dependencies
11
+ RUN npm install
12
+
13
+ # Copy source code
14
+ COPY . .
15
+
16
+ # Build the app
17
+ RUN npm run build
18
+
19
+ # Production stage
20
+ FROM node:18-alpine
21
+
22
+ WORKDIR /app
23
+
24
+ # Install http-server
25
+ RUN npm install -g http-server
26
+
27
+ # Copy built assets from build stage
28
+ COPY --from=build /app/build ./build
29
+
30
+ # Expose port 3000
31
+ EXPOSE 3000
32
+
33
+ # Start the app using http-server
34
+ CMD ["http-server", "build", "-p", "3000", "-a", "0.0.0.0"]
README.md CHANGED
@@ -1,81 +1,10 @@
1
  ---
2
- title: BizInsights Frontend
3
- emoji: 🐠
4
- colorFrom: indigo
5
- colorTo: red
6
- sdk: static
7
  pinned: false
8
- app_build_command: npm run build
9
- app_file: build/index.html
10
  ---
11
 
12
- # Getting Started with Create React App
13
-
14
- This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
15
-
16
- ## Available Scripts
17
-
18
- In the project directory, you can run:
19
-
20
- ### `npm start`
21
-
22
- Runs the app in the development mode.\
23
- Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
24
-
25
- The page will reload when you make changes.\
26
- You may also see any lint errors in the console.
27
-
28
- ### `npm test`
29
-
30
- Launches the test runner in the interactive watch mode.\
31
- See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
32
-
33
- ### `npm run build`
34
-
35
- Builds the app for production to the `build` folder.\
36
- It correctly bundles React in production mode and optimizes the build for the best performance.
37
-
38
- The build is minified and the filenames include the hashes.\
39
- Your app is ready to be deployed!
40
-
41
- See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
42
-
43
- ### `npm run eject`
44
-
45
- **Note: this is a one-way operation. Once you `eject`, you can't go back!**
46
-
47
- If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
48
-
49
- Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
50
-
51
- You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
52
-
53
- ## Learn More
54
-
55
- You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
56
-
57
- To learn React, check out the [React documentation](https://reactjs.org/).
58
-
59
- ### Code Splitting
60
-
61
- This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
62
-
63
- ### Analyzing the Bundle Size
64
-
65
- This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
66
-
67
- ### Making a Progressive Web App
68
-
69
- This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
70
-
71
- ### Advanced Configuration
72
-
73
- This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
74
-
75
- ### Deployment
76
-
77
- This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
78
-
79
- ### `npm run build` fails to minify
80
-
81
- This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
 
1
  ---
2
+ title: BizFrontend
3
+ emoji: 🦀
4
+ colorFrom: yellow
5
+ colorTo: yellow
6
+ sdk: docker
7
  pinned: false
 
 
8
  ---
9
 
10
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json CHANGED
@@ -1,20 +1,32 @@
1
  {
2
- "name": "react-template",
3
  "version": "0.1.0",
4
  "private": true,
5
  "dependencies": {
6
- "@testing-library/dom": "^10.4.0",
7
- "@testing-library/jest-dom": "^6.6.3",
8
- "@testing-library/react": "^16.3.0",
 
 
 
 
9
  "@testing-library/user-event": "^13.5.0",
10
- "react": "^19.1.0",
11
- "react-dom": "^19.1.0",
 
 
 
 
 
 
12
  "react-scripts": "5.0.1",
 
13
  "web-vitals": "^2.1.4"
14
  },
 
15
  "scripts": {
16
  "start": "react-scripts start",
17
- "build": "react-scripts build",
18
  "test": "react-scripts test",
19
  "eject": "react-scripts eject"
20
  },
 
1
  {
2
+ "name": "chat-interface",
3
  "version": "0.1.0",
4
  "private": true,
5
  "dependencies": {
6
+ "@emotion/react": "^11.11.1",
7
+ "@emotion/styled": "^11.11.0",
8
+ "@mui/icons-material": "^5.11.16",
9
+ "@mui/material": "^5.13.0",
10
+ "@testing-library/dom": "^9.3.1",
11
+ "@testing-library/jest-dom": "^5.16.5",
12
+ "@testing-library/react": "^13.4.0",
13
  "@testing-library/user-event": "^13.5.0",
14
+ "@types/jest": "^27.5.2",
15
+ "@types/jspdf": "^1.3.3",
16
+ "@types/node": "^16.18.38",
17
+ "@types/react": "^18.2.6",
18
+ "@types/react-dom": "^18.2.4",
19
+ "jspdf": "^3.0.3",
20
+ "react": "^18.2.0",
21
+ "react-dom": "^18.2.0",
22
  "react-scripts": "5.0.1",
23
+ "typescript": "^4.9.5",
24
  "web-vitals": "^2.1.4"
25
  },
26
+ "homepage": ".",
27
  "scripts": {
28
  "start": "react-scripts start",
29
+ "build": "GENERATE_SOURCEMAP=false INLINE_RUNTIME_CHUNK=false react-scripts build",
30
  "test": "react-scripts test",
31
  "eject": "react-scripts eject"
32
  },
public/index.html CHANGED
@@ -2,19 +2,19 @@
2
  <html lang="en">
3
  <head>
4
  <meta charset="utf-8" />
5
- <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
6
  <meta name="viewport" content="width=device-width, initial-scale=1" />
7
  <meta name="theme-color" content="#000000" />
8
  <meta
9
  name="description"
10
  content="Web site created using create-react-app"
11
  />
12
- <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
13
  <!--
14
  manifest.json provides metadata used when your web app is installed on a
15
  user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
16
  -->
17
- <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
18
  <!--
19
  Notice the use of %PUBLIC_URL% in the tags above.
20
  It will be replaced with the URL of the `public` folder during the build.
@@ -24,7 +24,7 @@
24
  work correctly both with client-side routing and a non-root public URL.
25
  Learn how to configure a non-root public URL by running `npm run build`.
26
  -->
27
- <title>React App</title>
28
  </head>
29
  <body>
30
  <noscript>You need to enable JavaScript to run this app.</noscript>
 
2
  <html lang="en">
3
  <head>
4
  <meta charset="utf-8" />
5
+ <link rel="icon" href="favicon.ico" />
6
  <meta name="viewport" content="width=device-width, initial-scale=1" />
7
  <meta name="theme-color" content="#000000" />
8
  <meta
9
  name="description"
10
  content="Web site created using create-react-app"
11
  />
12
+ <link rel="apple-touch-icon" href="logo192.png" />
13
  <!--
14
  manifest.json provides metadata used when your web app is installed on a
15
  user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
16
  -->
17
+ <link rel="manifest" href="manifest.json" />
18
  <!--
19
  Notice the use of %PUBLIC_URL% in the tags above.
20
  It will be replaced with the URL of the `public` folder during the build.
 
24
  work correctly both with client-side routing and a non-root public URL.
25
  Learn how to configure a non-root public URL by running `npm run build`.
26
  -->
27
+ <title>Agent Marketplace</title>
28
  </head>
29
  <body>
30
  <noscript>You need to enable JavaScript to run this app.</noscript>
public/static.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "root": "/",
3
+ "clean_urls": true,
4
+ "routes": {
5
+ "/**": "index.html"
6
+ },
7
+ "headers": {
8
+ "/**": {
9
+ "Cache-Control": "no-cache, no-store, must-revalidate",
10
+ "Pragma": "no-cache",
11
+ "Expires": "0"
12
+ }
13
+ }
14
+ }
src/.env ADDED
@@ -0,0 +1 @@
 
 
1
+ VITE_API_BASE_URL=http://localhost:8080
src/App.js DELETED
@@ -1,25 +0,0 @@
1
- import logo from './logo.svg';
2
- import './App.css';
3
-
4
- function App() {
5
- return (
6
- <div className="App">
7
- <header className="App-header">
8
- <img src={logo} className="App-logo" alt="logo" />
9
- <p>
10
- Edit <code>src/App.js</code> and save to reload.
11
- </p>
12
- <a
13
- className="App-link"
14
- href="https://reactjs.org"
15
- target="_blank"
16
- rel="noopener noreferrer"
17
- >
18
- Learn React
19
- </a>
20
- </header>
21
- </div>
22
- );
23
- }
24
-
25
- export default App;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/{App.test.js → App.test.tsx} RENAMED
@@ -1,3 +1,4 @@
 
1
  import { render, screen } from '@testing-library/react';
2
  import App from './App';
3
 
 
1
+ import React from 'react';
2
  import { render, screen } from '@testing-library/react';
3
  import App from './App';
4
 
src/App.tsx ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { AgentProvider, useAgent } from './contexts/AgentContext';
3
+ import ChatInterface from './components/ChatInterface';
4
+ import AgentSelector from './components/AgentSelector';
5
+ import { CssBaseline, ThemeProvider, createTheme } from '@mui/material';
6
+
7
+ const theme = createTheme({
8
+ palette: {
9
+ mode: 'light',
10
+ primary: {
11
+ main: '#7C3AED', // violet
12
+ light: '#A78BFA',
13
+ dark: '#5B21B6',
14
+ contrastText: '#ffffff',
15
+ },
16
+ secondary: {
17
+ main: '#06B6D4', // cyan
18
+ light: '#67E8F9',
19
+ dark: '#0E7490',
20
+ contrastText: '#003543',
21
+ },
22
+ background: {
23
+ default: '#f7f9fc',
24
+ paper: '#ffffff',
25
+ },
26
+ },
27
+ shape: {
28
+ borderRadius: 12,
29
+ },
30
+ typography: {
31
+ fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
32
+ },
33
+ });
34
+
35
+ function App() {
36
+ return (
37
+ <ThemeProvider theme={theme}>
38
+ <CssBaseline />
39
+ <AgentProvider>
40
+ <AppContent />
41
+ </AgentProvider>
42
+ </ThemeProvider>
43
+ );
44
+ }
45
+
46
+ // Separate component to use the useAgent hook
47
+ function AppContent() {
48
+ const { selectedAgent } = useAgent();
49
+
50
+ return (
51
+ <>
52
+ {selectedAgent ? (
53
+ <ChatInterface />
54
+ ) : (
55
+ <AgentSelector />
56
+ )}
57
+ </>
58
+ );
59
+ }
60
+
61
+ export default App;
src/components/AgentSelector.tsx ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { useAgent } from '../contexts/AgentContext';
3
+ import {
4
+ Container,
5
+ Typography,
6
+ Grid,
7
+ Card,
8
+ CardActionArea,
9
+ Box,
10
+ Chip,
11
+ Avatar,
12
+ } from '@mui/material';
13
+
14
+ const AgentSelector: React.FC = () => {
15
+ const { availableAgents, setSelectedAgent } = useAgent();
16
+
17
+ return (
18
+ <Box sx={{ minHeight: '100vh', bgcolor: 'background.default' }}>
19
+ {/* Hero Section */}
20
+ <Box
21
+ sx={{
22
+ py: { xs: 6, md: 10 },
23
+ px: 2,
24
+ background: 'linear-gradient(135deg, rgba(25,118,210,0.1) 0%, rgba(220,0,78,0.06) 100%)',
25
+ borderBottom: theme => `1px solid ${theme.palette.divider}`,
26
+ }}
27
+ >
28
+ <Container maxWidth="lg">
29
+ <Typography variant="h2" component="h1" align="center" gutterBottom sx={{ fontWeight: 700 }}>
30
+ Agent Marketplace
31
+ </Typography>
32
+ <Typography variant="h6" color="text.secondary" align="center" sx={{ maxWidth: 800, mx: 'auto' }}>
33
+ Your AI copilot for insights. Choose an agent to get started.
34
+ </Typography>
35
+ </Container>
36
+ </Box>
37
+
38
+ {/* Agents Grid */}
39
+ <Container maxWidth="lg" sx={{ mt: { xs: 3, md: 6 }, pb: 6 }}>
40
+ <Grid container spacing={4}>
41
+ {availableAgents.map((agent) => (
42
+ <Grid item xs={12} sm={6} md={4} key={agent.id}>
43
+ <Card
44
+ elevation={2}
45
+ sx={{
46
+ height: '100%',
47
+ display: 'flex',
48
+ flexDirection: 'column',
49
+ borderRadius: 3,
50
+ border: theme => `1px solid ${theme.palette.divider}`,
51
+ transition: 'transform 0.2s ease, box-shadow 0.2s ease',
52
+ position: 'relative',
53
+ '&:hover': {
54
+ transform: 'translateY(-6px)',
55
+ boxShadow: 8,
56
+ },
57
+ }}
58
+ >
59
+ {agent.upcoming && (
60
+ <Box sx={{ position: 'absolute', top: 12, right: 12 }}>
61
+ <Chip size="small" color="secondary" label="Upcoming" />
62
+ </Box>
63
+ )}
64
+
65
+ <CardActionArea
66
+ disabled={agent.upcoming}
67
+ onClick={() => !agent.upcoming && setSelectedAgent(agent)}
68
+ sx={{
69
+ height: '100%',
70
+ display: 'flex',
71
+ flexDirection: 'column',
72
+ alignItems: 'center',
73
+ textAlign: 'center',
74
+ p: 3,
75
+ opacity: agent.upcoming ? 0.7 : 1,
76
+ cursor: agent.upcoming ? 'not-allowed' : 'pointer',
77
+ }}
78
+ >
79
+ <Avatar
80
+ src={agent.avatar}
81
+ alt={agent.name}
82
+ sx={{
83
+ width: 88,
84
+ height: 88,
85
+ mb: 2,
86
+ border: '3px solid',
87
+ borderColor: 'primary.main',
88
+ boxShadow: 2,
89
+ }}
90
+ />
91
+ <Typography variant="h6" component="div" gutterBottom>
92
+ {agent.name}
93
+ </Typography>
94
+ <Typography variant="body2" color="text.secondary">
95
+ {agent.description}
96
+ </Typography>
97
+ </CardActionArea>
98
+ </Card>
99
+ </Grid>
100
+ ))}
101
+ </Grid>
102
+ </Container>
103
+ </Box>
104
+ );
105
+ };
106
+
107
+ export default AgentSelector;
src/components/ChatInterface.tsx ADDED
@@ -0,0 +1,284 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { useAgent } from '../contexts/AgentContext';
3
+ import ReportRenderer from './ReportRenderer';
4
+ import {
5
+ Box,
6
+ Typography,
7
+ IconButton,
8
+ Avatar,
9
+ Paper,
10
+ TextField,
11
+ InputAdornment,
12
+ List,
13
+ ListItem,
14
+ ListItemAvatar,
15
+ AppBar,
16
+ Toolbar,
17
+ Chip,
18
+ } from '@mui/material';
19
+ import SendIcon from '@mui/icons-material/Send';
20
+ import ArrowBackIcon from '@mui/icons-material/ArrowBack';
21
+
22
+ const ChatInterface: React.FC = () => {
23
+ const { selectedAgent, setSelectedAgent } = useAgent();
24
+ type QuickReplyButton = { title: string; payload: string };
25
+ type CustomMultiSelect = {
26
+ type: 'multi_select_chips';
27
+ options: string[];
28
+ columns?: number;
29
+ hint?: string;
30
+ selections?: string[]; // maintained client-side
31
+ };
32
+ type ChatMessage = {
33
+ sender: 'user' | 'bot';
34
+ text?: string;
35
+ buttons?: QuickReplyButton[];
36
+ custom?: CustomMultiSelect;
37
+ };
38
+
39
+ const [messages, setMessages] = useState<ChatMessage[]>([]);
40
+ const [inputValue, setInputValue] = useState('');
41
+ const [isLoading, setIsLoading] = useState(false);
42
+ const messagesEndRef = useRef<HTMLDivElement>(null);
43
+ const chatContainerRef = useRef<HTMLDivElement>(null);
44
+
45
+ const sendMessage = async (outgoing: string) => {
46
+ if (!outgoing.trim()) return;
47
+ setIsLoading(true);
48
+ try {
49
+ console.log('[DEBUG] Sending message to Rasa REST webhook...', outgoing);
50
+ const res = await fetch(`http://localhost:5005/webhooks/rest/webhook`, {
51
+ method: 'POST',
52
+ headers: { 'Content-Type': 'application/json' },
53
+ body: JSON.stringify({ sender: 'web-user', message: outgoing }),
54
+ });
55
+
56
+ if (!res.ok) throw new Error(`Backend error: ${res.status} ${res.statusText}`);
57
+ const data = await res.json();
58
+ console.log('[DEBUG] Rasa response:', data);
59
+
60
+ // Map Rasa messages to our ChatMessage structure (support text + buttons + custom multi-select)
61
+ const botMessages: ChatMessage[] = Array.isArray(data)
62
+ ? data
63
+ .map((m: any): ChatMessage | null => {
64
+ const text = typeof m?.text === 'string' ? m.text : undefined;
65
+ const buttons = Array.isArray(m?.buttons)
66
+ ? m.buttons
67
+ .filter((b: any) => typeof b?.title === 'string' && typeof b?.payload === 'string')
68
+ .map((b: any) => ({ title: b.title as string, payload: b.payload as string }))
69
+ : undefined;
70
+ let custom: ChatMessage['custom'] = undefined;
71
+ if (m?.custom && typeof m.custom === 'object' && m.custom.type === 'multi_select_chips') {
72
+ const opts = Array.isArray(m.custom.options) ? m.custom.options.filter((o: any) => typeof o === 'string') : [];
73
+ custom = {
74
+ type: 'multi_select_chips',
75
+ options: opts,
76
+ columns: typeof m.custom.columns === 'number' ? m.custom.columns : undefined,
77
+ hint: typeof m.custom.hint === 'string' ? m.custom.hint : undefined,
78
+ selections: [],
79
+ };
80
+ }
81
+ if (!text && (!buttons || buttons.length === 0) && !custom) return null;
82
+ return { sender: 'bot', text, buttons, custom } as ChatMessage;
83
+ })
84
+ .filter(Boolean) as ChatMessage[]
85
+ : [];
86
+
87
+ if (botMessages.length > 0) {
88
+ setMessages(prev => [...prev, ...botMessages]);
89
+ }
90
+ } catch (error) {
91
+ console.error('[DEBUG] sendMessage error:', error);
92
+ setMessages(prev => [...prev, { text: '❌ Something went wrong.', sender: 'bot' }]);
93
+ } finally {
94
+ setIsLoading(false);
95
+ }
96
+ };
97
+
98
+ const handleSendMessage = async (e: React.FormEvent) => {
99
+ e.preventDefault();
100
+ if (!inputValue.trim()) return;
101
+ const userMessage = inputValue;
102
+ setMessages(prev => [...prev, { text: userMessage, sender: 'user' }]);
103
+ setInputValue('');
104
+ await sendMessage(userMessage);
105
+ };
106
+
107
+ const handleQuickReply = async (btn: QuickReplyButton) => {
108
+ // If this is a Continue button after a multi-select, append selections
109
+ const lastMultiIndex = [...messages]
110
+ .map((m, idx) => ({ m, idx }))
111
+ .reverse()
112
+ .find(item => item.m.custom?.type === 'multi_select_chips')?.idx;
113
+
114
+ const lastMulti = lastMultiIndex !== undefined ? messages[lastMultiIndex] : undefined;
115
+ const selections = lastMulti?.custom?.selections ?? [];
116
+
117
+ if (btn.payload.startsWith('/')) {
118
+ if (selections.length > 0 && btn.payload.includes('continue')) {
119
+ // Send Rasa intent payload with inline JSON entity containing selections
120
+ const payloadWithSelections = `${btn.payload}${JSON.stringify({ selections })}`;
121
+ setMessages(prev => [
122
+ ...prev,
123
+ { text: `${btn.title}\n${selections.join(', ')}`, sender: 'user' },
124
+ ]);
125
+ await sendMessage(payloadWithSelections);
126
+ return;
127
+ }
128
+ }
129
+
130
+ // Default behavior: show the button title as user message, send payload
131
+ setMessages(prev => [...prev, { text: btn.title, sender: 'user' }]);
132
+ await sendMessage(btn.payload);
133
+ };
134
+
135
+ const toggleMultiSelect = (messageIndex: number, option: string) => {
136
+ setMessages(prev => {
137
+ const next = [...prev];
138
+ const msg = next[messageIndex];
139
+ if (!msg?.custom || msg.custom.type !== 'multi_select_chips') return prev;
140
+ const current = new Set(msg.custom.selections || []);
141
+ if (current.has(option)) {
142
+ current.delete(option);
143
+ } else {
144
+ current.add(option);
145
+ }
146
+ msg.custom = { ...msg.custom, selections: Array.from(current) };
147
+ next[messageIndex] = { ...msg };
148
+ return next;
149
+ });
150
+ };
151
+ const scrollToBottom = () => messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
152
+ useEffect(() => { scrollToBottom(); }, [messages, isLoading]);
153
+
154
+ if (!selectedAgent) return null;
155
+
156
+ return (
157
+ <Box sx={{ display: 'flex', height: '100vh', justifyContent: 'center', alignItems: 'center', bgcolor: 'background.default', p: { xs: 1.5, md: 2 } }}>
158
+ <Paper elevation={4} sx={{ width: '100%', maxWidth: 900, height: { xs: '90vh', md: '85vh' }, display: 'flex', flexDirection: 'column', borderRadius: 4, overflow: 'hidden' }}>
159
+ <AppBar position="static" color="transparent" elevation={0} sx={{ borderBottom: theme => `1px solid ${theme.palette.divider}`, bgcolor: 'background.paper' }}>
160
+ <Toolbar sx={{ gap: 1 }}>
161
+ <IconButton edge="start" onClick={() => setSelectedAgent(null)} sx={{ mr: 1 }}>
162
+ <ArrowBackIcon />
163
+ </IconButton>
164
+ <Avatar src={selectedAgent.avatar} alt={selectedAgent.name} sx={{ width: 32, height: 32, mr: 2 }} />
165
+ <Typography variant="h6" sx={{ flexGrow: 1 }}>{selectedAgent.name}</Typography>
166
+ </Toolbar>
167
+ </AppBar>
168
+
169
+ <Box ref={chatContainerRef} sx={{ flex: 1, overflowY: 'auto', p: { xs: 1.5, md: 2 }, bgcolor: 'background.paper' }}>
170
+ <List>
171
+ {messages.map((msg, i) => (
172
+ <ListItem key={i} sx={{ justifyContent: msg.sender === 'user' ? 'flex-end' : 'flex-start', alignItems: 'flex-start' }}>
173
+ {msg.sender === 'bot' && <ListItemAvatar><Avatar src={selectedAgent.avatar} /></ListItemAvatar>}
174
+ <Paper sx={{
175
+ p: 1.25,
176
+ px: 1.5,
177
+ maxWidth: '70%',
178
+ bgcolor: 'grey.100',
179
+ color: 'text.primary',
180
+ borderRadius: 3,
181
+ boxShadow: 1,
182
+ }}>
183
+ {msg.text && (
184
+ <ReportRenderer content={msg.text} isBotMessage={msg.sender === 'bot'} />
185
+ )}
186
+ {msg.custom?.type === 'multi_select_chips' && (
187
+ <Box sx={{ mt: msg.text ? 1 : 0 }}>
188
+ {msg.custom.hint && (
189
+ <Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
190
+ {msg.custom.hint}
191
+ </Typography>
192
+ )}
193
+ <Box
194
+ sx={{
195
+ display: 'flex',
196
+ flexWrap: 'wrap',
197
+ gap: 1,
198
+ // Each chip takes equal width per column if columns provided
199
+ '& > .multi-chip': {
200
+ flex: msg.custom.columns ? `0 0 ${100 / msg.custom.columns}%` : '0 0 auto',
201
+ },
202
+ }}
203
+ >
204
+ {msg.custom.options.map((opt) => {
205
+ const selected = !!msg.custom?.selections?.includes(opt);
206
+ return (
207
+ <Box key={opt} className="multi-chip">
208
+ <Chip
209
+ label={opt}
210
+ color={selected ? 'primary' : 'default'}
211
+ variant={selected ? 'filled' : 'outlined'}
212
+ onClick={() => toggleMultiSelect(i, opt)}
213
+ sx={{ borderRadius: 999, width: '100%' }}
214
+ />
215
+ </Box>
216
+ );
217
+ })}
218
+ </Box>
219
+ </Box>
220
+ )}
221
+ {msg.buttons && msg.buttons.length > 0 && (
222
+ <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, mt: msg.text ? 1 : 0 }}>
223
+ {msg.buttons.map((b, idx) => (
224
+ <Chip
225
+ key={idx}
226
+ label={b.title}
227
+ color="primary"
228
+ variant="outlined"
229
+ onClick={() => handleQuickReply(b)}
230
+ sx={{ borderRadius: 999 }}
231
+ />
232
+ ))}
233
+ </Box>
234
+ )}
235
+ </Paper>
236
+ </ListItem>
237
+ ))}
238
+ {isLoading && (
239
+ <ListItem>
240
+ <ListItemAvatar><Avatar src={selectedAgent.avatar} /></ListItemAvatar>
241
+ <Paper sx={{ p: 1.25, px: 1.5, bgcolor: 'grey.100', color: 'text.primary', borderRadius: 3 }}>
242
+ <Typography>Typing...</Typography>
243
+ </Paper>
244
+ </ListItem>
245
+ )}
246
+ <div ref={messagesEndRef} />
247
+ </List>
248
+ </Box>
249
+
250
+ <Box component="form" onSubmit={handleSendMessage} sx={{ display: 'flex', gap: 1, p: 1.5, bgcolor: 'background.paper', borderTop: theme => `1px solid ${theme.palette.divider}` }}>
251
+ <TextField
252
+ fullWidth
253
+ variant="outlined"
254
+ placeholder="Type a message..."
255
+ value={inputValue}
256
+ onChange={(e) => setInputValue(e.target.value)}
257
+ onKeyDown={(e) => {
258
+ if (e.key === 'Enter' && !e.altKey) {
259
+ e.preventDefault();
260
+ handleSendMessage(e);
261
+ } else if (e.key === 'Enter' && e.altKey) {
262
+ return;
263
+ }
264
+ }}
265
+ multiline
266
+ maxRows={4}
267
+ InputProps={{
268
+ sx: { borderRadius: 999, bgcolor: 'background.default' },
269
+ endAdornment: (
270
+ <InputAdornment position="end">
271
+ <IconButton type="submit" color="primary" disabled={!inputValue.trim()} sx={{ bgcolor: (theme) => (!inputValue.trim() ? 'transparent' : theme.palette.primary.light + '33'), ':hover': { bgcolor: (theme) => theme.palette.primary.light + '55' } }}>
272
+ <SendIcon />
273
+ </IconButton>
274
+ </InputAdornment>
275
+ ),
276
+ }}
277
+ />
278
+ </Box>
279
+ </Paper>
280
+ </Box>
281
+ );
282
+ };
283
+
284
+ export default ChatInterface;
src/components/ReportRenderer.tsx ADDED
@@ -0,0 +1,389 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import {
3
+ Box,
4
+ Typography,
5
+ Card,
6
+ CardContent,
7
+ Chip,
8
+ List,
9
+ ListItem,
10
+ ListItemText,
11
+ Link,
12
+ Table,
13
+ TableBody,
14
+ TableCell,
15
+ TableContainer,
16
+ TableHead,
17
+ TableRow,
18
+ IconButton,
19
+ Collapse,
20
+ } from '@mui/material';
21
+ import {
22
+ ExpandMore,
23
+ ExpandLess,
24
+ Code as CodeIcon,
25
+ Link as LinkIcon,
26
+ TableChart as TableIcon,
27
+ } from '@mui/icons-material';
28
+
29
+ interface ResponseRendererProps {
30
+ content: string;
31
+ isBotMessage?: boolean;
32
+ }
33
+
34
+ const ResponseRenderer: React.FC<ResponseRendererProps> = ({ content, isBotMessage = true }) => {
35
+ const [expandedSections, setExpandedSections] = React.useState<{ [key: string]: boolean }>({});
36
+
37
+ const toggleSection = (sectionId: string) => {
38
+ setExpandedSections(prev => ({
39
+ ...prev,
40
+ [sectionId]: !prev[sectionId]
41
+ }));
42
+ };
43
+
44
+ // Parse different content types
45
+ const parseContent = (text: string) => {
46
+ const sections: Array<{
47
+ type: 'header' | 'subheader' | 'text' | 'list' | 'code' | 'table' | 'link' | 'metric' | 'highlight';
48
+ content?: string;
49
+ level?: number;
50
+ items?: string[];
51
+ language?: string;
52
+ headers?: string[];
53
+ rows?: string[][];
54
+ url?: string;
55
+ title?: string;
56
+ }> = [];
57
+
58
+ const lines = text.split('\n');
59
+
60
+ for (let i = 0; i < lines.length; i++) {
61
+ const line = lines[i].trim();
62
+
63
+ if (!line) {
64
+ sections.push({ type: 'text', content: '' });
65
+ continue;
66
+ }
67
+
68
+ // Headers
69
+ if (line.startsWith('# ')) {
70
+ sections.push({ type: 'header', content: line.substring(2), level: 1 });
71
+ } else if (line.startsWith('## ')) {
72
+ sections.push({ type: 'subheader', content: line.substring(3), level: 2 });
73
+ } else if (line.startsWith('### ')) {
74
+ sections.push({ type: 'subheader', content: line.substring(4), level: 3 });
75
+ }
76
+ // Bold text as highlights
77
+ else if (line.startsWith('**') && line.endsWith('**')) {
78
+ sections.push({ type: 'highlight', content: line.substring(2, line.length - 2) });
79
+ }
80
+ // Lists
81
+ else if (line.startsWith('• ') || line.startsWith('- ') || line.startsWith('* ')) {
82
+ const listItems = [line.substring(2)];
83
+ let j = i + 1;
84
+ while (j < lines.length && (lines[j].trim().startsWith('• ') || lines[j].trim().startsWith('- ') || lines[j].trim().startsWith('* '))) {
85
+ listItems.push(lines[j].trim().substring(2));
86
+ j++;
87
+ }
88
+ i = j - 1; // Skip processed lines
89
+ sections.push({ type: 'list', content: '', items: listItems });
90
+ }
91
+ // Code blocks
92
+ else if (line.startsWith('```')) {
93
+ const language = line.substring(3).trim();
94
+ let codeContent = '';
95
+ let j = i + 1;
96
+ while (j < lines.length && !lines[j].trim().startsWith('```')) {
97
+ codeContent += lines[j] + '\n';
98
+ j++;
99
+ }
100
+ i = j; // Skip processed lines
101
+ sections.push({ type: 'code', content: codeContent.trim(), language });
102
+ }
103
+ // Table detection (simple format: | col1 | col2 |)
104
+ else if (line.includes('|') && lines[i + 1]?.includes('|') && lines[i + 1]?.includes('-')) {
105
+ const headers = line.split('|').map(h => h.trim()).filter(h => h);
106
+ let j = i + 2;
107
+ const rows: string[][] = [];
108
+ while (j < lines.length && lines[j].trim().includes('|')) {
109
+ const row = lines[j].split('|').map(cell => cell.trim()).filter(cell => cell);
110
+ if (row.length > 0) rows.push(row);
111
+ j++;
112
+ }
113
+ i = j - 1;
114
+ sections.push({ type: 'table', content: '', headers, rows });
115
+ }
116
+ // Links
117
+ else if (line.match(/https?:\/\/[^\s]+/)) {
118
+ const urlMatch = line.match(/(https?:\/\/[^\s]+)/);
119
+ if (urlMatch) {
120
+ const url = urlMatch[1];
121
+ const title = line.replace(url, '').trim();
122
+ sections.push({ type: 'link', content: title, url });
123
+ } else {
124
+ sections.push({ type: 'text', content: line });
125
+ }
126
+ }
127
+ // Key metrics (• Key: Value format)
128
+ else if (line.includes('•') && line.includes(':')) {
129
+ sections.push({ type: 'metric', content: line });
130
+ }
131
+ // Regular text
132
+ else {
133
+ sections.push({ type: 'text', content: line });
134
+ }
135
+ }
136
+
137
+ return sections;
138
+ };
139
+
140
+ const sections = parseContent(content);
141
+
142
+ const renderSection = (section: any, index: number) => {
143
+ const sectionId = `section-${index}`;
144
+
145
+ switch (section.type) {
146
+ case 'header':
147
+ return (
148
+ <Box key={index} sx={{ mb: 3 }}>
149
+ <Typography
150
+ variant="h4"
151
+ component="h1"
152
+ sx={{
153
+ color: 'primary.main',
154
+ fontWeight: 'bold',
155
+ mb: 2,
156
+ borderBottom: '2px solid',
157
+ borderColor: 'primary.light',
158
+ pb: 1
159
+ }}
160
+ >
161
+ {section.content}
162
+ </Typography>
163
+ </Box>
164
+ );
165
+
166
+ case 'subheader':
167
+ return (
168
+ <Box key={index} sx={{ mb: 2 }}>
169
+ <Typography
170
+ variant={section.level === 2 ? "h5" : "h6"}
171
+ component="h2"
172
+ sx={{
173
+ color: 'secondary.main',
174
+ fontWeight: '600',
175
+ mb: 1.5,
176
+ display: 'flex',
177
+ alignItems: 'center',
178
+ gap: 1
179
+ }}
180
+ >
181
+ <Box sx={{
182
+ width: section.level === 2 ? 4 : 3,
183
+ height: section.level === 2 ? 4 : 3,
184
+ bgcolor: 'primary.main',
185
+ borderRadius: '50%'
186
+ }} />
187
+ {section.content}
188
+ </Typography>
189
+ </Box>
190
+ );
191
+
192
+ case 'highlight':
193
+ return (
194
+ <Box key={index} sx={{ mb: 2 }}>
195
+ <Chip
196
+ label={section.content}
197
+ color="primary"
198
+ variant="filled"
199
+ sx={{
200
+ fontSize: '0.9rem',
201
+ fontWeight: 'bold',
202
+ mb: 1,
203
+ '& .MuiChip-label': {
204
+ px: 2,
205
+ py: 1
206
+ }
207
+ }}
208
+ />
209
+ </Box>
210
+ );
211
+
212
+ case 'list':
213
+ return (
214
+ <Card key={index} sx={{ mb: 2, bgcolor: 'background.paper' }}>
215
+ <CardContent sx={{ p: 2 }}>
216
+ <List dense>
217
+ {section.items.map((item: string, itemIndex: number) => (
218
+ <ListItem key={itemIndex} sx={{ px: 0, py: 0.5 }}>
219
+ <Box sx={{
220
+ width: 6,
221
+ height: 6,
222
+ bgcolor: 'primary.main',
223
+ borderRadius: '50%',
224
+ mr: 2,
225
+ mt: 0.5
226
+ }} />
227
+ <ListItemText
228
+ primary={item}
229
+ sx={{
230
+ '& .MuiListItemText-primary': {
231
+ fontSize: '0.9rem',
232
+ lineHeight: 1.4
233
+ }
234
+ }}
235
+ />
236
+ </ListItem>
237
+ ))}
238
+ </List>
239
+ </CardContent>
240
+ </Card>
241
+ );
242
+
243
+ case 'code':
244
+ const isExpanded = expandedSections[sectionId];
245
+ const shouldShowExpand = section.content.length > 200;
246
+
247
+ return (
248
+ <Card key={index} sx={{ mb: 2, bgcolor: 'grey.900', color: 'common.white' }}>
249
+ <CardContent sx={{ p: 2 }}>
250
+ <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
251
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
252
+ <CodeIcon sx={{ fontSize: 18, color: 'primary.main' }} />
253
+ <Typography variant="caption" sx={{ color: 'grey.400' }}>
254
+ {section.language || 'Code'}
255
+ </Typography>
256
+ </Box>
257
+ {shouldShowExpand && (
258
+ <IconButton
259
+ size="small"
260
+ onClick={() => toggleSection(sectionId)}
261
+ sx={{ color: 'grey.400' }}
262
+ >
263
+ {isExpanded ? <ExpandLess /> : <ExpandMore />}
264
+ </IconButton>
265
+ )}
266
+ </Box>
267
+ <Collapse in={isExpanded || !shouldShowExpand}>
268
+ <Box sx={{
269
+ bgcolor: 'grey.800',
270
+ p: 1.5,
271
+ borderRadius: 1,
272
+ fontFamily: 'monospace',
273
+ fontSize: '0.8rem',
274
+ overflow: 'auto',
275
+ maxHeight: isExpanded ? 'none' : '150px'
276
+ }}>
277
+ <pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
278
+ {section.content}
279
+ </pre>
280
+ </Box>
281
+ </Collapse>
282
+ </CardContent>
283
+ </Card>
284
+ );
285
+
286
+ case 'table':
287
+ return (
288
+ <Card key={index} sx={{ mb: 2 }}>
289
+ <CardContent sx={{ p: 0 }}>
290
+ <Box sx={{ display: 'flex', alignItems: 'center', p: 2, borderBottom: '1px solid', borderColor: 'divider' }}>
291
+ <TableIcon sx={{ mr: 1, color: 'primary.main' }} />
292
+ <Typography variant="subtitle2">Data Table</Typography>
293
+ </Box>
294
+ <TableContainer>
295
+ <Table size="small">
296
+ <TableHead>
297
+ <TableRow sx={{ bgcolor: 'primary.light' }}>
298
+ {section.headers.map((header: string, headerIndex: number) => (
299
+ <TableCell key={headerIndex} sx={{ fontWeight: 'bold', color: 'primary.contrastText' }}>
300
+ {header}
301
+ </TableCell>
302
+ ))}
303
+ </TableRow>
304
+ </TableHead>
305
+ <TableBody>
306
+ {section.rows.map((row: string[], rowIndex: number) => (
307
+ <TableRow key={rowIndex} sx={{ '&:nth-of-type(odd)': { bgcolor: 'action.hover' } }}>
308
+ {row.map((cell: string, cellIndex: number) => (
309
+ <TableCell key={cellIndex}>{cell}</TableCell>
310
+ ))}
311
+ </TableRow>
312
+ ))}
313
+ </TableBody>
314
+ </Table>
315
+ </TableContainer>
316
+ </CardContent>
317
+ </Card>
318
+ );
319
+
320
+ case 'link':
321
+ return (
322
+ <Box key={index} sx={{ mb: 2 }}>
323
+ <Link
324
+ href={section.url}
325
+ target="_blank"
326
+ rel="noopener noreferrer"
327
+ sx={{
328
+ display: 'inline-flex',
329
+ alignItems: 'center',
330
+ gap: 1,
331
+ color: 'primary.main',
332
+ textDecoration: 'none',
333
+ '&:hover': {
334
+ textDecoration: 'underline'
335
+ }
336
+ }}
337
+ >
338
+ <LinkIcon sx={{ fontSize: 16 }} />
339
+ {section.content || section.url}
340
+ </Link>
341
+ </Box>
342
+ );
343
+
344
+ case 'metric':
345
+ const [label, value] = section.content.split(':').map((s: string) => s.trim());
346
+ return (
347
+ <Card key={index} sx={{ mb: 2, bgcolor: 'success.light', color: 'success.contrastText' }}>
348
+ <CardContent sx={{ p: 2 }}>
349
+ <Typography variant="body2" sx={{ mb: 0.5, opacity: 0.8 }}>
350
+ {label.replace('•', '').trim()}
351
+ </Typography>
352
+ <Typography variant="h6" sx={{ fontWeight: 'bold' }}>
353
+ {value}
354
+ </Typography>
355
+ </CardContent>
356
+ </Card>
357
+ );
358
+
359
+ case 'text':
360
+ default:
361
+ return section.content ? (
362
+ <Typography
363
+ key={index}
364
+ variant="body1"
365
+ sx={{
366
+ mb: 2,
367
+ lineHeight: 1.6,
368
+ color: isBotMessage ? 'text.primary' : 'text.secondary'
369
+ }}
370
+ >
371
+ {section.content}
372
+ </Typography>
373
+ ) : null;
374
+ }
375
+ };
376
+
377
+ return (
378
+ <Box sx={{
379
+ maxWidth: '100%',
380
+ '& > *': {
381
+ mb: 1.5
382
+ }
383
+ }}>
384
+ {sections.map((section: any, index: number) => renderSection(section, index))}
385
+ </Box>
386
+ );
387
+ };
388
+
389
+ export default ResponseRenderer;
src/contexts/AgentContext.tsx ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { createContext, useContext, useState, ReactNode } from 'react';
2
+
3
+ export type Agent = {
4
+ id: string;
5
+ name: string;
6
+ description?: string;
7
+ avatar?: string;
8
+ upcoming?: boolean;
9
+ };
10
+
11
+ type AgentContextValue = {
12
+ selectedAgent: Agent | null;
13
+ setSelectedAgent: (a: Agent | null) => void;
14
+ availableAgents: Agent[]; // Add availableAgents to the context type
15
+ };
16
+ const AgentContext = createContext<AgentContextValue | undefined>(undefined);
17
+
18
+ export const AgentProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
19
+ // Define your agents list
20
+ const [availableAgents] = useState<Agent[]>([
21
+ {
22
+ id: 'agent-grapher',
23
+ name: 'Agent Grapher',
24
+ description: 'Advanced data visualization and analysis assistant',
25
+ avatar: 'https://api.iconify.design/mdi/chart-line.svg?color=%237C3AED',
26
+ },
27
+ {
28
+ id: 'rival-lens',
29
+ name: 'Bizinsight',
30
+ description: 'Business insights and competitive analysis bot',
31
+ avatar: 'https://api.iconify.design/mdi/brain.svg?color=%2306B6D4',
32
+ },
33
+ {
34
+ id: 'lexa-legal',
35
+ name: 'Lexa (Legal Assistant)',
36
+ description: 'Legal Assistant',
37
+ avatar: 'https://api.iconify.design/mdi/scale-balance.svg?color=%235B21B6',
38
+ upcoming: true,
39
+ }
40
+ ]);
41
+
42
+ const [selectedAgent, setSelectedAgent] = useState<Agent | null>(null);
43
+
44
+ return (
45
+ <AgentContext.Provider
46
+ value={{
47
+ selectedAgent,
48
+ setSelectedAgent,
49
+ availableAgents // Include availableAgents in the context value
50
+ }}
51
+ >
52
+ {children}
53
+ </AgentContext.Provider>
54
+ );
55
+ };
56
+
57
+ export const useAgent = (): AgentContextValue => {
58
+ const ctx = useContext(AgentContext);
59
+ if (!ctx) {
60
+ throw new Error('useAgent must be used within an AgentProvider');
61
+ }
62
+ return ctx;
63
+ };
src/{index.js → index.tsx} RENAMED
@@ -4,7 +4,9 @@ import './index.css';
4
  import App from './App';
5
  import reportWebVitals from './reportWebVitals';
6
 
7
- const root = ReactDOM.createRoot(document.getElementById('root'));
 
 
8
  root.render(
9
  <React.StrictMode>
10
  <App />
 
4
  import App from './App';
5
  import reportWebVitals from './reportWebVitals';
6
 
7
+ const root = ReactDOM.createRoot(
8
+ document.getElementById('root') as HTMLElement
9
+ );
10
  root.render(
11
  <React.StrictMode>
12
  <App />
src/react-app-env.d.ts ADDED
@@ -0,0 +1 @@
 
 
1
+ /// <reference types="react-scripts" />
src/{reportWebVitals.js → reportWebVitals.ts} RENAMED
@@ -1,4 +1,6 @@
1
- const reportWebVitals = onPerfEntry => {
 
 
2
  if (onPerfEntry && onPerfEntry instanceof Function) {
3
  import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4
  getCLS(onPerfEntry);
 
1
+ import { ReportHandler } from 'web-vitals';
2
+
3
+ const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4
  if (onPerfEntry && onPerfEntry instanceof Function) {
5
  import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6
  getCLS(onPerfEntry);
src/{setupTests.js → setupTests.ts} RENAMED
File without changes
tsconfig.json ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es5",
4
+ "lib": [
5
+ "dom",
6
+ "dom.iterable",
7
+ "esnext"
8
+ ],
9
+ "allowJs": true,
10
+ "skipLibCheck": true,
11
+ "esModuleInterop": true,
12
+ "allowSyntheticDefaultImports": true,
13
+ "strict": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "noFallthroughCasesInSwitch": true,
16
+ "module": "esnext",
17
+ "moduleResolution": "node",
18
+ "resolveJsonModule": true,
19
+ "isolatedModules": true,
20
+ "noEmit": true,
21
+ "jsx": "react-jsx"
22
+ },
23
+ "include": [
24
+ "src"
25
+ ]
26
+ }