Architecture
This document explains the internal architecture of InAppAI React, helping you understand how the component works and make informed decisions when building with it.
Overview
InAppAI React is built on three core principles:
- Controlled Mode - Parent component manages all state
- Backend Separation - API keys and AI logic stay on the server
- Client-Side Tools - Tool handlers execute in the browser
Component Architecture
┌─────────────────────────────────────────────────┐
│ Your React Application │
│ │
│ ┌────────────────────────────────────────┐ │
│ │ <InAppAI /> │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ UI Layer │ │ State Layer │ │ │
│ │ │ │ │ │ │ │
│ │ │ • Button │ │ • messages │ │ │
│ │ │ • Window │ │ • isOpen │ │ │
│ │ │ • Messages │ │ • isLoading │ │ │
│ │ │ • Input │ │ • error │ │ │
│ │ └──────────────┘ └──────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────────┐ │ │
│ │ │ Tool Registry │ │ │
│ │ │ • Executes handlers locally │ │ │
│ │ │ • Returns results to backend │ │ │
│ │ └──────────────────────────────────┘ │ │
│ └────────────────────────────────────────┘ │
│ ↓ HTTP │
└──────────────────────┼─────────────────────────┘
↓
┌─────────────────────────┐
│ InAppAI Backend │
│ │
│ • Validates requests │
│ • Calls AI API │
│ • Manages context │
│ • Handles tool calls │
└─────────────────────────┘
↓
┌────────────────┐
│ AI Provider │
│ (OpenAI, etc) │
└────────────────┘
Data Flow
Message Send Flow
1. User types message and presses Enter
↓
2. Component creates Message object
↓
3. Calls onMessagesChange([...messages, userMessage])
↓
4. Parent updates state (re-renders with new message)
↓
5. Component sends HTTP POST to backend
↓
6. Backend forwards to AI API
↓
7. AI responds (with optional tool calls)
↓
8. If tool calls:
a. Component executes tool handlers locally
b. Sends results back to backend
c. Backend gets natural language response
↓
9. Component calls onMessagesChange([...messages, assistantMessage])
↓
10. Parent updates state (re-renders with AI response)
Controlled Mode
InAppAI React uses controlled mode exclusively:
// The component NEVER manages messages internally
// Parent must provide both messages and onChange
function App() {
const [messages, setMessages] = useState<Message[]>([]); // ← Parent owns state
return (
<InAppAI
messages={messages} // ← Props IN
onMessagesChange={setMessages} // ← Callback OUT
/>
);
}
Why controlled mode?
- You control persistence (localStorage, backend, etc.)
- You can transform messages before storing
- Enables multi-conversation apps
- Full control over state management
- Works with any state library (Redux, Zustand, etc.)
Tool Execution Model
Tools use a local-first execution model:
1. User: "Add a todo: Buy milk"
↓
2. Backend receives message + tool definitions
↓
3. AI decides to call 'addTodo' tool
↓
4. Backend returns: { toolCalls: [{ name: 'addTodo', params: { text: 'Buy milk' } }] }
↓
5. Component finds local handler for 'addTodo'
↓
6. Executes: handler({ text: 'Buy milk' })
↓
7. Handler updates React state: setTodos([...todos, newTodo])
↓
8. Handler returns: { success: true, todo: { id: 1, text: 'Buy milk' } }
↓
9. Component sends result back to backend
↓
10. Backend asks AI to create natural language response
↓
11. AI responds: "I've added 'Buy milk' to your todo list"
Why local execution?
- Direct access to React state
- No need to expose state mutation APIs
- Tools can trigger side effects (navigate, open modals, etc.)
- Type-safe with TypeScript
Context Handling
Context is evaluated at message send time:
// Static context - captured once
<InAppAI
context={{ userId: '123' }} // ← Object evaluated once on mount
/>
// Dynamic context - fresh on every message
<InAppAI
context={() => ({
scrollPosition: window.scrollY, // ← Function called each send
selectedText: window.getSelection()?.toString(),
})}
/>
State Management
The component manages several internal states:
UI State (Internal)
const [isOpen, setIsOpen] = useState(false); // Window open/closed
const [isFolded, setIsFolded] = useState(false); // Sidebar folded
const [inputValue, setInputValue] = useState(''); // Input text
const [isLoading, setIsLoading] = useState(false); // AI thinking
const [error, setError] = useState<string | null>(null);
Message State (External)
// Provided by parent via props
messages: Message[]
onMessagesChange: (messages: Message[]) => void
This separation ensures:
- UI state is temporary (doesn’t need persistence)
- Message state is durable (parent controls storage)
Rendering Strategy
InAppAI React uses different render strategies per display mode:
Popup Mode
return (
<>
<button onClick={toggleOpen}>🤖</button> {/* Fixed position */}
{isOpen && (
<div className="window"> {/* Absolute positioned */}
{/* Chat UI */}
</div>
)}
</>
);
Sidebar Mode
return (
<div className="sidebar"> {/* Fixed position, full height */}
{isFolded ? (
<div className="folded-content">AI</div>
) : (
<div className="expanded-content">{/* Chat UI */}</div>
)}
</div>
);
Panel Mode
return (
<div className="panel" style={{ width: panelWidth }}>
<div className="resize-handle" onMouseDown={startResize} />
{/* Chat UI */}
</div>
);
Embedded Mode
// Returns chat UI directly (no wrapper)
return (
<div className="chat-window">
{/* Chat UI */}
</div>
);
Bundle Size
InAppAI React is optimized for small bundle size:
- Main package: ~50KB (minified)
- Dependencies:
react-markdown: ~25KBreact-syntax-highlighter: ~20KB
- Total: ~95KB minified (~30KB gzipped)
Tree-shaking eliminates unused code.
Browser Compatibility
Supports all modern browsers:
- Chrome 90+
- Firefox 88+
- Safari 14+
- Edge 90+
Uses standard Web APIs:
fetchfor networkinglocalStoragefor persistence (user-implemented)- CSS Grid/Flexbox for layout
TypeScript Support
Fully typed with TypeScript:
- All props have type definitions
- Message, Tool, CustomStyles interfaces exported
- Generic types for custom contexts
- Type inference for tool handlers
import type { InAppAIProps, Message, Tool } from '@inappai/react';
Next Steps
- Performance - Optimization techniques
- Security - Security best practices
- Troubleshooting - Debug issues