Overview
The Services module wraps REST API endpoints (14 core services) and integrates them with React Query for automatic caching, deduplication, and optimistic updates. Each service method returns a React Query query/mutation definition, not raw promises. This ensures consistent data flow across all pages.
🔗 Service Modules
Agent Service
src/services/agent-service.ts
- CRUD: createAgent, updateAgent, deleteAgent
- Agent Canvas Canvas & Execution — Canvas & Execution" class="xref">Graph DSL: serializeGraph, deserializeGraph
- List agents with pagination
- Publish/draft versions
- Endpoint:
/api/v1/agents
Chat Service
src/services/chat-service.ts
- Create chat session
- Send message (with streaming)
- Fetch chat history
- Update title/archive
- Endpoint:
/api/v1/chats
Knowledge Service
src/services/knowledge-service.ts
- Upload document file
- List chunks (paginated)
- Delete chunk
- Re-index chunks
- Endpoints:
/api/v1/knowledge,/v1/kb
Search Service
src/services/search-service.ts
- Semantic search query
- Filter by doc type, date
- Paginate results (100/page)
- Highlight snippet matches
- Endpoint:
/api/v1/search
User Service
src/services/user-service.ts
- Get current user profile
- Update profile (name, avatar)
- Change password
- Manage API keys
- Endpoint:
/api/v1/user
Admin Service
src/services/admin-service.ts
- List users, manage roles
- System health/quotas
- Audit logs
- Backup/restore settings
- Endpoint:
/api/v1/admin
📂 Service Layer Architecture
src/services/
├── agent-service.ts # Agent CRUD + DSL
├── chat-service.ts # Chat & streaming
├── knowledge-service.ts # File upload + KB mgmt
├── search-service.ts # Vector search
├── user-service.ts # User profile
├── admin-service.ts # Admin API
└── api-client.ts # Base HTTP client
🔄 React Query Integration
Pattern: Each service method returns a query/mutation definition that can be used with useQuery/useMutation.
Query Example
// In service: agent-service.ts
export const agentQueries = {
list: (page: number) => ({
queryKey: ['agents', page],
queryFn: () => http.get('/api/v1/agents', { params: { page } })
})
};
// In component: useAgentRequest hook
export const useAgentList = (page: number) => {
const query = agentQueries.list(page);
return useQuery({
...query,
staleTime: 5 * 60 * 1000 // 5 min
});
};
// Usage in component
const { data, isLoading, error } = useAgentList(1);
Mutation Example
// In service: agent-service.ts
export const agentMutations = {
create: {
mutationFn: (data: CreateAgentDTO) =>
http.post('/api/v1/agents', data),
onSuccess: () => {
queryClient.invalidateQueries(['agents']);
}
}
};
// Usage in component
const mutation = useMutation(agentMutations.create);
mutation.mutate({ name: 'New Agent' });
⚡ Optimizations
- Query deduplication: If 2 components request same agent within stale time, only 1 fetch happens
- Stale-while-revalidate: Cache used immediately; refetch in background
- Optimistic updates: UI updates before server response (agent name change appears instantly)
- Refetch on focus: Auto-sync when user returns to tab (configurable)
- Refetch on mount: New component instances re-validate cache
Error Handling
Centralized error interceptor: HTTP client catches 4xx/5xx and formats errors.
// In api-client.ts
http.interceptors.response.use(
res => res.data,
error => {
const msg = error.response?.data?.error || error.message;
toast.error(msg);
return Promise.reject(error);
}
);
Authentication & Headers
All requests automatically include JWT token from localStorage.
// In api-client.ts
http.interceptors.request.use(config => {
const token = localStorage.getItem('auth_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
Proxy Routing (Dev Only)
Vite dev server proxies API calls to backend (see vite.config.ts):
proxy: {
'/api/v1/agents': { target: 'http://localhost:9380' },
'/api/v1/admin': { target: 'http://localhost:9383' },
'/v1/kb': { target: 'http://localhost:9384' }
}
Common Issues & Solutions
| Issue | Cause | Solution |
|---|---|---|
| Query data is undefined | Still loading or cache miss | Check isLoading flag before rendering |
| Mutation not invalidating | Wrong queryKey | Use queryClient.invalidateQueries({ queryKey: ['agents'] }) |
| Request hangs (CORS error) | Dev server proxy not configured | Add proxy to vite.config.ts |
Next Steps
- Explore Pages to see service usage in components
- Review Components for UI binding patterns
- Deep-dive: API Data Layer for advanced React Query patterns
Services Module/{{SUBMOD_NAME}}/ — {{SUBMOD_TITLE}}
{{SUBMOD_PURPOSE}}
{{SUBMOD_DESC}}
📚 What is this & why does it exist?
{{SUBMOD_WHAT_WHY}} — Replace with 2-4 plain-English sentences explaining what this sub-module does and why it exists. No jargon. Target audience: someone who hasn't read the code.
💡 Real-world analogy
{{SUBMOD_ANALOGY}} — Replace with a vivid, concrete metaphor mapping this sub-module's core concept to everyday life (restaurant, airport, library, highway, etc.). 3-5 sentences.
🤔 Why Is It Designed This Way?
{{DESIGN_RATIONALE}} — 150+ words explaining the design choices: what pattern does this follow and why? What constraints shaped the implementation? What trade-offs were made and what was sacrificed?
| Design Choice | What Was Chosen | What Was Rejected | Why |
|---|---|---|---|
| Example: data layout | Array of structs | Struct of arrays | Random access pattern dominates; cache locality for single items matters more than SIMD sweeps |
Contents
Architecture
flowchart TD A["Component A"] --> B["Component B"] B --> C["Component C"] C --> D["Component D"]
Key Classes
| Class / Function | File | Purpose |
|---|---|---|
ClassName | file.h | Brief description |
Data Structures
| Type | Name | Description |
|---|---|---|
| enum | EnumName | Brief description |
Interactions
| Direction | Partner | Interface | Description |
|---|---|---|---|
| → | Other Module | methodName() | Brief description |
Configuration
| Knob / Parameter | Default | Description |
|---|---|---|
param_name | value | What it controls |
Directory Map
Source tree for {{SUBMOD_NAME}}
submod_name/
├── file_a.ext ← Role: brief annotation
├── file_b.ext ← Role: brief annotation
├── file_c.ext ← Role: brief annotation
└── index.ext ← Role: entry point
📑 Annotated Code Walkthrough
The most important code in this sub-module, annotated with WHY each piece exists:
{{KEY_FUNCTION_NAME}}
// Replace with actual code from the source
void key_function() {
// ← WHY: Explain the purpose of this line
// ← WHY: What constraint or requirement drives this?
}
Line-by-line:
- Line N: What this does and WHY — what would break if removed
- Line M: The design choice here — why this approach over the obvious alternative
⚠️ Edge Cases & Boundaries
| Edge Case | What Happens | Where in Code | Risk Level |
|---|---|---|---|
| Empty input | How it's handled | function_name:line | 🟢 Low |
| Concurrent access | How it's handled | function_name:line | 🔴 High |
Deep-Dive Highlights
Core mechanisms from this sub-module have been extracted into dedicated focus pages for in-depth exploration. Each page covers one algorithm, protocol, or subsystem with diagrams, code walks, and configuration details.
Deep-Dive Pages
Topic A Name Focus
Short description of what this focus page covers.
Topic B Name Focus
Short description of what this focus page covers.
Code Examples
Example: Typical Usage
// Replace with actual code example
void example() {
// ...
}