Skip to main content

Frontend Integration

This guide explains how to integrate Captide's document viewer and SEC Q&A capabilities into your React frontend application.

Important Security Note

Never expose your Captide API key in frontend code! Always use a backend proxy as shown in the Backend Integration guide.

React Integration

Basic Document Viewer Setup

Here's how to set up the Captide document viewer component in a React application:

import React, { useState } from 'react';
import { DocumentViewer, DocumentViewerProvider } from 'captide';

function App() {
// Function to fetch document through your backend API
const fetchDocument = async (sourceLink) => {
const response = await fetch('/api/document', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ source_link: sourceLink })
});

return response.json();
};

return (
<div className="app">
<h1>Captide Document Explorer</h1>

<DocumentViewerProvider fetchDocumentFn={fetchDocument}>
<DocumentExplorerDemo />
</DocumentViewerProvider>
</div>
);
}

function DocumentExplorerDemo() {
const [answer, setAnswer] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [query, setQuery] = useState('');

// Create a simple form for asking questions
const handleSubmit = async (e) => {
e.preventDefault();
setIsLoading(true);

try {
// Call your backend API that proxies to Captide
const response = await fetch('/api/document-snippets', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query })
});

const data = await response.json();
// Process and display the response
// This is a simplified example - you'd typically render the snippets
setAnswer(JSON.stringify(data, null, 2));
} catch (error) {
console.error('Error:', error);
} finally {
setIsLoading(false);
}
};

return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Ask about SEC filings (e.g., What's AAPL's revenue?)"
/>
<button type="submit" disabled={isLoading}>
{isLoading ? 'Loading...' : 'Ask'}
</button>
</form>

{answer && (
<pre>{answer}</pre>
)}

{/* The DocumentViewer needs to be sized explicitly */}
<div style={{ height: '600px', marginTop: '20px', border: '1px solid #ccc' }}>
<DocumentViewer />
</div>
</div>
);
}

export default App;

Streaming Response Example

For handling streaming responses from the AI agent:

import React, { useState, useEffect, useRef } from 'react';
import { DocumentViewer, DocumentViewerProvider, useDocumentViewer } from 'captide';

function StreamingDemo() {
const [query, setQuery] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [streamedText, setStreamedText] = useState('');
const [idMapping, setIdMapping] = useState({});
const { loadDocument } = useDocumentViewer();

const handleSubmit = async (e) => {
e.preventDefault();
setIsLoading(true);
setStreamedText('');

try {
// Call your backend API that proxies the streaming endpoint
const response = await fetch('/api/query-stream', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query })
});

// Create a reader for the stream
const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
const { value, done } = await reader.read();
if (done) break;

const text = decoder.decode(value);
// Process the SSE data
const lines = text.split('\n');

for (const line of lines) {
if (line.startsWith('data: ')) {
try {
const data = JSON.parse(line.substring(6));

// Handle different event types
if (data.type === 'id_mapping') {
setIdMapping(data.mapping);
} else if (data.type === 'markdown_chunk') {
setStreamedText(prev => prev + data.content);
} else if (data.type === 'full_answer') {
setStreamedText(data.content);
}
} catch (e) {
console.error('Error parsing SSE data:', e);
}
}
}
}
} catch (error) {
console.error('Error with streaming:', error);
} finally {
setIsLoading(false);
}
};

// Function to handle source link clicks
const handleSourceClick = async (sourceId) => {
// Extract source info from the mapping
const sourceInfo = idMapping[sourceId];
if (sourceInfo && sourceInfo.sourceLink) {
await loadDocument(sourceInfo.sourceLink, sourceId);
}
};

// Simple regex to detect source references like [#123abc]
const renderWithSourceLinks = (text) => {
if (!text) return '';

// Split by references [#id] and render as clickable spans
const parts = text.split(/(\[#[a-z0-9]+\])/g);

return parts.map((part, index) => {
// Check if this part is a reference
const match = part.match(/\[#([a-z0-9]+)\]/);

if (match) {
const sourceId = `#${match[1]}`;
return (
<span
key={index}
className="source-link"
onClick={() => handleSourceClick(sourceId)}
style={{ cursor: 'pointer', color: 'blue', textDecoration: 'underline' }}
>
[{index + 1}]
</span>
);
}

// Regular text
return <span key={index}>{part}</span>;
});
};

return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Ask about SEC filings (e.g., What's AAPL's revenue?)"
style={{ width: '400px' }}
/>
<button type="submit" disabled={isLoading}>
{isLoading ? 'Loading...' : 'Ask'}
</button>
</form>

<div className="answer-container">
{renderWithSourceLinks(streamedText)}
</div>

<div style={{ height: '600px', marginTop: '20px', border: '1px solid #ccc' }}>
<DocumentViewer />
</div>
</div>
);
}

function App() {
const fetchDocument = async (sourceLink) => {
const response = await fetch('/api/document', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ source_link: sourceLink })
});

return response.json();
};

return (
<DocumentViewerProvider fetchDocumentFn={fetchDocument}>
<StreamingDemo />
</DocumentViewerProvider>
);
}

export default App;

Next.js Integration

If you're using Next.js, you can create API routes to proxy requests to Captide:

// pages/api/document.js
export default async function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}

try {
const { source_link } = req.body;

const response = await fetch(source_link, {
headers: {
'Authorization': `Bearer ${process.env.CAPTIDE_API_KEY}`
}
});

if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}

const data = await response.json();
res.json(data);
} catch (error) {
console.error('Error fetching document:', error);
res.status(500).json({ error: 'Failed to fetch document' });
}
}

Live Example

To see a complete implementation of Captide's capabilities, visit app.captide.co/chat.

Next Steps