brainkb-assistant
Version:
A configurable, standalone BrainKB Assistant that can be integrated into any website
1,030 lines (994 loc) • 142 kB
JavaScript
'use client';
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react/jsx-runtime'), require('react'), require('react-dom')) :
typeof define === 'function' && define.amd ? define(['exports', 'react/jsx-runtime', 'react', 'react-dom'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.BrainKBAssistant = {}, global.jsxRuntime, global.React, global.ReactDOM));
})(this, (function (exports, jsxRuntime, React, ReactDOM) { 'use strict';
/**
* lucide-react v0.0.1 - ISC
*/
var defaultAttributes = {
xmlns: "http://www.w3.org/2000/svg",
width: 24,
height: 24,
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
strokeWidth: 2,
strokeLinecap: "round",
strokeLinejoin: "round"
};
/**
* lucide-react v0.0.1 - ISC
*/
const toKebabCase = (string) => string.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
const createLucideIcon = (iconName, iconNode) => {
const Component = React.forwardRef(
({ color = "currentColor", size = 24, strokeWidth = 2, absoluteStrokeWidth, children, ...rest }, ref) => React.createElement(
"svg",
{
ref,
...defaultAttributes,
width: size,
height: size,
stroke: color,
strokeWidth: absoluteStrokeWidth ? Number(strokeWidth) * 24 / Number(size) : strokeWidth,
className: `lucide lucide-${toKebabCase(iconName)}`,
...rest
},
[
...iconNode.map(([tag, attrs]) => React.createElement(tag, attrs)),
...(Array.isArray(children) ? children : [children]) || []
]
)
);
Component.displayName = `${iconName}`;
return Component;
};
var createLucideIcon$1 = createLucideIcon;
/**
* lucide-react v0.0.1 - ISC
*/
const Bot = createLucideIcon$1("Bot", [
[
"rect",
{ width: "18", height: "10", x: "3", y: "11", rx: "2", key: "1ofdy3" }
],
["circle", { cx: "12", cy: "5", r: "2", key: "f1ur92" }],
["path", { d: "M12 7v4", key: "xawao1" }],
["line", { x1: "8", x2: "8", y1: "16", y2: "16", key: "h6x27f" }],
["line", { x1: "16", x2: "16", y1: "16", y2: "16", key: "5lty7f" }]
]);
/**
* lucide-react v0.0.1 - ISC
*/
const Brain = createLucideIcon$1("Brain", [
[
"path",
{
d: "M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96.44 2.5 2.5 0 0 1-2.96-3.08 3 3 0 0 1-.34-5.58 2.5 2.5 0 0 1 1.32-4.24 2.5 2.5 0 0 1 1.98-3A2.5 2.5 0 0 1 9.5 2Z",
key: "1mhkh5"
}
],
[
"path",
{
d: "M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96.44 2.5 2.5 0 0 0 2.96-3.08 3 3 0 0 0 .34-5.58 2.5 2.5 0 0 0-1.32-4.24 2.5 2.5 0 0 0-1.98-3A2.5 2.5 0 0 0 14.5 2Z",
key: "1d6s00"
}
]
]);
/**
* lucide-react v0.0.1 - ISC
*/
const Check = createLucideIcon$1("Check", [
["polyline", { points: "20 6 9 17 4 12", key: "10jjfj" }]
]);
/**
* lucide-react v0.0.1 - ISC
*/
const Download = createLucideIcon$1("Download", [
["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4", key: "ih7n3h" }],
["polyline", { points: "7 10 12 15 17 10", key: "2ggqvy" }],
["line", { x1: "12", x2: "12", y1: "15", y2: "3", key: "1vk2je" }]
]);
/**
* lucide-react v0.0.1 - ISC
*/
const MapPin = createLucideIcon$1("MapPin", [
[
"path",
{ d: "M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z", key: "2oe9fu" }
],
["circle", { cx: "12", cy: "10", r: "3", key: "ilqhr7" }]
]);
/**
* lucide-react v0.0.1 - ISC
*/
const PenLine = createLucideIcon$1("PenLine", [
["path", { d: "M12 20h9", key: "t2du7b" }],
[
"path",
{ d: "M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z", key: "ymcmye" }
]
]);
/**
* lucide-react v0.0.1 - ISC
*/
const Send = createLucideIcon$1("Send", [
["path", { d: "m22 2-7 20-4-9-9-4Z", key: "1q3vgg" }],
["path", { d: "M22 2 11 13", key: "nzbqef" }]
]);
/**
* lucide-react v0.0.1 - ISC
*/
const Upload = createLucideIcon$1("Upload", [
["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4", key: "ih7n3h" }],
["polyline", { points: "17 8 12 3 7 8", key: "t8dd8p" }],
["line", { x1: "12", x2: "12", y1: "3", y2: "15", key: "widbto" }]
]);
/**
* lucide-react v0.0.1 - ISC
*/
const User = createLucideIcon$1("User", [
["path", { d: "M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2", key: "975kel" }],
["circle", { cx: "12", cy: "7", r: "4", key: "17ys0d" }]
]);
/**
* lucide-react v0.0.1 - ISC
*/
const X = createLucideIcon$1("X", [
["path", { d: "M18 6 6 18", key: "1bl5f8" }],
["path", { d: "m6 6 12 12", key: "d8bk6v" }]
]);
// API Service Class
// Create a persistent API service instance
let apiServiceInstance = null;
class BrainKBAPIService {
constructor(config) {
this.sessionId = null;
this.authToken = null;
this.refreshToken = null;
this.tokenExpiry = null;
this.config = config;
}
// JWT Authentication Methods
async authenticate() {
const { auth } = this.config.api || {};
console.log('🔐 Starting authentication...', {
enabled: auth?.enabled,
type: auth?.type,
hasJwtEndpoint: !!auth?.jwtEndpoint,
hasUsername: !!auth?.username,
hasPassword: !!auth?.password
});
if (!auth?.enabled || auth.type !== 'jwt') {
console.log('❌ JWT authentication not enabled or wrong type');
return null;
}
// Check if we have a valid token
if (this.authToken && this.tokenExpiry && Date.now() < this.tokenExpiry) {
console.log('✅ Using existing valid token');
return this.authToken;
}
// Check if we have a refresh token and auto-refresh is enabled
if (this.refreshToken && auth.autoRefresh) {
try {
console.log('🔄 Attempting token refresh...');
return await this.refreshAuthToken();
}
catch (error) {
console.warn('Failed to refresh token, will re-authenticate:', error);
}
}
// Perform initial authentication
console.log('🔑 Performing initial authentication...');
return await this.performAuthentication();
}
async performAuthentication() {
const { auth } = this.config.api || {};
if (!auth?.jwtEndpoint || !auth.username || !auth.password) {
console.warn('JWT authentication enabled but missing required credentials');
console.log('🔍 Credentials check:', {
hasJwtEndpoint: !!auth?.jwtEndpoint,
hasUsername: !!auth?.username,
hasPassword: !!auth?.password
});
return null;
}
try {
console.log('🌐 Making authentication request to:', auth.jwtEndpoint);
// Build request body - support both 'username' and 'email' fields
const requestBody = {
password: auth.password,
};
// Use email field by default, or override with emailField config
const emailField = auth.emailField || 'email';
requestBody[emailField] = auth.username;
console.log(`📧 Using ${emailField} field for authentication`);
const response = await fetch(auth.jwtEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestBody),
});
console.log('📡 Authentication response status:', response.status);
if (!response.ok) {
const errorText = await response.text();
console.error('❌ Authentication failed:', {
status: response.status,
statusText: response.statusText,
errorText
});
throw new Error(`Authentication failed: ${response.status} ${response.statusText}`);
}
const data = await response.json();
console.log('📦 Authentication response received');
// Extract tokens based on configuration
const tokenKey = auth.tokenKey || 'access_token' || 'token';
const refreshTokenKey = auth.refreshTokenKey || 'refresh_token';
this.authToken = data[tokenKey];
this.refreshToken = data[refreshTokenKey];
// Calculate token expiry (default to 1 hour if not provided)
const expiresIn = data.expires_in || 3600;
this.tokenExpiry = Date.now() + (expiresIn * 1000);
console.log('🔐 JWT authentication successful');
return this.authToken;
}
catch (error) {
console.error('❌ JWT authentication failed:', error);
return null;
}
}
async refreshAuthToken() {
const { auth } = this.config.api || {};
if (!auth?.jwtEndpoint || !this.refreshToken) {
return null;
}
try {
const response = await fetch(auth.jwtEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
refresh_token: this.refreshToken,
}),
});
if (!response.ok) {
throw new Error(`Token refresh failed: ${response.status} ${response.statusText}`);
}
const data = await response.json();
const tokenKey = auth.tokenKey || 'access_token' || 'token';
const refreshTokenKey = auth.refreshTokenKey || 'refresh_token';
this.authToken = data[tokenKey];
this.refreshToken = data[refreshTokenKey];
const expiresIn = data.expires_in || 3600;
this.tokenExpiry = Date.now() + (expiresIn * 1000);
console.log('🔄 JWT token refreshed successfully');
return this.authToken;
}
catch (error) {
console.error('❌ JWT token refresh failed:', error);
// Clear invalid tokens
this.authToken = null;
this.refreshToken = null;
this.tokenExpiry = null;
return null;
}
}
async getAuthHeaders() {
const headers = {
'Content-Type': 'application/json',
};
// Add JWT token if available
const token = await this.authenticate();
console.log('🔐 Authentication result:', token ? 'Token obtained' : 'No token');
if (token) {
headers['Authorization'] = `Bearer ${token}`;
console.log('🔑 Authorization header set:', `Bearer ${token.substring(0, 20)}...`);
}
else {
console.warn('⚠️ No JWT token available for request');
}
// Add any additional headers from config
if (this.config.api?.headers) {
Object.assign(headers, this.config.api.headers);
}
return headers;
}
async sendMessage(message, context, onStream) {
const { api } = this.config;
if (api?.endpoint) {
// Check if streaming is enabled via config or query parameter
const url = new URL(api.endpoint);
const isStreamingFromQuery = url.searchParams.get('stream') === 'true';
const isStreaming = api.streaming || isStreamingFromQuery;
if (isStreaming) {
return this.sendStreamingMessage(message, context, onStream);
}
else {
return this.sendRESTMessage(message, context);
}
}
// Fallback to local response
return this.generateLocalResponse(message, context);
}
async sendStreamingMessage(message, context, onStream) {
try {
const requestBody = {
message,
session_id: this.sessionId,
currentPage: context?.currentPage,
pageContext: context?.pageContext,
pageContent: context?.pageContent,
selectedPageContent: context?.selectedPageContent,
chatHistory: context?.chatHistory,
timestamp: new Date().toISOString(),
};
const endpoint = this.config.api.endpoint;
console.log('📤 Sending streaming request to API:', {
endpoint: endpoint,
sessionId: this.sessionId,
messageLength: message.length,
hasContext: !!context
});
const headers = await this.getAuthHeaders();
headers['Accept'] = 'text/event-stream';
const response = await fetch(endpoint, {
method: 'POST',
headers,
body: JSON.stringify(requestBody),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const reader = response.body?.getReader();
if (!reader) {
throw new Error('No response body reader available');
}
const decoder = new TextDecoder();
let fullContent = '';
let sessionId = this.sessionId;
while (true) {
const { done, value } = await reader.read();
if (done)
break;
const chunk = decoder.decode(value, { stream: true });
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6); // Remove 'data: ' prefix
if (data === '[DONE]') {
// Stream ended
break;
}
try {
const parsed = JSON.parse(data);
// Handle session ID
if (parsed.session_id) {
sessionId = parsed.session_id;
this.sessionId = sessionId;
console.log('🔗 Session ID received and stored:', sessionId);
}
// Handle content chunks
if (parsed.content) {
fullContent += parsed.content;
onStream?.(parsed.content);
}
// Handle other fields
if (parsed.type === 'error') {
console.error('Streaming API Error:', parsed.error);
throw new Error(parsed.error || 'Streaming API error');
}
}
catch (parseError) {
// If it's not JSON, treat as plain text content
if (data.trim()) {
fullContent += data;
onStream?.(data);
}
}
}
}
}
return {
content: fullContent,
session_id: sessionId,
parsed_content: this.parseResponse(fullContent)
};
}
catch (error) {
console.error('Streaming API Error:', error);
return this.generateLocalResponse(message, context);
}
}
async sendRESTMessage(message, context) {
try {
// Try simplified request first, then fallback to full request
let requestBody = {
message,
session_id: this.sessionId,
};
// If the first request fails, try with more context
if (context) {
requestBody = {
message,
session_id: this.sessionId,
currentPage: context?.currentPage,
pageContext: context?.pageContext,
pageContent: context?.pageContent,
selectedPageContent: context?.selectedPageContent,
chatHistory: context?.chatHistory,
timestamp: new Date().toISOString(),
// Add action-specific fields if they exist
...(context?.action && { action: context.action }),
...(context?.actionLabel && { actionLabel: context.actionLabel }),
...(context?.actionDescription && { actionDescription: context.actionDescription }),
};
}
console.log('📤 Sending request to API:', {
endpoint: this.config.api.endpoint,
sessionId: this.sessionId,
messageLength: message.length,
hasContext: !!context,
requestBody: requestBody
});
const headers = await this.getAuthHeaders();
const response = await fetch(this.config.api.endpoint, {
method: 'POST',
headers,
body: JSON.stringify(requestBody),
});
if (!response.ok) {
const errorText = await response.text();
console.error('❌ API Error:', {
status: response.status,
statusText: response.statusText,
errorText: errorText
});
// Try to parse error response
let errorData;
try {
errorData = JSON.parse(errorText);
}
catch {
errorData = { detail: errorText };
}
throw new Error(`API Error ${response.status}: ${JSON.stringify(errorData)}`);
}
const responseData = await response.json();
// Debug the response data
console.log('📥 API Response Data:', responseData);
console.log('📥 Response Data Type:', typeof responseData);
console.log('📥 Response Data Keys:', Object.keys(responseData || {}));
// Store session ID from response for future requests
if (responseData.session_id) {
this.sessionId = responseData.session_id;
console.log('🔗 Session ID received and stored:', this.sessionId);
}
// Parse the response content dynamically
const parsedContent = this.parseResponse(responseData);
return {
...responseData,
parsed_content: parsedContent
};
}
catch (error) {
console.error('REST API Error:', error);
return this.generateLocalResponse(message, context);
}
}
generateLocalResponse(message, context) {
// Generate contextual response based on message content and page context
const responses = {
greeting: "Hello! I'm your BrainKB Assistant. How can I help you today?",
question: "I understand your question. Let me help you find the information you need.",
knowledge: "I can help you explore the knowledge base and find relevant information.",
default: "I'm here to help! What would you like to know about?"
};
const lowerMessage = message.toLowerCase();
// If selected page content is available, provide more contextual response
if (context?.selectedPageContent) {
const selectedContent = context.selectedPageContent;
return {
content: `I can see you've selected specific content from the page (${selectedContent.length} characters). I can help you analyze this content and answer questions about it. What would you like to know about the selected text?`
};
}
// If page content is available, provide more contextual response
if (context?.pageContent) {
context.pageContent;
const pageContext = context.pageContext;
return {
content: `I can see you're on the ${pageContext?.title || 'current page'}. I have access to the page content and can help you with questions about what's displayed here. What would you like to know about this page?`
};
}
// If page context is available but no content
if (context?.pageContext) {
return {
content: `I can help you with questions about ${context.pageContext.title || 'this page'}. What would you like to know?`
};
}
// Default responses based on message content
if (lowerMessage.includes('hello') || lowerMessage.includes('hi')) {
return { content: responses.greeting };
}
else if (lowerMessage.includes('?')) {
return { content: responses.question };
}
else if (lowerMessage.includes('knowledge') || lowerMessage.includes('data')) {
return { content: responses.knowledge };
}
return { content: responses.default };
}
// Dynamic response parser that handles any API response structure
parseResponse(response) {
console.log('🔍 Parsing response:', response);
console.log('🔍 Response type:', typeof response);
// If response is already a string, return it
if (typeof response === 'string') {
console.log('✅ Response is string, returning as-is');
return response;
}
// If response is null or undefined, return default message
if (response === null || response === undefined) {
console.log('⚠️ Response is null/undefined, returning default');
return 'I received your message but got an empty response. Please try again.';
}
// If response is an object, try to extract content
if (typeof response === 'object') {
console.log('🔍 Response keys:', Object.keys(response));
// Common response content field names
const contentFields = [
'content', 'message', 'response', 'text', 'data', 'answer', 'reply',
'result', 'output', 'body', 'value', 'description', 'summary'
];
// Try to find content in common fields
for (const field of contentFields) {
if (response[field] !== undefined && response[field] !== null) {
console.log(`✅ Found content in field '${field}':`, response[field]);
return this.stringifyIfNeeded(response[field]);
}
}
// If no content field found, check if the object itself is the content
// (e.g., if the API returns the message directly as an object)
if (response.toString && response.toString() !== '[object Object]') {
console.log('✅ Using response.toString():', response.toString());
return response.toString();
}
// If it's an array, try to join it or take the first element
if (Array.isArray(response)) {
if (response.length === 0) {
console.log('⚠️ Response is empty array');
return 'I received an empty response. Please try again.';
}
// If array has one element, use it
if (response.length === 1) {
console.log('✅ Using first array element:', response[0]);
return this.stringifyIfNeeded(response[0]);
}
// If array has multiple elements, join them
console.log('✅ Joining array elements');
return response.map(item => this.stringifyIfNeeded(item)).join('\n\n');
}
// Last resort: stringify the entire response
console.log('⚠️ No content field found, stringifying entire response');
return JSON.stringify(response, null, 2);
}
// For any other type, convert to string
console.log('✅ Converting response to string:', String(response));
return String(response);
}
// Helper method to stringify objects if needed
stringifyIfNeeded(value) {
if (typeof value === 'string') {
return value;
}
if (typeof value === 'object' && value !== null) {
// If it's a simple object with a few properties, format it nicely
const keys = Object.keys(value);
if (keys.length <= 5) {
try {
return JSON.stringify(value, null, 2);
}
catch {
return String(value);
}
}
// For complex objects, just stringify
return JSON.stringify(value, null, 2);
}
return String(value);
}
}
// Code Block Component with Syntax Highlighting
const CodeBlock = ({ code, language = 'javascript' }) => {
const copyToClipboard = async () => {
try {
await navigator.clipboard.writeText(code);
}
catch (err) {
console.error('Failed to copy: ', err);
}
};
return (jsxRuntime.jsxs("div", { className: "bg-gray-900 rounded-lg overflow-hidden border border-gray-700 my-2", style: {
maxWidth: '100%',
width: '100%'
}, children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-4 py-2 bg-gray-800 border-b border-gray-700", children: [jsxRuntime.jsx("span", { className: "text-xs font-medium text-gray-300 uppercase", children: language }), jsxRuntime.jsx("button", { onClick: copyToClipboard, className: "text-xs text-gray-400 hover:text-white transition-colors", title: "Copy to clipboard", children: "Copy" })] }), jsxRuntime.jsx("pre", { className: "p-4 overflow-x-auto", style: {
fontSize: '13px',
lineHeight: '1.4',
maxWidth: '100%',
width: '100%',
wordWrap: 'break-word',
overflowWrap: 'break-word'
}, children: jsxRuntime.jsx("code", { className: "text-gray-100", style: {
wordWrap: 'break-word',
overflowWrap: 'break-word',
width: '100%'
}, children: code }) })] }));
};
// Helper function to extract table data for download (standalone)
const extractTableDataForDownload = (headers, rows) => {
const dataRows = [];
rows.forEach((row, index) => {
if (index === 0)
return; // Skip header row
const cells = row.split('|').filter(cell => cell.trim());
if (cells.length > 0) {
dataRows.push(cells.map(cell => cell.trim()));
}
});
return {
headers: headers,
rows: dataRows
};
};
// Enhanced Table Component with Download
const EnhancedTable = ({ headers, rows, tableId }) => {
const [tableData] = React.useState(() => extractTableDataForDownload(headers, rows));
const handleDownload = (format) => {
console.log(`📥 Downloading table ${tableId} as ${format}`);
// You can add analytics or tracking here
};
return (jsxRuntime.jsxs("div", { className: "table-container", style: {
background: 'white',
borderRadius: '16px',
border: '1px solid #e2e8f0',
overflow: 'hidden',
margin: '16px 0',
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.08)',
maxWidth: '100%',
boxSizing: 'border-box',
width: '100%'
}, children: [jsxRuntime.jsxs("div", { className: "table-header", style: {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '24px 28px 20px 28px',
borderBottom: '2px solid #e2e8f0',
background: 'linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)',
position: 'relative',
minHeight: '70px',
boxSizing: 'border-box'
}, children: [jsxRuntime.jsx("div", { className: "table-title", style: {
fontSize: '18px',
color: '#1e293b',
fontWeight: '700',
display: 'flex',
alignItems: 'center',
gap: '12px',
textShadow: '0 1px 2px rgba(0, 0, 0, 0.1)',
flex: '1',
wordWrap: 'break-word',
overflowWrap: 'break-word',
maxWidth: '100%'
}, children: "\uD83D\uDCCA Query Results Table" }), jsxRuntime.jsx("div", { className: "download-container", style: {
position: 'relative',
display: 'inline-block',
zIndex: 10
}, children: jsxRuntime.jsx(DownloadDataButton, { data: tableData, filename: `query-results-${tableId}`, onDownload: handleDownload }) })] }), jsxRuntime.jsx("div", { style: {
overflowX: 'auto',
maxWidth: '100%',
width: '100%'
}, children: jsxRuntime.jsxs("table", { style: {
width: '100%',
borderCollapse: 'collapse',
fontSize: '13px',
lineHeight: '1.4',
tableLayout: 'fixed',
wordWrap: 'break-word',
overflowWrap: 'break-word',
minWidth: '100%'
}, children: [jsxRuntime.jsx("thead", { style: {
background: 'linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)',
borderBottom: '2px solid #e2e8f0'
}, children: jsxRuntime.jsx("tr", { children: headers.map((header, index) => (jsxRuntime.jsx("th", { style: {
padding: '12px 16px',
textAlign: 'left',
fontWeight: '600',
color: '#1e293b',
fontSize: '13px',
borderBottom: '1px solid #e2e8f0',
background: 'linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)',
wordWrap: 'break-word',
overflowWrap: 'break-word',
maxWidth: '120px',
minWidth: '80px',
whiteSpace: 'normal',
verticalAlign: 'top'
}, children: header }, index))) }) }), jsxRuntime.jsx("tbody", { children: rows.map((row, rowIndex) => {
if (rowIndex === 0)
return null; // Skip header row
const cells = row.split('|').filter(cell => cell.trim());
if (cells.length > 0) {
return (jsxRuntime.jsx("tr", { style: {
borderBottom: '1px solid #f1f5f9',
transition: 'background-color 0.2s ease',
backgroundColor: rowIndex % 2 === 0 ? 'transparent' : '#fafbfc'
}, children: cells.map((cell, cellIndex) => {
const cellContent = cell.trim();
let cellStyle = {
padding: '12px 16px',
color: '#374151',
fontSize: '12px',
lineHeight: '1.4',
verticalAlign: 'top',
wordWrap: 'break-word',
overflowWrap: 'break-word',
hyphens: 'auto',
maxWidth: '120px',
minWidth: '80px',
whiteSpace: 'normal'
};
// Apply special styling based on content type
if (cellIndex === 0 && /^\d+$/.test(cellContent)) {
// Index cell styling
cellStyle = {
...cellStyle,
fontWeight: '600',
color: '#1e293b',
textAlign: 'center',
background: 'linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%)',
borderRadius: '8px',
padding: '6px 10px',
minWidth: '40px',
display: 'inline-block',
border: '1px solid #bae6fd',
fontSize: '12px'
};
}
else if (cellContent.includes('ncbitaxon:') || cellContent.includes('http') || cellContent.includes('://') || cellContent.includes('GCF_') || cellContent.includes('GCA_')) {
// URI cell styling
cellStyle = {
...cellStyle,
fontFamily: "'Monaco', 'Menlo', 'Ubuntu Mono', monospace",
fontSize: '11px',
color: '#0369a1',
background: 'linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%)',
borderRadius: '6px',
padding: '4px 8px',
border: '1px solid #bae6fd',
wordBreak: 'break-all',
overflowWrap: 'break-word',
maxWidth: '100%'
};
}
return (jsxRuntime.jsx("td", { style: cellStyle, children: cellContent }, cellIndex));
}) }, rowIndex));
}
return null;
}) })] }) })] }));
};
// Markdown Renderer Component
const MarkdownRenderer = ({ content }) => {
const renderContent = (text) => {
const elements = [];
let currentIndex = 0;
// Split by code blocks first
const parts = text.split(/(```[\s\S]*?```)/);
for (const part of parts) {
if (part.startsWith('```')) {
// Extract language and code
const match = part.match(/```(\w+)?\n([\s\S]*?)```/);
if (match) {
const language = match[1] || 'text';
const code = match[2];
elements.push(jsxRuntime.jsx(CodeBlock, { code: code, language: language }, currentIndex++));
continue;
}
}
// Enhanced markdown processing with better structure handling
let processedText = part;
// Clean up repeated content patterns (common in AI responses)
processedText = processedText
.replace(/(Certainly! Let's break down and analyze[^.]*\.)/g, '')
.replace(/(Based on your data, the following entities are central:)/g, '### Key Entities Identified')
.replace(/(The relationships between these entities can be visualized as follows:)/g, '### Relationships and Data Flow')
.replace(/(In a knowledge graph, these entities and relationships might look like:)/g, '### Knowledge Graph Representation')
.replace(/(Would you like a visual diagram[^?]*\?)/g, '');
// Format query result summaries
processedText = processedText
.replace(/Here is a (?:formatted summary|clear and concise formatting) of your query results:/g, '<div class="query-summary"><strong>📊 Query Results Summary</strong></div>')
.replace(/Variable:\s*(\w+)/g, '<div class="query-summary">Variable: <strong>$1</strong></div>')
.replace(/Results:\s*(\d+)\s*entries?/g, '<div class="query-summary">Results: <strong>$1 entries</strong></div>');
// Enhanced table detection and formatting
const lines = processedText.split('\n');
const processedLines = [];
let inTable = false;
let tableRows = [];
let tableHeaders = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// Detect table structure (lines with | and ---)
if (line.includes('|') && line.trim().startsWith('|') && line.trim().endsWith('|')) {
if (!inTable) {
inTable = true;
tableRows = [];
tableHeaders = [];
}
// Extract headers from first row
if (tableHeaders.length === 0) {
const headerCells = line.split('|').filter(cell => cell.trim());
tableHeaders = headerCells.map(cell => cell.trim());
}
// Extract data cells
const cells = line.split('|').filter(cell => cell.trim());
if (cells.length > 0) {
tableRows.push(line);
}
}
else if (line.includes('---') && inTable) {
// Skip separator lines
continue;
}
else if (inTable && !line.includes('|')) {
// End of table
inTable = false;
// Convert table to React component
if (tableRows.length > 0) {
const tableId = `table-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
elements.push(jsxRuntime.jsx(EnhancedTable, { headers: tableHeaders, rows: tableRows, tableId: tableId }, currentIndex++));
}
// Add the current line as regular text
if (line.trim()) {
processedLines.push(line);
}
}
else if (inTable) {
// Continue building table
tableRows.push(line);
}
else {
// Regular text processing
processedLines.push(line);
}
}
// Handle table at end of content
if (inTable && tableRows.length > 0) {
const tableId = `table-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
elements.push(jsxRuntime.jsx(EnhancedTable, { headers: tableHeaders, rows: tableRows, tableId: tableId }, currentIndex++));
}
processedText = processedLines.join('\n');
// Headers (h1-h6) - improved regex to handle edge cases
processedText = processedText
.replace(/^#{6}\s+(.+?)(?:\n|$)/gm, '<h6 style="font-size: 1rem; font-weight: 600; margin: 1rem 0 0.5rem 0; color: #374151;">$1</h6>')
.replace(/^#{5}\s+(.+?)(?:\n|$)/gm, '<h5 style="font-size: 1.125rem; font-weight: 600; margin: 1rem 0 0.5rem 0; color: #374151;">$1</h5>')
.replace(/^#{4}\s+(.+?)(?:\n|$)/gm, '<h4 style="font-size: 1.25rem; font-weight: 600; margin: 1rem 0 0.5rem 0; color: #374151;">$1</h4>')
.replace(/^#{3}\s+(.+?)(?:\n|$)/gm, '<h3 style="font-size: 1.5rem; font-weight: 600; margin: 1rem 0 0.5rem 0; color: #374151;">$1</h3>')
.replace(/^#{2}\s+(.+?)(?:\n|$)/gm, '<h2 style="font-size: 1.875rem; font-weight: 600; margin: 1rem 0 0.5rem 0; color: #374151;">$1</h2>')
.replace(/^#{1}\s+(.+?)(?:\n|$)/gm, '<h1 style="font-size: 2.25rem; font-weight: 600; margin: 1rem 0 0.5rem 0; color: #374151;">$1</h1>');
// Bold and italic - improved to handle nested patterns
processedText = processedText
.replace(/\*\*(.*?)\*\*/g, '<strong style="font-weight: 600;">$1</strong>')
.replace(/\*(.*?)\*/g, '<em style="font-style: italic;">$1</em>')
.replace(/__(.*?)__/g, '<strong style="font-weight: 600;">$1</strong>')
.replace(/_(.*?)_/g, '<em style="font-style: italic;">$1</em>');
// Inline code
processedText = processedText
.replace(/`([^`]+)`/g, '<code style="background-color: #f3f4f6; padding: 0.125rem 0.25rem; border-radius: 0.25rem; font-size: 0.875rem; font-family: monospace; color: #1f2937;">$1</code>');
// Links
processedText = processedText
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" style="color: #3b82f6; text-decoration: underline;" target="_blank" rel="noopener noreferrer">$1</a>');
// Lists - improved to handle numbered and bulleted lists better
processedText = processedText
.replace(/^\s*[-*+]\s+(.+?)(?:\n|$)/gm, '<li style="margin: 0.25rem 0; padding-left: 0.5rem;">$1</li>')
.replace(/^\s*\d+\.\s+(.+?)(?:\n|$)/gm, '<li style="margin: 0.25rem 0; padding-left: 0.5rem;">$1</li>');
// Wrap consecutive list items in ul/ol
processedText = processedText
.replace(/(<li[^>]*>.*?<\/li>)(?:\s*<li[^>]*>.*?<\/li>)*/gs, (match) => {
return `<ul style="margin: 0.5rem 0; padding-left: 1.5rem; list-style-type: disc;">${match}</ul>`;
});
// Blockquotes
processedText = processedText
.replace(/^>\s+(.+?)(?:\n|$)/gm, '<blockquote style="border-left: 4px solid #e5e7eb; padding-left: 1rem; margin: 1rem 0; color: #6b7280; font-style: italic;">$1</blockquote>');
// Horizontal rules
processedText = processedText
.replace(/^---+$/gm, '<hr style="border: none; border-top: 1px solid #e5e7eb; margin: 1rem 0;">');
// Paragraphs - wrap text in paragraphs for better structure
processedText = processedText
.split('\n\n')
.map(paragraph => {
if (paragraph.trim() && !paragraph.match(/^<[^>]+>/) && !paragraph.match(/^#{1,6}\s/)) {
return `<p style="margin: 0.75rem 0; line-height: 1.6;">${paragraph.trim()}</p>`;
}
return paragraph;
})
.join('\n\n');
// Line breaks within paragraphs
processedText = processedText
.replace(/\n(?!\n)/g, '<br>');
// Add processed text as HTML element
if (processedText.trim()) {
elements.push(jsxRuntime.jsx("div", { style: {
fontSize: '14px',
lineHeight: '1.6',
wordWrap: 'break-word',
overflowWrap: 'break-word',
maxWidth: '100%',
width: '100%',
color: '#374151'
}, dangerouslySetInnerHTML: { __html: processedText } }, currentIndex++));
}
}
return elements;
};
return (jsxRuntime.jsx("div", { style: {
wordWrap: 'break-word',
overflowWrap: 'break-word',
maxWidth: '100%',
width: '100%'
}, children: renderContent(content) }));
};
// File Renderer Component
const FileRenderer = ({ file, content }) => {
const [imageUrl, setImageUrl] = React.useState(null);
const getLanguage = (filename) => {
const ext = filename.split('.').pop()?.toLowerCase();
switch (ext) {
case 'json': return 'json';
case 'jsonld': return 'json';
case 'ttl': return 'turtle';
case 'csv': return 'csv';
case 'txt': return 'text';
default: return 'text';
}
};
const isImageFile = (filename) => {
const ext = filename.split('.').pop()?.toLowerCase();
return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'].includes(ext || '');
};
const formatContent = (content, filename) => {
const ext = filename.split('.').pop()?.toLowerCase();
if (ext === 'json' || ext === 'jsonld') {
try {
return JSON.stringify(JSON.parse(content), null, 2);
}
catch {
return content;
}
}
if (ext === 'csv') {
return content;
}
if (ext === 'ttl') {
return content;
}
return content;
};
// Handle image files
React.useEffect(() => {
if (isImageFile(file.name)) {
const url = URL.createObjectURL(file);
setImageUrl(url);
// Cleanup URL when component unmounts
return () => URL.revokeObjectURL(url);
}
}, [file]);
// Render image files
if (isImageFile(file.name) && imageUrl) {
return (jsxRuntime.jsxs("div", { className: "bg-gray-50 rounded-lg p-4 border border-gray-200", children: [jsxRuntime.jsx("div", { className: "flex items-center justify-between mb-3", children: jsxRuntime.jsxs("div", { className: "flex items-center space-x-2", children: [jsxRuntime.jsx(Upload, { className: "w-4 h-4 text-gray-500" }), jsxRuntime.jsx("span", { className: "text-sm font-medium text-gray-700", children: file.name }), jsxRuntime.jsxs("span", { className: "text-xs text-gray-500", children: ["(", (file.size / 1024).toFixed(1), " KB)"] })] }) }), jsxRuntime.jsx("div", { className: "flex justify-center", children: jsxRuntime.jsx("img", { src: imageUrl, alt: file.name, className: "max-w-full max-h-96 rounded-lg shadow-md", style: { objectFit: 'contain' } }) })] }));
}
// Render text files
if (content) {
return (jsxRuntime.jsxs("div", { className: "bg-gray-50 rounded-lg p-4 border border-gray-200", children: [jsxRuntime.jsx("div", { className: "flex items-c