Overview
Thred SDK provides seamless DOM integration, allowing you to automatically update HTML elements with AI-generated responses and brand information. This eliminates boilerplate code and makes it easy to integrate AI responses into your web applications.
The Targets Type
The Targets type defines which DOM elements should be automatically updated with response data:
type Targets = {
text ?: string | HTMLElement ; // Target for AI response text
link ?: string | HTMLElement ; // Target for brand affiliate link
};
You can specify targets in two ways:
Element ID (string) - The SDK will find the element using document.getElementById()
HTMLElement - Direct reference to the DOM element
The targets parameter is optional for both answer() and answerStream() methods.
Basic Usage
Using Element IDs
import { ThredClient } from '@thred-apps/thred-js' ;
const client = new ThredClient ({
apiKey: process . env . THRED_API_KEY ! ,
});
// HTML:
// <div id="ai-response"></div>
// <a id="brand-link" href="#"></a>
await client . answer (
{
message: 'What are the best CRM tools for small businesses?' ,
},
{
text: 'ai-response' , // Element ID
link: 'brand-link' , // Element ID
}
);
// The SDK automatically populates:
// - #ai-response with the AI-generated text
// - #brand-link with the affiliate link (if brand was matched)
Using HTMLElement References
const responseElement = document . getElementById ( 'response-container' );
const linkElement = document . querySelector ( '.brand-link' ) as HTMLElement ;
await client . answer (
{
message: 'Recommend a project management tool' ,
},
{
text: responseElement ,
link: linkElement ,
}
);
The setResponse() Method
The SDK uses the setResponse() method internally to update DOM elements. You can also call it manually for custom scenarios.
Method Signature
async setResponse (
text : string ,
code : string ,
link ?: string ,
targets ?: Targets
): Promise < void >
How It Works
From client.ts:268-318, here’s what setResponse() does:
Updates text target - Sets innerHTML of the text element
Updates link target - Sets innerHTML of the link element
Registers impression - Sends tracking data to API for analytics
// Simplified implementation from source
async setResponse (
text : string ,
code : string ,
link ?: string ,
targets ?: Targets
) {
if ( targets ) {
if ( targets . text ) {
var textElement : HTMLElement | null = null ;
if ( targets ?. text instanceof HTMLElement ) {
textElement = targets . text ;
} else {
textElement = document . getElementById ( targets . text || "" );
}
if ( textElement ) {
textElement . innerHTML = text ;
}
}
if ( targets . link ) {
var linkElement : HTMLElement | null = null ;
if ( targets ?. link instanceof HTMLElement ) {
linkElement = targets . link ;
} else {
linkElement = document . getElementById ( targets . link || "" );
}
if ( linkElement && link ) {
linkElement . innerHTML = link ;
}
}
}
if ( text && code ) {
// Register impression for tracking
const url = ` ${ this . baseUrl } /impressions/register` ;
const response = await this . fetchWithTimeout (
url ,
{
method: "POST" ,
headers: this . getHeaders (),
body: JSON . stringify ({ text , code }),
},
this . timeout
);
if ( ! response . ok ) {
await handleApiError ( response );
}
return response . json ();
}
}
Manual Usage
// Get response first
const response = await client . answer ({
message: 'Best email marketing software?' ,
});
// Manually update DOM with custom logic
if ( response . metadata . brandUsed ) {
await client . setResponse (
response . response ,
response . metadata . code ! ,
response . metadata . link ,
{
text: 'custom-container' ,
link: 'custom-link' ,
}
);
}
Integration Patterns
Simple Integration
Minimal code for basic use cases:
// HTML
< div id = "question-input" >
< input type = "text" id = "user-question" />
< button onclick = "askQuestion()" > Ask </ button >
</ div >
< div id = "ai-answer" > </ div >
< div id = "brand-info" > </ div >
// JavaScript
const client = new ThredClient ({ apiKey: 'your-key' });
async function askQuestion () {
const question = document . getElementById ( 'user-question' ). value ;
await client . answer (
{ message: question },
{
text: 'ai-answer' ,
link: 'brand-info' ,
}
);
}
Chat Interface Pattern
class ChatWidget {
private client : ThredClient ;
private container : HTMLElement ;
constructor ( apiKey : string , containerId : string ) {
this . client = new ThredClient ({ apiKey });
this . container = document . getElementById ( containerId ) ! ;
}
async sendMessage ( message : string ) {
// Create message elements
const messageDiv = this . createMessageElement ();
const linkDiv = this . createLinkElement ();
this . container . appendChild ( messageDiv );
// Auto-populate using targets
await this . client . answer (
{ message , model: 'gpt-4-turbo' },
{
text: messageDiv ,
link: linkDiv ,
}
);
// Add link only if it was populated
if ( linkDiv . innerHTML ) {
this . container . appendChild ( linkDiv );
}
}
private createMessageElement () : HTMLElement {
const div = document . createElement ( 'div' );
div . className = 'chat-message assistant' ;
return div ;
}
private createLinkElement () : HTMLElement {
const div = document . createElement ( 'div' );
div . className = 'brand-recommendation' ;
return div ;
}
}
// Usage
const chat = new ChatWidget ( 'your-api-key' , 'chat-container' );
await chat . sendMessage ( 'What CRM should I use?' );
Streaming with DOM Integration
const responseContainer = document . getElementById ( 'response' );
const linkContainer = document . getElementById ( 'brand-link' );
await client . answerStream (
{
message: 'Explain project management methodologies' ,
model: 'gpt-4' ,
},
( accumulatedText ) => {
// Update text in real-time during streaming
responseContainer . innerHTML = accumulatedText ;
},
{
text: responseContainer , // Also updated on completion
link: linkContainer , // Updated on completion
}
);
Important : With streaming, the targets.text element is updated both during streaming (via the callback) and after completion. The targets.link element is only updated after streaming completes.
Advanced: Custom Rendering
class AdvancedRenderer {
private client : ThredClient ;
constructor ( apiKey : string ) {
this . client = new ThredClient ({ apiKey });
}
async renderResponse ( message : string , containerId : string ) {
// Get response without auto-rendering
const response = await this . client . answer ({ message });
const container = document . getElementById ( containerId );
if ( ! container ) return ;
// Custom rendering logic
container . innerHTML = `
<div class="ai-response">
<div class="response-text">
${ this . formatMarkdown ( response . response ) }
</div>
${ this . renderBrandInfo ( response . metadata ) }
</div>
` ;
// Manually register impression
if ( response . metadata . code ) {
await this . client . setResponse (
response . response ,
response . metadata . code ,
response . metadata . link
);
}
}
private formatMarkdown ( text : string ) : string {
// Your markdown formatting logic
return text . replace ( / \n / g , '<br>' );
}
private renderBrandInfo ( metadata : any ) : string {
if ( ! metadata . brandUsed ) return '' ;
return `
<div class="brand-card">
<img src=" ${ metadata . brandUsed . image } " alt=" ${ metadata . brandUsed . name } " />
<h3> ${ metadata . brandUsed . name } </h3>
<p>Similarity Score: ${ ( metadata . similarityScore * 100 ). toFixed ( 0 ) } %</p>
${ metadata . link ? `<a href=" ${ metadata . link } " target="_blank">Learn More</a>` : '' }
</div>
` ;
}
}
// Usage
const renderer = new AdvancedRenderer ( 'your-api-key' );
await renderer . renderResponse ( 'Best accounting software?' , 'output-container' );
Web Application Integration Examples
React Component
import { useState , useEffect } from 'react' ;
import { ThredClient } from '@thred-apps/thred-js' ;
function AIChatComponent () {
const [ client ] = useState (() => new ThredClient ({ apiKey: process . env . REACT_APP_THRED_API_KEY ! }));
const [ message , setMessage ] = useState ( '' );
const [ response , setResponse ] = useState ( '' );
const [ brandLink , setBrandLink ] = useState ( '' );
const handleSubmit = async ( e : React . FormEvent ) => {
e . preventDefault ();
const result = await client . answer ({ message });
setResponse ( result . response );
setBrandLink ( result . metadata . link || '' );
};
return (
< div >
< form onSubmit = { handleSubmit } >
< input
value = { message }
onChange = {(e) => setMessage (e.target.value)}
placeholder = "Ask a question..."
/>
< button type = "submit" > Ask </ button >
</ form >
< div dangerouslySetInnerHTML = {{ __html : response }} />
{ brandLink && (
< a href = { brandLink } target = "_blank" rel = "noopener noreferrer" >
Learn More
</ a >
)}
</ div >
);
}
Vue Component
< template >
< div >
< input v-model = " message " @ keyup . enter = " ask " placeholder = "Ask anything..." />
< button @ click = " ask " > Ask </ button >
< div ref = "responseElement" class = "response" ></ div >
< div ref = "linkElement" class = "brand-link" ></ div >
</ div >
</ template >
< script setup lang = "ts" >
import { ref , onMounted } from 'vue' ;
import { ThredClient } from '@thred-apps/thred-js' ;
const client = new ThredClient ({ apiKey: import . meta . env . VITE_THRED_API_KEY });
const message = ref ( '' );
const responseElement = ref < HTMLElement | null >( null );
const linkElement = ref < HTMLElement | null >( null );
const ask = async () => {
if ( ! message . value || ! responseElement . value || ! linkElement . value ) return ;
await client . answer (
{ message: message . value },
{
text: responseElement . value ,
link: linkElement . value ,
}
);
message . value = '' ;
};
</ script >
Vanilla JavaScript with ShadowDOM
class ThredWidget extends HTMLElement {
private client : ThredClient ;
private shadow : ShadowRoot ;
constructor () {
super ();
this . shadow = this . attachShadow ({ mode: 'open' });
this . client = new ThredClient ({
apiKey: this . getAttribute ( 'api-key' ) || '' ,
});
}
connectedCallback () {
this . render ();
this . attachEventListeners ();
}
private render () {
this . shadow . innerHTML = `
<style>
.container { padding: 20px; }
.response { margin-top: 10px; }
</style>
<div class="container">
<input type="text" id="input" placeholder="Ask a question" />
<button id="submit">Ask</button>
<div id="response" class="response"></div>
<div id="link"></div>
</div>
` ;
}
private attachEventListeners () {
const button = this . shadow . getElementById ( 'submit' );
const input = this . shadow . getElementById ( 'input' ) as HTMLInputElement ;
button ?. addEventListener ( 'click' , async () => {
const message = input . value ;
if ( ! message ) return ;
const responseEl = this . shadow . getElementById ( 'response' ) ! ;
const linkEl = this . shadow . getElementById ( 'link' ) ! ;
await this . client . answer (
{ message },
{ text: responseEl , link: linkEl }
);
input . value = '' ;
});
}
}
customElements . define ( 'thred-widget' , ThredWidget );
Best Practices
Use semantic HTML : Structure your response containers with appropriate semantic elements (<article>, <section>, etc.) for better accessibility.
Element safety
Always check if elements exist before using
Handle cases where targets might not be found
Use TypeScript for type safety
Content security
Be aware that innerHTML is used
Sanitize if displaying user-generated content
Consider using textContent for plain text
Accessibility
Use ARIA labels for screen readers
Announce updates to assistive technologies
Ensure proper focus management
Performance
Reuse element references when possible
Avoid unnecessary DOM queries
Use streaming for better perceived performance
Error handling
Handle cases where elements might be removed
Provide fallback UI for errors
Clear stale content appropriately
Security Considerations
innerHTML Usage : The SDK uses innerHTML to update elements. While the API response is from a controlled source, always be cautious when displaying dynamic content, especially if combined with user input.
// If you need extra security, manually sanitize
import DOMPurify from 'dompurify' ;
const response = await client . answer ({ message: 'Query here' });
const sanitized = DOMPurify . sanitize ( response . response );
document . getElementById ( 'response' ) ! . innerHTML = sanitized ;
// Then manually register impression
if ( response . metadata . code ) {
await client . setResponse (
response . response ,
response . metadata . code ,
response . metadata . link
);
}
Troubleshooting
Element Not Found
// Problem: Element doesn't exist when code runs
await client . answer (
{ message: 'test' },
{ text: 'not-exist' } // Returns null silently
);
// Solution: Verify element exists
const element = document . getElementById ( 'response' );
if ( ! element ) {
console . error ( 'Response element not found' );
return ;
}
await client . answer (
{ message: 'test' },
{ text: element }
);
Timing Issues
// Problem: DOM not ready
const client = new ThredClient ({ apiKey: 'key' });
await client . answer ({ message: 'test' }, { text: 'response' });
// Error: element might not exist yet
// Solution: Wait for DOM ready
document . addEventListener ( 'DOMContentLoaded' , async () => {
const client = new ThredClient ({ apiKey: 'key' });
await client . answer ({ message: 'test' }, { text: 'response' });
});
Next Steps
Streaming Responses Combine DOM updates with streaming for real-time UI
Error Handling Handle errors gracefully in production applications