Bright Wrapper LLM Integration Guide
=====================================
Bright Wrapper provides JavaScript components and backend APIs for integrating LLM chat into your application.
MODEL SELECTION
---------------
Bright Wrapper manages model selection internally. You do NOT need to specify which model to use -
Bright Wrapper automatically uses the best available model for each endpoint type.
To see current models: GET https://brightwrapper.com/api/model
Returns: {"text_model": "...", "image_model": "...", "note": "..."}
QUICK START
-----------
1. Add a container div and load the component script:
2. Create backend proxy endpoints that add your Bright Wrapper API key server-side and forward requests to https://brightwrapper.com
BACKEND PROXY SETUP
-------------------
Your backend must proxy these endpoints:
/api/brightwrapper/chat/stream → POST https://brightwrapper.com/api/chat/stream
/api/brightwrapper/estimate → POST https://brightwrapper.com/api/estimate
/api/brightwrapper/version → GET https://brightwrapper.com/api/version
Each proxy endpoint:
- Receives request from frontend component
- Adds header: X-API-Key:
- Forwards to Bright Wrapper
- Streams response back to frontend
This keeps your API key secure on the server.
COMPONENT OPTIONS
-----------------
new LLMChatComponent({
containerId: 'chat-container', // Required: DOM element ID
projectName: 'my_app', // Required: identifier for logging
apiUrl: '...', // Required: your proxy endpoint for chat
estimateUrl: '...', // Required: your proxy endpoint for estimates
versionUrl: '...', // Required: your proxy endpoint for version
temperature: 0.7, // Optional: 0.0-1.0
systemPrompt: '...', // Optional: system instructions
initialMessage: '...', // Optional: greeting message
headers: {} // Optional: extra headers for requests
});
Note: Model selection is handled by Bright Wrapper - do not specify a model.
API REFERENCE
-------------
POST /api/chat/stream
Streaming chat endpoint. Model is chosen by Bright Wrapper.
Request body:
{
"message": "user message",
"project_name": "my_app",
"system_prompt": "optional instructions", // optional
"temperature": 0.7 // optional
}
Headers:
X-API-Key:
Response: Server-Sent Events stream
data: {"chunk": "text"}
data: {"done": true}
data: {"error": "message"} // on error
POST /api/estimate
Get estimated response time based on historical data.
Request body:
{
"prompt": "user message",
"project_name": "my_app", // optional: scope estimate to project
"endpoint_version": "1" // optional: version for fresh timing data
}
Response:
{
"estimated_seconds": 5.2,
"has_version_data": true // true if estimate uses this version's data
}
GET /api/version
Get service version.
Response:
{"version": "1.0.0"}
GET /api/model
Get current model info and tradeoff options. No authentication required.
Response:
{
"default_model": "gemini-2.5-pro-preview-06-05",
"image_model": "gemini-2.0-flash-preview-image-generation",
"api_version": "1.0.0",
"tradeoff_selection": {
"description": "Specify speed and quality (0-1 each) to influence model selection",
"examples": {
"balanced": {"speed": 0.5, "quality": 0.5, "selects": "..."},
"fast": {"speed": 0.8, "quality": 0.2, "selects": "..."},
"quality": {"speed": 0.2, "quality": 0.8, "selects": "..."}
}
},
"available_models": [...]
}
POST /api/structured_llm_call
Structured output with JSON schema. Model is chosen by Bright Wrapper.
Request body:
{
"prompt": "your prompt",
"project_name": "my_app",
"text_format_schema": {
"properties": {
"field_name": {"type": "string"}
},
"required": ["field_name"]
},
"should_log": true, // optional, default true
"max_completion_tokens": 4096, // optional
"speed": 0.5, // optional, 0-1 (higher = faster model)
"quality": 0.5, // optional, 0-1 (higher = better model)
"endpoint_version": "1" // optional, for timing estimates
}
Headers:
X-API-Key:
Response:
{
"model_used": "gemini-2.5-pro-preview-06-05",
"duration_seconds": 1.234,
"result": {...} // JSON matching your schema
}
POST /api/structured_llm_call/async
Async version - returns immediately with task_id. Model is chosen by Bright Wrapper.
Request body: Same as /api/structured_llm_call
Response (202):
{"task_id": "...", "status": "pending", "poll_url": "/api/task/..."}
GET /api/task/{task_id}
Poll async task status.
Response: Always 200 (or 404 if task not found)
{
"task_id": "...",
"status": "pending|running|completed|failed",
"result": {...}, // when status="completed"
"error": "...", // when status="failed"
"logs": ["[HH:MM:SS] message", ...], // timestamped log messages
"progress": 0.5 // 0.0-1.0 scale (not percentage)
}
LLM PROGRESS COMPONENT
----------------------
Bright Wrapper provides a reusable progress bar component for long-running LLM operations.
Include it in your page and use it to show progress during any async LLM call.
Include the component:
Basic usage:
Constructor options:
new LLMProgress({
container: element, // Required: DOM element to append progress bar to
estimateUrl: '...', // URL to fetch time estimates (default: brightwrapper.com)
projectName: 'my_app', // Project name for estimate lookup
theme: 'light', // 'light' or 'dark' theme
showDelay: 300, // ms delay before showing (avoids flash for fast responses)
headers: {} // Extra headers for API calls
});
Methods:
await progress.start(title, options)
Start showing progress. Fetches estimate and begins animation.
- title: Text to display (e.g., "Generating response...")
- options.prompt: Prompt text for better estimate
- options.estimatedTime: Override estimate (seconds)
progress.complete(message)
Fill bar to 100% and show completion message.
- message: Optional custom completion text
progress.hide()
Hide the progress bar immediately.
progress.destroy()
Remove the progress bar from DOM.
progress.updateLogs(logs)
Update the log display with array of log messages.
- logs: Array of strings (e.g., ["[12:00:01] Starting...", "[12:00:02] Done"])
progress.showRetry(attemptNumber)
Show retry warning when API retries occur.
await progress.pollForTask(taskId, taskUrl, options)
Poll for async task completion. Updates progress bar automatically.
- taskId: The task ID from async endpoint
- taskUrl: Base URL (e.g., '/api/task')
- options.maxAttempts: Max poll attempts (default: 300 = 5 min)
- options.pollInterval: Ms between polls (default: 1000)
Returns: Completed task result
Throws: Error on timeout or task failure
Features:
- 300ms delay before showing (avoids flash for cached/fast responses)
- Fetches time estimate from Bright Wrapper
- Shows "X seconds remaining" countdown
- Caps at 95% until complete (never shows false 100%)
- Auto-scrolling log display
- Retry detection and display
- Light and dark theme support
SMART PROGRESS BARS (Manual Implementation)
-------------------------------------------
For long-running async tasks, implement a progress bar UI that polls /api/task/{task_id}:
1. INITIATE: POST to async endpoint, get task_id (HTTP 202)
2. POLL: GET /api/task/{task_id} every 300ms
3. UPDATE: Set progress bar width from `progress` field (0.0-1.0 scale, multiply by 100 for %)
4. LOGS: Display `logs` array in scrollable container (newest at bottom)
5. COMPLETE: Stop polling when status="completed" or status="failed"
Log Format:
Logs are timestamped with emoji indicators:
[14:32:01] 🔍 Starting task...
[14:32:02] 📊 Processing data...
[14:32:03] ✅ Complete!
Example Progress Bar CSS:
.progress-bar-wrapper { background: #333; border-radius: 8px; height: 24px; overflow: hidden; position: relative; }
.progress-bar { height: 100%; background: linear-gradient(90deg, #00d4ff, #4ade80); transition: width 0.3s; }
.progress-text { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #fff; }
.progress-logs { max-height: 150px; overflow-y: auto; font-family: monospace; font-size: 12px; }
Example JavaScript:
async function pollTask(taskId) {
while (true) {
await new Promise(r => setTimeout(r, 300));
const res = await fetch(`/api/task/${taskId}`);
const data = await res.json();
// Update UI (progress is 0.0-1.0, multiply by 100 for percentage)
const pct = (data.progress || 0) * 100;
progressBar.style.width = `${pct}%`;
progressText.textContent = `${Math.round(pct)}%`;
logsContainer.innerHTML = data.logs.map(l => `${l}
`).join('');
logsContainer.scrollTop = logsContainer.scrollHeight;
if (data.status === 'completed') return data.result;
if (data.status === 'failed') throw new Error(data.error);
}
}
Backend Implementation (log_user_message):
When implementing async endpoints, use timestamped log messages:
def log_user_message(task_id: str, message: str):
"""Log a user-facing message (timestamps added automatically)."""
timestamp = time.strftime('%H:%M:%S')
task_status[task_id]['logs'].append(f"[{timestamp}] {message}")
# Usage:
log_user_message(task_id, "🔍 Starting to process...")
log_user_message(task_id, "📊 Analyzing data...")
log_user_message(task_id, "✅ Complete!")
# To update progress separately:
task_status[task_id]['progress'] = 0.5 # 0.0-1.0 scale
COMBINED PROGRESS BAR + LOG CONSOLE PATTERN
--------------------------------------------
For long-running operations, show BOTH a progress bar AND a scrolling log console.
This gives users both an at-a-glance status and detailed visibility into what's happening.
Layout:
┌─────────────────────────────────────────────────────┐
│ [===========> ] 45% - 12s left │ ← Progress bar
├─────────────────────────────────────────────────────┤
│ [19:02:24] Starting task... │ ← Scrolling log
│ [19:02:25] Downloading video... │
│ [19:02:33] Video cached (197.9 MB) │
│ [19:02:41] Generating hierarchical summary... │
│ [19:02:59] Expanded section 1/4: Introduction... │
└─────────────────────────────────────────────────────┘
Example HTML:
Example CSS:
.progress-log-container { background: #1e1e1e; border-radius: 8px; overflow: hidden; }
.progress-section { padding: 10px 15px; border-bottom: 1px solid #333; }
.progress-bar-wrapper { background: #333; border-radius: 4px; height: 24px; position: relative; }
.progress-bar { height: 100%; background: linear-gradient(90deg, #00d4ff, #4ade80);
border-radius: 4px; transition: width 0.3s; }
.progress-text { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
color: #fff; font-size: 13px; white-space: nowrap; }
.log-console { color: #d4d4d4; font-family: monospace; font-size: 13px;
height: 200px; overflow-y: auto; padding: 10px 15px; white-space: pre-wrap; }
Example JavaScript (polling pattern):
async function pollWithProgressAndLogs(taskId) {
const progressBar = document.getElementById('progressBar');
const progressText = document.getElementById('progressText');
const logConsole = document.getElementById('logConsole');
while (true) {
await new Promise(r => setTimeout(r, 1000));
const res = await fetch(`/api/task/${taskId}`);
const data = await res.json();
// Update progress bar (progress is 0.0-1.0, multiply by 100 for %)
const pct = (data.progress || 0) * 100;
progressBar.style.width = `${pct}%`;
progressText.textContent = `${Math.round(pct)}% - ${data.status}`;
// Update log console
if (data.logs) {
logConsole.innerHTML = data.logs.map(l => `${l}
`).join('');
logConsole.scrollTop = logConsole.scrollHeight; // Auto-scroll to bottom
}
if (data.status === 'completed') return data.result;
if (data.status === 'failed') throw new Error(data.error);
}
}
Backend pattern (add logs and progress together):
def log_with_progress(task_id: str, message: str, progress: float = None):
"""Log a message and optionally update progress (0.0-1.0 scale)."""
timestamp = time.strftime('%H:%M:%S')
task = tasks[task_id]
task['logs'].append(f"[{timestamp}] {message}")
if progress is not None:
task['progress'] = progress
# Usage - log messages appear in console, progress updates the bar:
log_with_progress(task_id, "Using cached video (197.9 MB)", 0.1)
log_with_progress(task_id, "Generating hierarchical summary...", 0.2)
log_with_progress(task_id, "Expanded section 1/4: Introduction...", 0.4)
log_with_progress(task_id, "Expanded section 2/4: Main content...", 0.55)
Benefits of combined approach:
- Progress bar gives quick visual status (how much longer?)
- Log console shows detailed activity (what's happening now?)
- Users can ignore logs for casual use, or inspect them for debugging
- No "fake" multi-step progress - logs show real operations
- Log timestamps help identify slow steps for optimization
PROMPT TRANSPARENCY
-------------------
For long-running LLM calls, show users what's actually happening. This builds trust
and helps debugging. Key principles:
1. Show the actual prompt being sent (collapsible)
2. Be honest about timing uncertainty ("Waiting for response..." not fake multi-step)
3. Show the raw response after completion (collapsible)
Don't fake multi-step processes. If it's one LLM call, say "Waiting for Claude..."
not "Step 1: Analyzing... Step 2: Processing..." - users can tell when you're lying.
Example HTML Structure:
Waiting for Claude to respond...
12s elapsed
View prompt being sent →
[actual prompt text here]
View raw API response →
[full JSON response]
Example JavaScript:
function showPrompt(prompt) {
document.getElementById('prompt-display').textContent = prompt;
}
function showRawResponse(data) {
document.getElementById('raw-response').textContent = JSON.stringify(data, null, 2);
}
Why This Matters:
- Technical users want to verify what's being sent
- Debugging is easier when you can see the actual prompt/response
- Honest progress bars build trust (fake ones erode it)
- Collapsible sections keep casual users uncluttered while power users can inspect
POST /api/generate_image
Generate image. Model is chosen by Bright Wrapper.
Request body:
{
"prompt": "image description",
"output_path": "/path/to/save.png",
"project_name": "my_app",
"aspect_ratio": "9:16", // optional, default "9:16"
"should_log": true // optional, default true
}
Headers:
X-API-Key:
Response:
{"image_path": "/path/to/saved.png"}
EXAMPLE: PYTHON PROXY (FastAPI)
-------------------------------
import httpx
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse
BRIGHT_WRAPPER_URL = "https://brightwrapper.com"
BRIGHT_WRAPPER_API_KEY = "your_api_key_here"
app = FastAPI()
@app.post("/api/brightwrapper/chat/stream")
async def proxy_chat(request: Request):
body = await request.json()
async def stream():
async with httpx.AsyncClient() as client:
async with client.stream(
"POST",
f"{BRIGHT_WRAPPER_URL}/api/chat/stream",
json=body,
headers={"X-API-Key": BRIGHT_WRAPPER_API_KEY}
) as response:
async for chunk in response.aiter_bytes():
yield chunk
return StreamingResponse(stream(), media_type="text/event-stream")
@app.post("/api/brightwrapper/estimate")
async def proxy_estimate(request: Request):
body = await request.json()
async with httpx.AsyncClient() as client:
response = await client.post(
f"{BRIGHT_WRAPPER_URL}/api/estimate",
json=body
)
return response.json()
@app.get("/api/brightwrapper/version")
async def proxy_version():
async with httpx.AsyncClient() as client:
response = await client.get(f"{BRIGHT_WRAPPER_URL}/api/version")
return response.json()
EXAMPLE: NODE.JS PROXY (EXPRESS)
--------------------------------
const express = require('express');
const fetch = require('node-fetch');
const app = express();
const BRIGHT_WRAPPER_URL = 'https://brightwrapper.com';
const BRIGHT_WRAPPER_API_KEY = 'your_api_key_here';
app.use(express.json());
app.post('/api/brightwrapper/chat/stream', async (req, res) => {
const response = await fetch(`${BRIGHT_WRAPPER_URL}/api/chat/stream`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': BRIGHT_WRAPPER_API_KEY
},
body: JSON.stringify(req.body)
});
res.setHeader('Content-Type', 'text/event-stream');
response.body.pipe(res);
});
app.post('/api/brightwrapper/estimate', async (req, res) => {
const response = await fetch(`${BRIGHT_WRAPPER_URL}/api/estimate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(req.body)
});
res.json(await response.json());
});
app.get('/api/brightwrapper/version', async (req, res) => {
const response = await fetch(`${BRIGHT_WRAPPER_URL}/api/version`);
res.json(await response.json());
});
API KEY MANAGEMENT
------------------
Create API key:
POST /api/create_key
Body: {"auth_token": ""}
Response: {"api_key": "...", "user_id": 1, "user_email": "..."}
List your keys:
GET /api/keys
Headers: X-API-Key:
Revoke a key:
POST /api/revoke_key
Headers: X-API-Key:
Body: {"api_key": ""}
VERSIONING & UPGRADE NOTIFICATIONS
----------------------------------
Bright Wrapper uses API response bulletins to notify clients about upgrades,
deprecations, and important changes. Clients should check /api/status periodically.
GET /api/status
Get complete service status (no authentication required).
Response:
{
"api_version": "1.0.0",
"min_supported_version": "1.0.0",
"service_version": "1.2.3",
"text_model": "gemini-3-pro-preview",
"image_model": "gemini-3-pro-image-preview",
"bulletins": [
{"message": "Model upgraded", "severity": "info", "expires": "2025-01-15"}
],
"deprecations": {
"/api/old_endpoint": {
"deprecated": true,
"sunset_date": "2025-02-01",
"replacement": "/api/new_endpoint"
}
}
}
Bulletin Severity Levels:
- "info" - Informational, no action required
- "warning" - Action may be required soon
- "critical" - Immediate action required
Best Practices for Clients:
1. Check /api/status on startup or periodically (e.g., daily)
2. Log bulletins with severity >= "warning" for developer review
3. Display critical bulletins to users if appropriate
4. Monitor sunset_date for any deprecated endpoints you use
5. Plan upgrades before sunset dates to avoid service interruption
Version Compatibility:
- Clients using API version >= min_supported_version will continue working
- When min_supported_version increases, older clients may receive errors
- Bulletins will warn about upcoming version changes before they happen