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
(includes full conversation history + current message)
↓
6. Backend forwards conversation history + message to AI API
↓
7. AI responds (with optional tool calls)
↓
8. If tool calls:
a. Component executes tool handlers locally
b. Records each call in toolActions array
c. Sends results back to backend (with conversation history)
d. Backend gets natural language response
↓
9. Component calls onMessagesChange([...messages, assistantMessage])
(assistantMessage includes toolActions if tools were used)
↓
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.)
Conversation Memory
The component sends the full messages array to the backend with each API request, giving the AI complete conversation history. This means the AI can:
- Reference earlier messages (“As I mentioned before…”)
- Remember what it was asked and what it answered
- Recall tool actions it performed in previous turns
When assistant messages include tool executions, the component appends structured tool action context to the conversation history:
[Tool actions: addTodo({"text":"Buy groceries"}) → {"success":true}, completeTodo({"identifier":"Buy groceries"}) → {"success":true}]
This enables the AI to recall not just what it said, but exactly what actions it took and their outcomes.
What the AI sees per request:
[system] You are a helpful assistant... Context: {todos: [...]}
[user] Add a task called "Buy groceries"
[assistant] I've added "Buy groceries" to your list!
[Tool actions: addTodo({"text":"Buy groceries"}) → {"success":true}]
[user] What did you just do? ← Current message
Tool Execution Model
Tools use a local-first, iterative execution model:
1. User: "Add 3 todos: Buy milk, Call dentist, Finish report"
↓
2. Backend receives message + tool definitions
↓
3. AI decides to call tools (may return multiple tool calls)
↓
4. Backend returns: { toolCalls: [...] }
↓
5. Component executes ALL tool handlers locally (in parallel)
↓
6. Component sends results back to backend WITH tool definitions
↓
7. AI checks results:
a. If more actions needed → returns more tool calls (go to step 5)
b. If all done → returns natural language response
↓
8. Loop continues until AI responds with text only,
or maxToolRounds is reached
↓
9. Component calls onMessagesChange([...messages, assistantMessage])
The maxToolRounds prop (default: 10) limits how many rounds the loop can execute, preventing runaway tool calling.
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
Each Message in the array may include:
content- The text of the messageusage- Token usage statistics (assistant messages)toolActions- Record of tool calls and results (assistant messages that used tools)
This separation ensures:
- UI state is temporary (doesn’t need persistence)
- Message state is durable (parent controls storage)
- Tool execution history is preserved with messages
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