n8n-nodes-puter-ai
Version:
Advanced n8n node for Puter.js AI with RAG agentic capabilities, document processing, audio transcription, Supabase integration, and cost-optimized model priorities
1,773 lines • 77.9 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PuterAi = void 0;
const n8n_workflow_1 = require("n8n-workflow");
const axios_1 = __importDefault(require("axios"));
// Optional dependencies - will be loaded dynamically when needed
let createClient;
let pdfParse;
let mammoth;
// Dynamic imports for optional dependencies
async function loadSupabase() {
if (!createClient) {
try {
const supabase = await Promise.resolve().then(() => __importStar(require('@supabase/supabase-js')));
createClient = supabase.createClient;
}
catch (error) {
throw new Error('Supabase is required for RAG operations. Please install: npm install @supabase/supabase-js');
}
}
return createClient;
}
async function loadPdfParse() {
if (!pdfParse) {
try {
pdfParse = (await Promise.resolve().then(() => __importStar(require('pdf-parse')))).default;
}
catch (error) {
throw new Error('pdf-parse is required for PDF processing. Please install: npm install pdf-parse');
}
}
return pdfParse;
}
async function loadMammoth() {
if (!mammoth) {
try {
mammoth = await Promise.resolve().then(() => __importStar(require('mammoth')));
}
catch (error) {
throw new Error('mammoth is required for DOCX processing. Please install: npm install mammoth');
}
}
return mammoth;
}
class PuterAi {
constructor() {
this.description = {
displayName: 'Puter AI',
name: 'puterAi',
icon: 'file:puter.svg',
group: ['transform'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["model"]}}',
description: 'Interact with Puter AI with automatic authentication and fallback',
defaults: {
name: 'Puter AI',
},
inputs: ["main" /* NodeConnectionType.Main */],
outputs: ["main" /* NodeConnectionType.Main */],
credentials: [
{
name: 'puterAiApi',
required: true,
},
{
name: 'supabaseApi',
required: false,
displayOptions: {
show: {
operation: ['documentProcessing', 'agenticRag', 'vectorSearch'],
},
},
},
],
properties: [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Chat Completion',
value: 'chatCompletion',
description: 'Generate AI chat completion',
action: 'Generate chat completion',
},
{
name: 'RAG Chat',
value: 'ragChat',
description: 'Chat with RAG context',
action: 'Chat with RAG context',
},
{
name: 'Document Processing',
value: 'documentProcessing',
description: 'Process and store documents for RAG',
action: 'Process documents',
},
{
name: 'Agentic RAG',
value: 'agenticRag',
description: 'Intelligent document-based reasoning',
action: 'Perform agentic RAG',
},
{
name: 'Vector Search',
value: 'vectorSearch',
description: 'Search documents by semantic similarity',
action: 'Search documents',
},
{
name: 'Transcribe Audio',
value: 'transcribe',
description: 'Transcribe audio files to text',
action: 'Transcribe audio',
},
{
name: 'Upload Document',
value: 'uploadDocument',
description: 'Upload and process document for RAG',
action: 'Upload document',
},
],
default: 'chatCompletion',
},
{
displayName: 'Model Selection Strategy',
name: 'modelStrategy',
type: 'options',
default: 'credential-priority',
description: 'How to select which model to use',
options: [
{
name: 'Use Credential Priority',
value: 'credential-priority',
description: 'Use models defined in credential settings (recommended)',
},
{
name: 'Override with Specific Model',
value: 'override',
description: 'Override credential settings with a specific model',
},
{
name: 'Auto (Smart Selection)',
value: 'auto',
description: 'Automatically select the best available model',
},
],
},
{
displayName: 'Specific Model',
name: 'model',
type: 'options',
options: [
{ name: 'Gemma 2 27B ($0.10) - Most Cost-Effective', value: 'google/gemma-2-27b-it' },
{ name: 'Gemini 1.5 Flash ($0.225)', value: 'gemini-1.5-flash' },
{ name: 'Gemini 2.0 Flash ($0.30)', value: 'gemini-2.0-flash' },
{ name: 'GPT-5 Nano ($0.35)', value: 'gpt-5-nano' },
{ name: 'GPT-4o Mini ($0.375)', value: 'gpt-4o-mini' },
{ name: 'o4-mini (~$0.40)', value: 'o4-mini' },
{ name: 'GPT-4.1 Nano (~$0.45)', value: 'gpt-4.1-nano' },
{ name: 'Llama 3.1 70B ($0.88)', value: 'meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo' },
{ name: 'GPT-4o (Premium)', value: 'gpt-4o' },
{ name: 'Claude 3.5 Sonnet (Premium)', value: 'claude-3-5-sonnet-20241022' },
{ name: 'o3 (Latest Premium)', value: 'o3' },
{ name: 'o1-pro (Premium)', value: 'o1-pro' },
{ name: 'Auto (Smart Selection)', value: 'auto' },
],
default: 'auto',
description: 'Specific AI model to use (overrides credential settings)',
displayOptions: {
show: {
modelStrategy: ['override', 'auto'],
},
},
},
{
displayName: 'Account & Model Info',
name: 'accountInfo',
type: 'notice',
default: '',
description: 'When using credential priority: Primary account models are tried first, then fallback account models. Check your credentials to see model priorities.',
displayOptions: {
show: {
modelStrategy: ['credential-priority'],
},
},
},
{
displayName: 'Message',
name: 'message',
type: 'string',
typeOptions: {
rows: 4,
},
default: '',
placeholder: 'Enter your message here...',
description: 'The message to send to the AI',
},
{
displayName: 'RAG Context',
name: 'ragContext',
type: 'string',
typeOptions: {
rows: 6,
},
default: '',
placeholder: 'Enter RAG context here...',
description: 'Additional context for RAG-enhanced responses',
displayOptions: {
show: {
operation: ['ragChat'],
},
},
},
{
displayName: 'Auto-Detect Documents',
name: 'autoDetectDocuments',
type: 'boolean',
default: true,
description: 'Automatically detect and process documents from input data (e.g., Telegram files)',
},
{
displayName: 'Auto-Process Settings',
name: 'autoProcessSettings',
type: 'notice',
default: '',
description: 'When auto-detect is enabled, the node will automatically process any files found in the input data and store them in Supabase for RAG functionality.',
displayOptions: {
show: {
autoDetectDocuments: [true],
},
},
},
{
displayName: 'Enable Model Fallback',
name: 'enableModelFallback',
type: 'boolean',
default: true,
description: 'Automatically try alternative models if the selected model fails',
},
{
displayName: 'Document Input',
name: 'documentInput',
type: 'options',
default: 'file',
options: [
{ name: 'File Upload', value: 'file' },
{ name: 'Text Content', value: 'text' },
{ name: 'URL/Link', value: 'url' },
],
description: 'Source of the document to process',
displayOptions: {
show: {
operation: ['documentProcessing'],
},
},
},
{
displayName: 'File Data',
name: 'fileData',
type: 'string',
default: '',
placeholder: 'Binary file data or base64 encoded content',
description: 'The file content to process (from Telegram or other sources)',
displayOptions: {
show: {
operation: ['documentProcessing'],
documentInput: ['file'],
},
},
},
{
displayName: 'File Name',
name: 'fileName',
type: 'string',
default: '',
placeholder: 'document.pdf',
description: 'Name of the file being processed',
displayOptions: {
show: {
operation: ['documentProcessing'],
documentInput: ['file'],
},
},
},
{
displayName: 'Document Text',
name: 'documentText',
type: 'string',
typeOptions: {
rows: 6,
},
default: '',
placeholder: 'Enter document text content...',
description: 'Text content to process and store',
displayOptions: {
show: {
operation: ['documentProcessing'],
documentInput: ['text'],
},
},
},
{
displayName: 'Document URL',
name: 'documentUrl',
type: 'string',
default: '',
placeholder: 'https://example.com/document.pdf',
description: 'URL of the document to download and process',
displayOptions: {
show: {
operation: ['documentProcessing'],
documentInput: ['url'],
},
},
},
{
displayName: 'Document Title',
name: 'documentTitle',
type: 'string',
default: '',
placeholder: 'Document Title',
description: 'Title/name for the document (for metadata)',
displayOptions: {
show: {
operation: ['documentProcessing'],
},
},
},
{
displayName: 'Document Tags',
name: 'documentTags',
type: 'string',
default: '',
placeholder: 'tag1, tag2, tag3',
description: 'Comma-separated tags for document categorization',
displayOptions: {
show: {
operation: ['documentProcessing'],
},
},
},
{
displayName: 'Search Query',
name: 'searchQuery',
type: 'string',
default: '',
placeholder: 'What information are you looking for?',
description: 'Query to search for relevant documents',
displayOptions: {
show: {
operation: ['vectorSearch', 'agenticRag'],
},
},
},
{
displayName: 'Max Results',
name: 'maxResults',
type: 'number',
default: 5,
description: 'Maximum number of documents to retrieve',
typeOptions: {
minValue: 1,
maxValue: 20,
},
displayOptions: {
show: {
operation: ['vectorSearch', 'agenticRag'],
},
},
},
{
displayName: 'Response Format',
name: 'responseFormat',
type: 'options',
options: [
{ name: 'Simple Text', value: 'simple' },
{ name: 'Formatted with Metadata', value: 'formatted' },
{ name: 'Telegram Ready', value: 'telegram' },
{ name: 'Raw Response', value: 'raw' },
],
default: 'formatted',
description: 'How to format the AI response',
displayOptions: {
show: {
operation: ['chatCompletion', 'ragChat', 'agenticRag'],
},
},
},
{
displayName: 'Model Strategy',
name: 'modelStrategy',
type: 'options',
options: [
{ name: 'Use Credential Priority (Cost-Optimized)', value: 'credential-priority' },
{ name: 'Override with Specific Model', value: 'override' },
{ name: 'Auto (Smart Selection)', value: 'auto' },
],
default: 'credential-priority',
description: 'How to select the AI model for processing',
displayOptions: {
show: {
operation: ['chatCompletion', 'ragChat', 'agenticRag'],
},
},
},
// Transcribe-specific parameters
{
displayName: 'Audio File',
name: 'audioFile',
type: 'string',
default: '',
placeholder: 'Binary audio data or file path',
description: 'The audio file to transcribe (from Telegram voice message or file upload)',
displayOptions: {
show: {
operation: ['transcribe'],
},
},
},
{
displayName: 'Audio Format',
name: 'audioFormat',
type: 'options',
options: [
{ name: 'Auto-detect', value: 'auto' },
{ name: 'MP3', value: 'mp3' },
{ name: 'WAV', value: 'wav' },
{ name: 'OGG', value: 'ogg' },
{ name: 'M4A', value: 'm4a' },
{ name: 'FLAC', value: 'flac' },
],
default: 'auto',
description: 'Audio file format (auto-detect recommended)',
displayOptions: {
show: {
operation: ['transcribe'],
},
},
},
{
displayName: 'Language',
name: 'language',
type: 'options',
options: [
{ name: 'Auto-detect', value: 'auto' },
{ name: 'English', value: 'en' },
{ name: 'Spanish', value: 'es' },
{ name: 'French', value: 'fr' },
{ name: 'German', value: 'de' },
{ name: 'Italian', value: 'it' },
{ name: 'Portuguese', value: 'pt' },
{ name: 'Russian', value: 'ru' },
{ name: 'Japanese', value: 'ja' },
{ name: 'Korean', value: 'ko' },
{ name: 'Chinese', value: 'zh' },
],
default: 'auto',
description: 'Language of the audio (auto-detect recommended)',
displayOptions: {
show: {
operation: ['transcribe'],
},
},
},
// Upload Document specific parameters
{
displayName: 'File Content',
name: 'fileContent',
type: 'string',
typeOptions: {
rows: 6,
},
default: '',
placeholder: 'Document content or file data...',
description: 'The content of the document to upload',
displayOptions: {
show: {
operation: ['uploadDocument'],
},
},
},
{
displayName: 'File ID',
name: 'fileId',
type: 'string',
default: '',
placeholder: 'telegram_file_123',
description: 'Unique identifier for the file (e.g., Telegram file ID)',
displayOptions: {
show: {
operation: ['uploadDocument'],
},
},
},
{
displayName: 'User ID',
name: 'userId',
type: 'string',
default: '',
placeholder: 'user123',
description: 'User ID for document ownership',
displayOptions: {
show: {
operation: ['uploadDocument'],
},
},
},
{
displayName: 'Chat ID',
name: 'chatId',
type: 'string',
default: '',
placeholder: 'chat123',
description: 'Chat ID for document context',
displayOptions: {
show: {
operation: ['uploadDocument'],
},
},
},
{
displayName: 'Supabase URL',
name: 'supabaseUrl',
type: 'string',
default: '',
placeholder: 'https://your-project.supabase.co',
description: 'Supabase project URL (optional if using credentials)',
displayOptions: {
show: {
operation: ['uploadDocument'],
},
},
},
{
displayName: 'Supabase API Key',
name: 'supabaseApiKey',
type: 'string',
typeOptions: {
password: true,
},
default: '',
placeholder: 'your-supabase-anon-key',
description: 'Supabase API key (optional if using credentials)',
displayOptions: {
show: {
operation: ['uploadDocument'],
},
},
},
{
displayName: 'Database Password',
name: 'databasePassword',
type: 'string',
typeOptions: {
password: true,
},
default: '',
placeholder: 'your-database-password',
description: 'Database password (optional if using credentials)',
displayOptions: {
show: {
operation: ['uploadDocument'],
},
},
},
],
};
}
async execute() {
const items = this.getInputData();
const returnData = [];
for (let i = 0; i < items.length; i++) {
try {
const operation = this.getNodeParameter('operation', i);
const autoDetectDocuments = this.getNodeParameter('autoDetectDocuments', i, true);
// Auto-detect and process documents from input data
if (autoDetectDocuments && (operation === 'chatCompletion' || operation === 'ragChat' || operation === 'agenticRag')) {
const inputData = items[i].json;
const documentInfo = await detectAndProcessDocuments(this, inputData, i);
if (documentInfo.documentsProcessed > 0) {
// If documents were processed, enhance the response with this information
const enhancedResult = await handleChatWithAutoProcessedDocs(this, i, documentInfo);
returnData.push({ json: enhancedResult });
continue;
}
}
// Handle different operations
if (operation === 'documentProcessing') {
const result = await handleDocumentProcessing(this, i);
returnData.push({ json: result });
continue;
}
else if (operation === 'vectorSearch') {
const result = await handleVectorSearch(this, i);
returnData.push({ json: result });
continue;
}
else if (operation === 'agenticRag') {
const result = await handleAgenticRag(this, i);
returnData.push({ json: result });
continue;
}
else if (operation === 'transcribe') {
const result = await handleTranscribe(this, i);
returnData.push({ json: result });
continue;
}
else if (operation === 'uploadDocument') {
const result = await handleUploadDocument(this, i);
returnData.push({ json: result });
continue;
}
// Handle chat operations (chatCompletion, ragChat)
const result = await handleRegularChat(this, i);
returnData.push({ json: result });
}
catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: {
error: error.message,
},
});
continue;
}
throw error;
}
}
return [returnData];
}
}
exports.PuterAi = PuterAi;
async function handleDocumentProcessing(context, itemIndex) {
const documentInput = context.getNodeParameter('documentInput', itemIndex);
const fileData = context.getNodeParameter('fileData', itemIndex, '');
const fileName = context.getNodeParameter('fileName', itemIndex, '');
const documentText = context.getNodeParameter('documentText', itemIndex, '');
const documentUrl = context.getNodeParameter('documentUrl', itemIndex, '');
const documentTitle = context.getNodeParameter('documentTitle', itemIndex, '');
const documentTags = context.getNodeParameter('documentTags', itemIndex, '');
// Validate required inputs based on document input type
if (documentInput === 'file' && !fileData) {
throw new Error('File data is required when document input is set to "File Upload"');
}
if (documentInput === 'text' && !documentText) {
throw new Error('Document text is required when document input is set to "Text Content"');
}
if (documentInput === 'url' && !documentUrl) {
throw new Error('Document URL is required when document input is set to "URL/Link"');
}
// Get credentials
const puterCredentials = await context.getCredentials('puterAiApi');
let supabaseCredentials;
try {
supabaseCredentials = await context.getCredentials('supabaseApi');
}
catch (error) {
throw new Error('Supabase credentials are required for document processing. Please configure Supabase API credentials.');
}
// Process document
const extractedText = await processDocument(documentInput, fileData, fileName, documentText, documentUrl);
// Generate embeddings
const primaryAccount = {
username: puterCredentials.primaryUsername,
password: puterCredentials.primaryPassword,
};
const authToken = await authenticateAccount(primaryAccount);
const embeddings = await generateEmbeddings(extractedText, authToken);
// Store in Supabase
const supabase = await createSupabaseClient(supabaseCredentials);
const result = await storeDocumentInSupabase(supabase, extractedText, documentTitle, documentTags, fileName, embeddings, supabaseCredentials);
return {
...result,
extractedTextLength: extractedText.length,
embeddingDimension: embeddings.length,
timestamp: new Date().toISOString(),
};
}
async function handleVectorSearch(context, itemIndex) {
const searchQuery = context.getNodeParameter('searchQuery', itemIndex);
const maxResults = context.getNodeParameter('maxResults', itemIndex);
// Validate required inputs
if (!searchQuery || searchQuery.trim() === '') {
throw new Error('Search query is required for vector search operation');
}
// Get credentials
const puterCredentials = await context.getCredentials('puterAiApi');
let supabaseCredentials;
try {
supabaseCredentials = await context.getCredentials('supabaseApi');
}
catch (error) {
throw new Error('Supabase credentials are required for vector search. Please configure Supabase API credentials.');
}
// Generate query embeddings
const primaryAccount = {
username: puterCredentials.primaryUsername,
password: puterCredentials.primaryPassword,
};
const authToken = await authenticateAccount(primaryAccount);
const queryEmbeddings = await generateEmbeddings(searchQuery, authToken);
// Search documents
const supabase = await createSupabaseClient(supabaseCredentials);
const searchResults = await searchDocuments(supabase, queryEmbeddings, maxResults, supabaseCredentials);
return {
query: searchQuery,
resultsCount: searchResults.length,
results: searchResults,
timestamp: new Date().toISOString(),
};
}
async function handleAgenticRag(context, itemIndex) {
const searchQuery = context.getNodeParameter('searchQuery', itemIndex);
const maxResults = context.getNodeParameter('maxResults', itemIndex);
const responseFormat = context.getNodeParameter('responseFormat', itemIndex);
const modelStrategy = context.getNodeParameter('modelStrategy', itemIndex, 'credential-priority');
const model = context.getNodeParameter('model', itemIndex, 'auto');
const enableModelFallback = context.getNodeParameter('enableModelFallback', itemIndex, true);
// Validate required inputs
if (!searchQuery || searchQuery.trim() === '') {
throw new Error('Search query is required for agentic RAG operation');
}
// Get credentials
const puterCredentials = await context.getCredentials('puterAiApi');
let supabaseCredentials;
try {
supabaseCredentials = await context.getCredentials('supabaseApi');
}
catch (error) {
throw new Error('Supabase credentials are required for agentic RAG. Please configure Supabase API credentials.');
}
// Generate query embeddings and search documents
const primaryAccount = {
username: puterCredentials.primaryUsername,
password: puterCredentials.primaryPassword,
};
const authToken = await authenticateAccount(primaryAccount);
const queryEmbeddings = await generateEmbeddings(searchQuery, authToken);
// Search for relevant documents
const supabase = await createSupabaseClient(supabaseCredentials);
const searchResults = await searchDocuments(supabase, queryEmbeddings, maxResults, supabaseCredentials);
// Build context from retrieved documents
const ragContext = searchResults
.map((result, index) => {
const doc = result.documents || result;
const similarity = result.similarity || 'N/A';
return `Document ${index + 1} (Similarity: ${similarity}):\nTitle: ${doc.title || 'Untitled'}\nContent: ${result.content || doc.content || ''}`;
})
.join('\n\n---\n\n');
// Determine model selection based on strategy
let selectedModel = model;
let modelPriority;
if (modelStrategy === 'credential-priority') {
modelPriority = puterCredentials.primaryModels || ['google/gemma-2-27b-it', 'gemini-1.5-flash'];
selectedModel = modelPriority[0];
}
else if (modelStrategy === 'auto') {
selectedModel = 'auto';
}
// Create agentic RAG prompt
const agenticPrompt = `You are an intelligent document analysis agent. Based on the retrieved documents below, provide a comprehensive and accurate answer to the user's question.
Retrieved Documents:
${ragContext}
User Question: ${searchQuery}
Instructions:
1. Analyze the retrieved documents carefully
2. Synthesize information from multiple sources when relevant
3. Provide specific citations or references when possible
4. If the documents don't contain sufficient information, clearly state this
5. Be precise and factual in your response
Answer:`;
// Prepare AI request with agentic RAG context
const aiRequest = buildAiRequest('ragChat', selectedModel, agenticPrompt, '', modelPriority);
// Execute AI request with fallback
const response = await executeAiRequest(aiRequest, authToken, puterCredentials, enableModelFallback, context.getNode(), modelPriority);
// Format response with RAG metadata
const ragResponse = {
...response,
ragMetadata: {
documentsRetrieved: searchResults.length,
query: searchQuery,
contextLength: ragContext.length,
documentsUsed: searchResults.map((r) => {
var _a;
return ({
title: ((_a = r.documents) === null || _a === void 0 ? void 0 : _a.title) || r.title || 'Untitled',
similarity: r.similarity || 'N/A',
});
}),
},
};
return formatResponse(ragResponse, responseFormat, selectedModel);
}
async function detectAndProcessDocuments(context, inputData, itemIndex) {
var _a, _b, _c;
const documentsProcessed = [];
let totalDocuments = 0;
try {
// Check for Telegram file attachments
if ((_a = inputData.message) === null || _a === void 0 ? void 0 : _a.document) {
const doc = inputData.message.document;
const result = await processDetectedDocument(context, doc, 'telegram_document', itemIndex);
documentsProcessed.push(result);
totalDocuments++;
}
// Check for Telegram photo attachments
if ((_b = inputData.message) === null || _b === void 0 ? void 0 : _b.photo) {
const photos = Array.isArray(inputData.message.photo) ? inputData.message.photo : [inputData.message.photo];
for (const photo of photos) {
const result = await processDetectedDocument(context, photo, 'telegram_photo', itemIndex);
documentsProcessed.push(result);
totalDocuments++;
}
}
// Check for generic file attachments in input
if (inputData.file || inputData.files) {
const files = Array.isArray(inputData.files) ? inputData.files : [inputData.file || inputData.files];
for (const file of files) {
if (file) {
const result = await processDetectedDocument(context, file, 'generic_file', itemIndex);
documentsProcessed.push(result);
totalDocuments++;
}
}
}
// Check for text content that might be a document
if (((_c = inputData.message) === null || _c === void 0 ? void 0 : _c.text) && inputData.message.text.length > 500) {
const result = await processDetectedDocument(context, {
content: inputData.message.text,
type: 'text',
}, 'text_content', itemIndex);
documentsProcessed.push(result);
totalDocuments++;
}
return {
documentsProcessed: totalDocuments,
processedDocuments: documentsProcessed,
autoDetected: true,
};
}
catch (error) {
return {
documentsProcessed: 0,
processedDocuments: [],
autoDetected: false,
error: error.message,
};
}
}
async function processDetectedDocument(context, fileInfo, source, itemIndex) {
try {
// Get credentials
const puterCredentials = await context.getCredentials('puterAiApi');
let supabaseCredentials;
try {
supabaseCredentials = await context.getCredentials('supabaseApi');
}
catch (error) {
// If Supabase is not configured, skip document processing
return {
success: false,
source,
error: 'Supabase credentials not configured for document storage',
};
}
let extractedText = '';
let fileName = '';
let title = '';
// Process different file types
if (source === 'telegram_document') {
fileName = fileInfo.file_name || 'telegram_document';
title = `Telegram Document: ${fileName}`;
// Note: In real implementation, you'd need to download the file using Telegram Bot API
extractedText = `Document received: ${fileName} (${fileInfo.file_size} bytes)`;
}
else if (source === 'telegram_photo') {
fileName = `telegram_photo_${fileInfo.file_id}.jpg`;
title = 'Telegram Photo';
extractedText = `Photo received: ${fileName} (${fileInfo.file_size} bytes)`;
}
else if (source === 'generic_file') {
fileName = fileInfo.name || fileInfo.filename || 'unknown_file';
title = `File: ${fileName}`;
if (fileInfo.content || fileInfo.data) {
extractedText = await extractTextFromFile(fileInfo.content || fileInfo.data, fileName);
}
else {
extractedText = `File received: ${fileName}`;
}
}
else if (source === 'text_content') {
fileName = 'text_message.txt';
title = 'Text Message Content';
extractedText = fileInfo.content;
}
// Generate embeddings
const primaryAccount = {
username: puterCredentials.primaryUsername,
password: puterCredentials.primaryPassword,
};
const authToken = await authenticateAccount(primaryAccount);
const embeddings = await generateEmbeddings(extractedText, authToken);
// Store in Supabase
const supabase = await createSupabaseClient(supabaseCredentials);
const result = await storeDocumentInSupabase(supabase, extractedText, title, `auto-processed,${source}`, fileName, embeddings, supabaseCredentials);
return {
...result,
source,
fileName,
extractedTextLength: extractedText.length,
};
}
catch (error) {
return {
success: false,
source,
error: error.message,
};
}
}
async function handleChatWithAutoProcessedDocs(context, itemIndex, documentInfo) {
const operation = context.getNodeParameter('operation', itemIndex);
const message = context.getNodeParameter('message', itemIndex);
const responseFormat = context.getNodeParameter('responseFormat', itemIndex);
// If documents were auto-processed, use agentic RAG for enhanced responses
if (operation === 'agenticRag' || (operation === 'chatCompletion' && documentInfo.documentsProcessed > 0)) {
// Use the message as search query for recently processed documents
const searchQuery = message || 'What information is available in the processed documents?';
// Get credentials
const puterCredentials = await context.getCredentials('puterAiApi');
let supabaseCredentials;
try {
supabaseCredentials = await context.getCredentials('supabaseApi');
}
catch (error) {
// If Supabase is not configured, fall back to regular chat
return handleRegularChat(context, itemIndex);
}
// Generate query embeddings and search documents
const primaryAccount = {
username: puterCredentials.primaryUsername,
password: puterCredentials.primaryPassword,
};
const authToken = await authenticateAccount(primaryAccount);
const queryEmbeddings = await generateEmbeddings(searchQuery, authToken);
// Search for relevant documents (including recently processed ones)
const supabase = await createSupabaseClient(supabaseCredentials);
const searchResults = await searchDocuments(supabase, queryEmbeddings, 10, supabaseCredentials);
// Build enhanced context
const enhancedContext = searchResults
.map((result, index) => {
const doc = result.documents || result;
const similarity = result.similarity || 'N/A';
return `Document ${index + 1} (Similarity: ${similarity}):\nTitle: ${doc.title || 'Untitled'}\nContent: ${result.content || doc.content || ''}`;
})
.join('\n\n---\n\n');
// Create enhanced prompt with auto-processing context
const enhancedPrompt = `You are an intelligent AI assistant with access to recently processed documents.
Recently Auto-Processed Documents:
${documentInfo.processedDocuments.map((doc, i) => `${i + 1}. ${doc.title || doc.fileName} (Source: ${doc.source})`).join('\n')}
Retrieved Context:
${enhancedContext}
User Message: ${message}
Please provide a helpful response based on the available information. If the user's message relates to the processed documents, use that information in your response.
Response:`;
// Determine model selection
const modelStrategy = context.getNodeParameter('modelStrategy', itemIndex, 'credential-priority');
const model = context.getNodeParameter('model', itemIndex, 'auto');
let selectedModel = model;
let modelPriority;
if (modelStrategy === 'credential-priority') {
modelPriority = puterCredentials.primaryModels || ['google/gemma-2-27b-it', 'gemini-1.5-flash'];
selectedModel = modelPriority[0];
}
// Prepare AI request
const aiRequest = buildAiRequest('ragChat', selectedModel, enhancedPrompt, '', modelPriority);
// Execute AI request
const enableModelFallback = context.getNodeParameter('enableModelFallback', itemIndex, true);
const response = await executeAiRequest(aiRequest, authToken, puterCredentials, enableModelFallback, context.getNode(), modelPriority);
// Enhanced response with auto-processing metadata
const enhancedResponse = {
...response,
autoProcessing: {
documentsProcessed: documentInfo.documentsProcessed,
processedFiles: documentInfo.processedDocuments,
ragDocumentsRetrieved: searchResults.length,
},
};
return formatResponse(enhancedResponse, responseFormat, selectedModel);
}
// Fallback to regular chat if no documents or different operation
return handleRegularChat(context, itemIndex);
}
async function handleRegularChat(context, itemIndex) {
// Handle regular chat operations without RAG
const operation = context.getNodeParameter('operation', itemIndex);
const modelStrategy = context.getNodeParameter('modelStrategy', itemIndex, 'credential-priority');
const model = context.getNodeParameter('model', itemIndex, 'auto');
const message = context.getNodeParameter('message', itemIndex);
const ragContext = context.getNodeParameter('ragContext', itemIndex, '');
const enableModelFallback = context.getNodeParameter('enableModelFallback', itemIndex, true);
const responseFormat = context.getNodeParameter('responseFormat', itemIndex, 'formatted');
const credentials = await context.getCredentials('puterAiApi');
// Get authentication token for primary account
const primaryAccount = {
username: credentials.primaryUsername,
password: credentials.primaryPassword,
};
const authToken = await authenticateAccount(primaryAccount);
// Determine model selection based on strategy
let selectedModel = model;
let modelPriority;
if (modelStrategy === 'credential-priority') {
// Use primary account model priorities
modelPriority = credentials.primaryModels || ['google/gemma-2-27b-it', 'gemini-1.5-flash', 'gemini-2.0-flash', 'gpt-4o-mini'];
selectedModel = modelPriority[0]; // Use first model in priority list
}
else if (modelStrategy === 'auto') {
selectedModel = 'auto';
}
// For 'override' strategy, use the selected model as-is
// Prepare AI request
const aiRequest = buildAiRequest(operation, selectedModel, message, ragContext, modelPriority);
// Execute AI request with fallback
const response = await executeAiRequest(aiRequest, authToken, credentials, enableModelFallback, context.getNode(), modelPriority);
// Format response
return formatResponse(response, responseFormat, selectedModel);
}
// Helper functions
async function authenticateAccount(account) {
var _a;
try {
const response = await axios_1.default.post('https://puter.com/login', {
username: account.username,
password: account.password,
}, {
headers: {
'Content-Type': 'application/json',
'Accept': '*/*',
'Origin': 'https://puter.com',
'Referer': 'https://puter.com/',
},
});
if ((_a = response.data) === null || _a === void 0 ? void 0 : _a.token) {
return response.data.token;
}
throw new Error('Authentication failed - no token received');
}
catch (error) {
throw new Error(`Authentication failed: ${error.message}`);
}
}
function buildAiRequest(operation, model, message, ragContext, modelPriority) {
// Use provided model priority or fallback to cost-optimized default
const defaultModelPriority = [
'google/gemma-2-27b-it', 'gemini-1.5-flash', 'gemini-2.0-flash', 'gpt-5-nano',
'gpt-4o-mini', 'o4-mini', 'gpt-4.1-nano', 'meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo'
];
const effectiveModelPriority = modelPriority || defaultModelPriority;
const selectedModel = model === 'auto' ? effectiveModelPriority[0] : model;
let enhancedMessage = message;
if (operation === 'ragChat' && ragContext) {
enhancedMessage = `Based on the following context, please answer the user's question:\n\nContext:\n${ragContext}\n\nUser Question: ${message}\n\nAnswer:`;
}
return {
interface: 'puter-chat-completion',
driver: 'openai-completion',
method: 'complete',
args: {
messages: [
{
role: 'user',
content: enhancedMessage,
},
],
model: selectedModel,
},
sdk_version: 'v2',
client: 'puter.js',
};
}
async function executeAiRequest(aiRequest, authToken, credentials, enableModelFallback, node, modelPriority) {
var _a, _b, _c, _d, _e, _f, _g;
try {
const response = await axios_1.default.post('https://api.puter.com/drivers/call', aiRequest, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`,
'Accept': '*/*',
'Origin': 'https://puter.com',
'Referer': 'https://puter.com/',
'X-Puter-SDK': 'v2',
'User-Agent': 'Puter.js/2.0',
},
});
if (((_a = response.data) === null || _a === void 0 ? void 0 : _a.success) && ((_d = (_c = (_b = response.data) === null || _b === void 0 ? void 0 : _b.result) === null || _c === void 0 ? void 0 : _c.message) === null || _d === void 0 ? void 0 : _d.content)) {
return {
success: true,
content: response.data.result.message.content,
model: aiRequest.args.model,
usage: response.data.result.usage,
account: 'primary',
accountUsed: credentials.primaryUsername,
};
}
// Check for 400 errors and try fallback
if (is400Error(response.data) && credentials.enableAutoFallback && hasFallbackAccounts(credentials)) {
return await tryFallbackAccounts(aiRequest, credentials, enableModelFallback, node, modelPriority);
}
throw new Error(((_f = (_e = response.data) === null || _e === void 0 ? void 0 : _e.error) === null || _f === void 0 ? void 0 : _f.message) || 'AI request failed');
}
catch (error) {
if (is400Error((_g = error.response) === null || _g === void 0 ? void 0 : _g.data) && credentials.enableAutoFallback && hasFallbackAccounts(credentials)) {
return await tryFallbackAccounts(aiRequest, credentials, enableModelFallback, node, modelPriority);
}
throw error;
}
}
async function tryModelFallback(aiRequest, authToken, modelPriority) {
var _a, _b, _c, _d;
const defaultModelPriority = [
'google/gemma-2-27b-it', 'gemini-1.5-flash', 'gemini-2.0-flash', 'gpt-5-nano',
'gpt-4o-mini', 'o4-mini', 'gpt-4.1-nano', 'meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo'
];
const effectiveModelPriority = modelPriority || defaultModelPriority;
const currentModelIndex = effectiveModelPriority.indexOf(aiRequest.args.model);
const fallbackModels = currentModelIndex >= 0 ?
effectiveModelPriority.slice(currentModelIndex + 1) :
effectiveModelPriority;
for (const model of fallbackModels) {
try {
const fallbackRequest = { ...aiRequest };
fallbackRequest.args.model = model;
const response = await axios_1.default.post('https://api.puter.com/drivers/call', fallbackRequest, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`,
'Accept': '*/*',
'Origin': 'https://puter.com',
'Referer': 'https://puter.com/',
'X-Puter-SDK': 'v2',
'User-Agent': 'Puter.js/2.0',
},
});
if (((_a = response.data) === null || _a === void 0 ? void 0 : _a.success) && ((_d = (_c = (_b = response.data) === null || _b === void 0 ? void 0 : _b.result) === null || _c === void 0 ? void 0 : _c.message) === null || _d === void 0 ? void 0 : _d.content)) {
return {
success: true,
content: response.data.result.message.content,
model: model,
usage: response.data.result.usage,
account: 'fallback',
modelFallbackUsed: true,
originalModel: aiRequest.args.model,
};
}
}
catch (error) {
// Continue to next model
continue;
}
}
throw new Error('All fallback models failed');
}
// Helper function to check if fallback accounts exist
function hasFallbackAccounts(credentials) {
return ((credentials.fallback1Username && credentials.fallback1Password) ||
(credentials.fallback2Username && credentials.fallback2Password));
}
// Helper function to get sorted fallback accounts
function getFallbackAccounts(credentials) {
const accounts = [];
// Add fallback account 1 if exists
if (credentials.fallback1Username && credentials.fallback1Password) {
accounts.push({
username: credentials.fallback1Username,
password: credentials.fallback1Password,
priority: 1,
models: credentials.fallback1Models || ['gpt-4o-mini', 'claude-3-5-haiku-20241022'],
});
}
// Add fallback account 2 if exists
if (credentials.fallback2Username && credentials.fallback2Password) {
accounts.push({
username: credentials.fallback2Username,
password: credentials.fallback2Password,
priority: 2,
models: credentials.fallback2Models || ['gpt-4o-mini'],
});
}
// Sort by priority (lower number = higher priority)
if (credentials.fallbackStrategy === 'random') {
// Shuffle array for random strategy
for (let i = accounts.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[accounts[i], accounts[j]] = [accounts[j], accounts[i]];
}
}
else {
// Sort by priority for sequential strategy (already sorted by priority)
accounts.sort((a, b) => a.priority - b.priority);
}
return accounts;
}
// Enhanced fallback function for multiple accounts
async function tryFallbackAccounts(aiRequest, credentials, enableModelFallback, node, modelPriority) {
var _a, _b, _c, _d;
const fallbackAccounts = getFallbackAccounts(credentials);
const maxAttempts = Math.min(credentials.maxRetryAttempts || 3, fallbackAccounts.length);
let lastError;
for (let i = 0; i < maxAttempts && i < fallbackAccounts.length; i++) {
const account = fallbackAccounts[i];
try {
const fallbackToken = await authenticateAccount(account);
const response = await axios_1.default.post('https://api.puter.com/drivers/call', aiRequest, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${fallbackToken}`,
'Accept': '*/*',
'Origin': 'https://puter.com',
'Referer': 'https://puter.com/',
'X-Puter-SDK': 'v2',
'User-Agent': 'Puter.js/2.0',
},
});
if (((_a = response.data) === null || _a === void 0 ? void 0 : _a.success) && ((_d = (_c = (_b = response.data) === null || _b === void 0 ? void 0 : _b.result) === null || _c === void 0 ? void 0 : _c.message) === null || _d === void 0 ? void 0 : _d.content)) {
return {
success: true,
content: response.data.result.message.content,
model: aiRequest.args.model,
usage: response.data.result.usage,
account: 'fallback',
accountUsed: account.username,
fallbackAttempt: i + 1,
totalFallbackAccounts: fallbackAccounts.length,
};
}
// Try model fallback if enabled and this account works but model fails
if (enableModelFallback) {
try {
// Use the account-specific model priorities for fallback
return await tryModelFallback(aiRequest, fallbackToken, account.models);
}
catch (modelError) {
// Continue to next account if model fallback also fails
lastError = modelError;
continue;
}
}
lastError = new Error(`Account ${account.username} authenticated but AI request failed`);
}
catch (error) {
lastError = error;
// Continue to next account
continue;
}
}
// If we get here, all fallback accounts failed
throw new n8n_workflow_1.NodeOperationError(node, `All ${maxAttempts} fallback accounts failed. Last error: ${lastError.message}`);
}
function is400Error(data) {
var _a;
if (!data)
return false;
const errorMsg = ((_a = data === null || data === void 0 ? void 0 : data.error) === null || _a === void 0 ? void 0 : _a.message) || (data === null || data === void 0 ? void 0 : data.message) || '';
return (data.statusCode === 400 ||
errorMsg.includes('400') ||
errorMsg.toLowerCase().includes('usage-limited-chat') ||
errorMsg.toLowerCase().includes('permission denied') ||
errorMsg.toLowerCase().includes('usage limit') ||
errorMsg.toLowerCase().includes('rate limit') ||
errorMsg.toLowerCase().includes('quota exceeded'));
}
function formatResponse(response, format, requestedModel) {
const baseResponse = {
success: response.success,
content: response.content,
model: response.model,
account: response.account,
accountUsed: response.accountUsed,
usage: response.usage,
fallbackAttempt: response.fallbackAttempt,
totalFallbackAccounts: response.totalFallbackAccounts,
};
switch (format) {
case 'simple':
return { message: response.content };
case 'telegram': {
const cost = response.usage ? response.usage.reduce((sum, u) => sum + (u.cost || 0), 0) : 0;
const costInfo = cost > 0 ? `\n*💰 Cost: ${cost} credits*` : '';
// Enhanced account info
let accountInfo = '';
if (response.account === 'fallback') {
const attemptInfo = response.fallbackAttempt ? ` (Attempt ${response.fallbackAttempt}/${response.totalFallbackAccounts})` : '';
accountInfo = ` (Fallback: ${response.accountUsed}${attemptInfo})`;
}
else {
accountInfo = ` (${response.accountUsed})`;
}
return {
message: `🤖 **Puter AI Assistant**\n\n${response.content}\n\n---\n*⭐ Model: ${response.model}*${accountInfo}${costInfo}\n*⏰ ${new Date().toLocaleString()}*`,
chatId: null, // To be set by user
};
}
case 'raw':
return response;
case 'formatted':
default:
return {
...baseResponse,
timestamp: new Date().toISOString(),
requestedModel,
fallbackUsed: response.account === 'fallback',
modelFallbackUsed: response.modelFallbackUsed || false,
};
}
}
// Document processing helper functions
async function processDocument(documentInput, fileData, fileName, documentText, documentUrl) {
let extractedText = '';
switch (documentInput) {
case 'file':
extractedText = await extractTextFromFile(fileData, fileName);
break;
case 'text':
extractedText = documentText;
break;
case 'url':
extractedText = await extractTextFromUrl(documentUrl);
break;
default:
throw new Error('Invalid document input type');
}
return extractedText;
}
async function extractTextFromFile(fileData, fileName) {
var _a;
try {
if (!fileData) {
throw new Error('File data is empty or undefined');
}
if (!fileName) {
throw new Error('File name is required for processing');
}
// Convert base64 to buffer if needed
let buffer;
try {
buffer = Buffer.isBuffer(fileData) ? fileData : Buffer.from(fileData, 'base64');
}
catch (error) {
// Try treating as regular string if base64 decode fails
buffer = Buffer.from(fileData, 'utf-8');
}
const fileExtension = (_a = fileName.split('.').pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase();
switch (fileExtension) {
case 'pdf': {
const pdfParseLib = await loadPdfParse();
const pdfData = await pdfParseLib(buffer);
return pdfData.text;
}
case 'docx': {
const mammothLib = await loadMammoth();
const docxResult = await mammothLib.extractRawText({ buffer });
return docxResult.value;
}
case 'txt':
case 'md':
return buffer.toString('utf-8');
default:
// Try to extract as text
return buffer.toString('utf-8');
}
}
catch (error) {
throw new Error(`Failed to extract text from file: ${error.message}`);
}
}
async function extractTextFromUrl(url) {
try {
const response = await axios_1.default.get(url, {
responseType: 'arraybuffer',
headers: {
'User-Agent': 'n8n-puter-ai-document-processor',
},
});
const buffer = Buffer.from(response.data);
const contentType = response.headers['content-type'] || '';
if (contentType.includes('application/pdf')) {
const pdfParseLib = await loadPdfParse();
const pdfData = await pdfParseLib(buffer);
return pdfData.text;
}
else if (contentType.includes('text/')) {
return buffer.toString('utf-8');
}
else {
// Try to extract as text
return buffer.toString('utf-8');
}
}
catch (error) {
throw new Error(`Failed to extract text from URL: ${error.message}`);
}
}
// Supabase helper functions
async function createSupabaseClient(credentials) {
if (!credentials.supabaseUrl) {
throw new Error('Supabase URL is required');
}
if (!credentials.anonKey && !credentials.serviceRoleKey) {
throw new Error('Either Supabase anon key or service role key is required');
}
const supabaseCreateClient = await loadSupabase();
return supabaseCreateClient(credentials.supabaseUrl, credentials.serviceRoleKey || credentials.anonKey, {
auth: {
autoRefreshToken: false,
persistSession: false,
},
});
}
async function generateEmbeddings(text, authToken) {
var _a, _b, _c, _d, _e;
try {
// Use cost-effective embedding model
const embeddingRequest = {
interface: 'puter-embedding',
driver: 'openai-embedding',
method: 'embed',
args: {
input: text,
model: 'text-embedding-3-small', // Cost-effective embedding model
},
sdk_version: 'v2',
client: 'puter.js',
};
const response = await axios_1.default.post('https://api.puter.com/drivers/call', embeddingRequest, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`,
'Accept': '*/*',
'Origin': 'https://puter.com',
'Referer': 'https://puter.com/',
'X-Puter-SDK': 'v2',
'User-Agent': 'Puter.js/2.0',
},
});
if (((_a = response.data) === null || _a === void 0 ? void 0 : _a.success) && ((_e = (_d = (_c = (_b = response.data) === null || _b === void 0 ? void 0 : _b.result) === null || _c === void 0 ? void 0 : _c.data) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.embedding)) {
return response.data.result.data[0].embedding;
}
throw new Error('Failed to generate embeddings');
}
catch (error) {
throw new Error(`Embedding generation failed: ${error.message}`);
}
}
async function storeDocumentInSupabase(supabase, text, title, tags, fileName, embeddings, supabaseCredentials) {
try {
// Store document metadata
const documentData = {
title: title || fileName || 'Untitled Document',
content: text,
tags: tags ? tags.split(',').map(tag => tag.trim()) : [],
file_name: fileName,
created_at: new Date().toISOString(),
content_length: text.length,
};
const { data: docResult, error: docError } = await supabase
.from(supabaseCredentials.documentsTable || 'documents')
.insert(documentData)
.select()
.single();
if (docError) {
throw new Error(`Failed to store document: ${docError.message}`);
}
// Store embeddings if vector storage is enabled
if (supabaseCredentials.enableVectorStorage && embeddings.length > 0) {
const embeddingData = {
document_id: docResult.id,
content: text,
embedding: embeddings,
created_at: new Date().toISOString(),
};
const { error: embError } = await supabase
.from(supabaseCredentials.embeddingsTable || 'document_embeddings')
.insert(embeddingData);
if (embError) {
throw new Error(`Failed to store embeddings: ${embError.message}`);
}
}
return {
success: true,
documentId: docResult.id,
title: documentData.title,
contentLength: text.length,
embeddingsStored: supabaseCredentials.enableVectorStorage,
};
}
catch (error) {
throw new Error(`Supabase storage failed: ${error.message}`);
}
}
async function searchDocuments(supabase, queryEmbeddings, maxResults, supabaseCredentials) {
try {
const similarityThreshold = supabaseCredentials.similarityThreshold || 0.7;
const embeddingsTable = supabaseCredentials.embeddingsTable || 'document_embeddings';
const documentsTable = supabaseCredentials.documentsTable || 'documents';
// Use Supabase vector similarity search
const { data: searchResults, error } = await supabase.rpc('match_documents', {
query_embedding: queryEmbeddings,
match_threshold: similarityThreshold,
match_count: maxResults,
});
if (error) {
// Fallback to manual similarity calculation if RPC function doesn't exist
const { data: embeddings, error: embError } = await supabase
.from(embeddingsTable)
.select(`
*,
${documentsTable}:document_id (*)
`)
.limit(100); // Limit for performance
if (embError) {
throw new Error(`Search failed: ${embError.message}`);
}
// Calculate cosine similarity manually
const results = embeddings
.map((item) => ({
...item,
similarity: cosineSimilarity(queryEmbeddings, item.embedding),
}))
.filter((item) => item.similarity >= similarityThreshold)
.sort((a, b) => b.similarity - a.similarity)
.slice(0, maxResults);
return results;
}
return searchResults || [];
}
catch (error) {
throw new Error(`Document search failed: ${error.message}`);
}
}
function cosineSimilarity(vecA, vecB) {
const dotProduct = vecA.reduce((sum, a, i) => sum + a * vecB[i], 0);
const magnitudeA = Math.sqrt(vecA.reduce((sum, a) => sum + a * a, 0));
const magnitudeB = Math.sqrt(vecB.reduce((sum, b) => sum + b * b, 0));
return dotProduct / (magnitudeA * magnitudeB);
}
async function handleTranscribe(context, itemIndex) {
var _a, _b, _c;
try {
const audioFile = context.getNodeParameter('audioFile', itemIndex, '');
const audioFormat = context.getNodeParameter('audioFormat', itemIndex, 'auto');
const language = context.getNodeParameter('language', itemIndex, 'auto');
if (!audioFile) {
throw new Error('Audio file is required for transcription');
}
// Get credentials
const credentials = await context.getCredentials('puterAiApi');
// Authenticate with Puter
const primaryAccount = {
username: credentials.primaryUsername,
password: credentials.primaryPassword,
};
const authToken = await authenticateAccount(primaryAccount);
// Prepare transcription request
const transcriptionRequest = {
interface: 'puter-speech-recognition',
driver: 'openai-speech-recognition',
method: 'recognize',
args: {
audio: audioFile,
model: 'whisper-1',
language: language === 'auto' ? undefined : language,
response_format: 'text',
},
sdk_version: 'v2',
client: 'puter.js',
};
// Execute transcription request
const response = await axios_1.default.post('https://api.puter.com/drivers/call', transcriptionRequest, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`,
'Accept': '*/*',
'Origin': 'https://puter.com',
'Referer': 'https://puter.com/',
'X-Puter-SDK': 'v2',
'User-Agent': 'Puter.js/2.0',
},
});
if (((_a = response.data) === null || _a === void 0 ? void 0 : _a.success) && ((_c = (_b = response.data) === null || _b === void 0 ? void 0 : _b.result) === null || _c === void 0 ? void 0 : _c.text)) {
return {
success: true,
text: response.data.result.text,
language: response.data.result.language || language,
duration: response.data.result.duration,
model: 'whisper-1',
account: 'primary',
};
}
throw new Error('Transcription failed: No text returned from API');
}
catch (error) {
throw new Error(`Transcription failed: ${error.message}`);
}
}
async function handleUploadDocument(context, itemIndex) {
var _a, _b, _c, _d, _e;
try {
const fileName = context.getNodeParameter('fileName', itemIndex, '');
const fileContent = context.getNodeParameter('fileContent', itemIndex, '');
const fileId = context.getNodeParameter('fileId', itemIndex, '');
const userId = context.getNodeParameter('userId', itemIndex, '');
const chatId = context.getNodeParameter('chatId', itemIndex, '');
if (!fileName || !fileContent) {
throw new Error('File name and content are required for document upload');
}
// Get credentials
const puterCredentials = await context.getCredentials('puterAiApi');
let supabaseCredentials;
try {
supabaseCredentials = await context.getCredentials('supabaseApi');
}
catch (error) {
// Try to get Supabase credentials from node parameters
const supabaseUrl = context.getNodeParameter('supabaseUrl', itemIndex, '');
const supabaseApiKey = context.getNodeParameter('supabaseApiKey', itemIndex, '');
const databasePassword = context.getNodeParameter('databasePassword', itemIndex, '');
if (supabaseUrl && supabaseApiKey) {
supabaseCredentials = {
host: supabaseUrl,
serviceRole: supabaseApiKey,
password: databasePassword,
};
}
else {
throw new Error('Supabase credentials are required for document upload. Please configure Supabase API credentials or provide connection parameters.');
}
}
// Initialize Supabase client
const createClientFunc = await loadSupabase();
const supabase = createClientFunc(supabaseCredentials.host, supabaseCredentials.serviceRole);
// Process document content and create embeddings
const chunks = splitTextIntoChunks(fileContent, 500, 100);
const processedChunks = [];
// Get embeddings for each chunk
const primaryAccount = {
username: puterCredentials.primaryUsername,
password: puterCredentials.primaryPassword,
};
const authToken = await authenticateAccount(primaryAccount);
for (const chunk of chunks) {
const embeddingRequest = {
interface: 'puter-text-embedding',
driver: 'openai-embedding',
method: 'embed',
args: {
input: chunk,
model: 'text-embedding-ada-002',
},
sdk_version: 'v2',
client: 'puter.js',
};
const embeddingResponse = await axios_1.default.post('https://api.puter.com/drivers/call', embeddingRequest, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`,
'Accept': '*/*',
'Origin': 'https://puter.com',
'Referer': 'https://puter.com/',
'X-Puter-SDK': 'v2',
'User-Agent': 'Puter.js/2.0',
},
});
if (((_a = embeddingResponse.data) === null || _a === void 0 ? void 0 : _a.success) && ((_e = (_d = (_c = (_b = embeddingResponse.data) === null || _b === void 0 ? void 0 : _b.result) === null || _c === void 0 ? void 0 : _c.data) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.embedding)) {
processedChunks.push({
content: chunk,
embedding: embeddingResponse.data.result.data[0].embedding,
metadata: {
file_name: fileName,
file_id: fileId,
user_id: userId,
chat_id: chatId,
chunk_index: processedChunks.length,
timestamp: new Date().toISOString(),
},
});
}
}
// Store in Supabase
const { data, error } = await supabase
.from('documents')
.insert(processedChunks);
if (error) {
throw new Error(`Failed to store document in Supabase: ${error.message}`);
}
return {
success: true,
message: 'Document uploaded and processed successfully',
fileName,
fileId,
chunksProcessed: processedChunks.length,
userId,
chatId,
};
}
catch (error) {
throw new Error(`Document upload failed: ${error.message}`);
}
}
function splitTextIntoChunks(text, chunkSize = 500, overlap = 100) {
const chunks = [];
const sentences = text.split(/[.!?]+/).filter(s => s.trim().length > 0);
let currentChunk = '';
let currentSize = 0;
for (const sentence of sentences) {
const sentenceLength = sentence.trim().length;
if (currentSize + sentenceLength > chunkSize && currentChunk.length > 0) {
chunks.push(currentChunk.trim());
// Create overlap by keeping the last part of the current chunk
const words = currentChunk.split(' ');
const overlapWords = words.slice(-Math.floor(overlap / 10)); // Approximate overlap
currentChunk = overlapWords.join(' ') + ' ' + sentence.trim();
currentSize = currentChunk.length;
}
else {
currentChunk += (currentChunk ? ' ' : '') + sentence.trim();
currentSize = currentChunk.length;
}
}
if (currentChunk.trim().length > 0) {
chunks.push(currentChunk.trim());
}
return chunks.length > 0 ? chunks : [text]; // Fallback to original text if no chunks created
}