Skip to content

Commit 956bfa8

Browse files
committed
feat: update to use agentic search
1 parent a114871 commit 956bfa8

File tree

5 files changed

+1259
-627
lines changed

5 files changed

+1259
-627
lines changed

clients/agentic-rag-cli/index.ts

Lines changed: 192 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22

33
import fs from 'fs';
44
import path from 'path';
5-
import { TrieveSDK, Topic } from 'trieve-ts-sdk';
5+
import { TrieveSDK, Topic, ChunkMetadata } from 'trieve-ts-sdk';
66
import { program } from 'commander';
77
import chalk from 'chalk';
88
import inquirer from 'inquirer';
99
import Conf from 'conf';
1010
import os from 'os';
11+
import readline from 'readline';
1112

1213
interface UploadedFile {
1314
fileName: string;
@@ -251,6 +252,11 @@ async function updateFileStatuses(): Promise<UploadedFile[]> {
251252
}
252253
}
253254

255+
// Sort files by uploadedAt timestamp (most recent first)
256+
updatedFiles.sort((a, b) => {
257+
return new Date(b.uploadedAt).getTime() - new Date(a.uploadedAt).getTime();
258+
});
259+
254260
// Save the updated statuses
255261
if (updatedFiles.length > 0) {
256262
fs.writeFileSync(uploadedFilesPath, JSON.stringify(updatedFiles, null, 2));
@@ -320,15 +326,22 @@ async function checkSpecificFile(trackingId: string): Promise<void> {
320326

321327
// Interactive function to select and check a specific file
322328
async function interactiveCheckStatus(): Promise<void> {
323-
const files = manageUploadedFiles('get');
329+
let files = manageUploadedFiles('get');
324330

325331
if (files.length === 0) {
326332
console.log(chalk.yellow('No files have been uploaded yet.'));
327333
return;
328334
}
329335

336+
// Sort files by uploadedAt timestamp (most recent first)
337+
files.sort((a, b) => {
338+
return new Date(b.uploadedAt).getTime() - new Date(a.uploadedAt).getTime();
339+
});
340+
330341
const fileChoices = files.map((file) => ({
331-
name: `${file.fileName} (${file.trackingId})`,
342+
name: `${file.fileName} (${file.trackingId}) - ${new Date(
343+
file.uploadedAt,
344+
).toLocaleString()}`,
332345
value: file.trackingId,
333346
}));
334347

@@ -387,38 +400,134 @@ async function askQuestion(question: string): Promise<void> {
387400
console.log(chalk.blue('🔍 Fetching answer...'));
388401

389402
// Create a message and stream the response
390-
const { reader, queryId } =
391-
await trieveClient.createMessageReaderWithQueryId({
392-
topic_id: topicData.id,
393-
new_message_content: question,
394-
});
395-
396-
console.log(chalk.yellow('\n🤖 Answer:'));
397-
console.log('─'.repeat(80));
403+
const { reader } = await trieveClient.createMessageReaderWithQueryId({
404+
topic_id: topicData.id,
405+
new_message_content: question,
406+
use_agentic_search: true,
407+
});
398408

399409
// Stream the response
400410
const decoder = new TextDecoder();
401-
let answer = '';
411+
let fullResponse = '';
412+
let parsedChunks: ChunkMetadata[] = [];
413+
let isCollapsed = true;
414+
let isChunkSection = true; // Initially assume we're receiving chunks
415+
let actualAnswer = '';
416+
417+
// Set up keyboard interaction for collapsible chunks
418+
readline.emitKeypressEvents(process.stdin);
419+
if (process.stdin.isTTY) {
420+
process.stdin.setRawMode(true);
421+
}
422+
423+
const keyPressHandler = (
424+
str: string,
425+
key: { name: string; ctrl?: boolean; sequence?: string },
426+
) => {
427+
// Handle both key.name and raw sequence for better compatibility
428+
if (key.name === 'j' || key.sequence === 'j') {
429+
isCollapsed = !isCollapsed;
430+
// Clear console and redisplay with updated collapse state
431+
console.clear();
432+
433+
if (parsedChunks.length > 0) {
434+
if (isCollapsed) {
435+
console.log(
436+
chalk.cyan(
437+
`📚 Found ${parsedChunks.length} reference chunks (press 'j' to expand)`,
438+
),
439+
);
440+
} else {
441+
console.log(formatChunksCollapsible(parsedChunks));
442+
}
443+
444+
// Add a separator between chunks and answer
445+
console.log(chalk.dim('─'.repeat(40) + ' Answer ' + '─'.repeat(40)));
446+
}
447+
448+
if (actualAnswer) {
449+
console.log(actualAnswer);
450+
}
451+
} else if (key.name === 'c' && key.ctrl) {
452+
// Allow Ctrl+C to exit
453+
process.exit();
454+
}
455+
};
456+
457+
process.stdin.on('keypress', keyPressHandler);
402458

403459
try {
404460
while (true) {
405461
const { done, value } = await reader.read();
406462
if (done) break;
407463

408464
const chunk = decoder.decode(value);
409-
answer += chunk;
410-
process.stdout.write(chunk);
465+
fullResponse += chunk;
466+
467+
// Check if we've reached the separator between chunks and answer
468+
if (isChunkSection && fullResponse.includes('||')) {
469+
isChunkSection = false;
470+
const parts = fullResponse.split('||');
471+
472+
try {
473+
// The first part should contain the JSON array of chunks
474+
const chunksJson = parts[0].trim();
475+
if (chunksJson) {
476+
parsedChunks = JSON.parse(chunksJson);
477+
478+
// Only show a collapsed summary initially
479+
if (isCollapsed) {
480+
console.log(
481+
chalk.cyan(
482+
`📚 Found ${parsedChunks.length} reference chunks (press 'j' to expand)`,
483+
),
484+
);
485+
} else {
486+
console.log(formatChunksCollapsible(parsedChunks));
487+
}
488+
489+
// Add a separator between chunks and answer
490+
console.log(
491+
chalk.dim('─'.repeat(40) + ' Answer ' + '─'.repeat(40)),
492+
);
493+
}
494+
} catch (e) {
495+
console.error(chalk.red('❌ Error parsing chunks:'), e);
496+
}
497+
498+
// Start displaying the actual answer from the second part
499+
actualAnswer = parts[1] || '';
500+
process.stdout.write(actualAnswer);
501+
} else if (!isChunkSection) {
502+
// We're in the answer section, just display the chunk
503+
actualAnswer += chunk;
504+
process.stdout.write(chunk);
505+
}
411506
}
412507
} catch (e) {
413508
console.error(chalk.red('❌ Error streaming response:'), e);
414509
} finally {
415510
reader.releaseLock();
511+
// Clean up the keypress listener
512+
if (process.stdin.isTTY) {
513+
process.stdin.setRawMode(false);
514+
}
515+
process.stdin.removeListener('keypress', keyPressHandler);
416516
}
417517

418518
console.log('\n' + '─'.repeat(80));
419519
console.log(chalk.green('✅ Response complete'));
520+
521+
if (parsedChunks.length > 0) {
522+
console.log(
523+
chalk.blue(
524+
`📚 ${parsedChunks.length} reference chunks used (press 'j' to ${isCollapsed ? 'expand' : 'collapse'})`,
525+
),
526+
);
527+
}
528+
420529
console.log(
421-
chalk.blue(`📚 Topic ID: ${topicData.id} (saved for future reference)`),
530+
chalk.blue(` Topic ID: ${topicData.id} (saved for future reference)`),
422531
);
423532
} catch (error) {
424533
console.error(
@@ -428,6 +537,52 @@ async function askQuestion(question: string): Promise<void> {
428537
}
429538
}
430539

540+
// Function to format chunk metadata in a collapsible way
541+
function formatChunksCollapsible(chunks: ChunkMetadata[]): string {
542+
if (!chunks || chunks.length === 0) {
543+
return '';
544+
}
545+
546+
const summary = chalk.cyan(`📚 Found ${chunks.length} reference chunks`);
547+
const collapsedMessage = chalk.dim(`(Use 'j' to expand/collapse references)`);
548+
549+
// Format each chunk in a more readable way
550+
const formattedChunks = chunks
551+
.map((chunk, index) => {
552+
const header = chalk.yellow(
553+
`\n📄 Reference #${index + 1}: ${chunk.tracking_id || chunk.id.substring(0, 8)}`,
554+
);
555+
556+
// Extract important fields for preview
557+
const details = [
558+
chunk.link ? chalk.blue(`🔗 ${chunk.link}`) : '',
559+
chunk.tag_set?.length
560+
? chalk.magenta(`🏷️ Tags: ${chunk.tag_set.join(', ')}`)
561+
: '',
562+
chalk.grey(
563+
`📅 Created: ${new Date(chunk.created_at).toLocaleString()}`,
564+
),
565+
]
566+
.filter(Boolean)
567+
.join('\n ');
568+
569+
// Create preview of chunk content (if available)
570+
let contentPreview = '';
571+
if (chunk.chunk_html) {
572+
// Strip HTML tags for clean preview and limit length
573+
const plainText = chunk.chunk_html.replace(/<[^>]*>?/gm, '');
574+
contentPreview = chalk.white(
575+
`\n "${plainText.substring(0, 150)}${plainText.length > 150 ? '...' : ''}"`,
576+
);
577+
}
578+
579+
return `${header}\n ${details}${contentPreview}`;
580+
})
581+
.join('\n');
582+
583+
return `${summary} ${collapsedMessage}\n${formattedChunks}`;
584+
}
585+
431586
program
432587
.name('trieve-cli')
433588
.description('A CLI tool for using Trieve')
@@ -483,7 +638,6 @@ program
483638
'-t, --tracking-id <trackingId>',
484639
'Check specific file by tracking ID',
485640
)
486-
.option('-a, --all', 'Check all uploaded files')
487641
.action(async (options) => {
488642
if (options.trackingId) {
489643
await checkSpecificFile(options.trackingId);
@@ -496,42 +650,41 @@ program
496650

497651
program
498652
.command('ask')
499-
.description('Ask a question and get a streamed response')
500-
.argument('<question>', 'The question to ask')
653+
.description(
654+
'Ask a question and get a streamed response (interactive mode if no question provided)',
655+
)
656+
.argument('[question]', 'The question to ask')
501657
.action(async (question) => {
502-
await askQuestion(question);
503-
});
504-
505-
program
506-
.command('interactive-ask')
507-
.description('Ask a question interactively and get a streamed response')
508-
.action(async () => {
509-
const answers = await inquirer.prompt([
510-
{
511-
type: 'input',
512-
name: 'question',
513-
message: 'What would you like to ask?',
514-
validate: (input) => {
515-
if (!input) return 'Question is required';
516-
return true;
658+
if (question) {
659+
await askQuestion(question);
660+
} else {
661+
// Interactive mode when no question is provided
662+
const answers = await inquirer.prompt([
663+
{
664+
type: 'input',
665+
name: 'question',
666+
message: 'What would you like to ask?',
667+
validate: (input) => {
668+
if (!input) return 'Question is required';
669+
return true;
670+
},
517671
},
518-
},
519-
]);
672+
]);
520673

521-
await askQuestion(answers.question);
674+
await askQuestion(answers.question);
675+
}
522676
});
523677

524678
program.addHelpText(
525679
'after',
526680
`
527681
${chalk.yellow('Examples:')}
528-
$ ${chalk.green('trieve-cli upload path/to/file.txt -t my-tracking-id')}
529682
$ ${chalk.green('trieve-cli configure')}
683+
$ ${chalk.green('trieve-cli upload path/to/file.txt -t my-tracking-id')}
530684
$ ${chalk.green('trieve-cli check-upload-status')}
531-
$ ${chalk.green('trieve-cli check-upload-status --all')}
532685
$ ${chalk.green('trieve-cli check-upload-status --tracking-id <tracking-id>')}
533686
$ ${chalk.green('trieve-cli ask "What is the capital of France?"')}
534-
$ ${chalk.green('trieve-cli interactive-ask')}
687+
$ ${chalk.green('trieve-cli ask')}
535688
`,
536689
);
537690

0 commit comments

Comments
 (0)