feat: enhance prompt functionality with library detection from chat history

- Updated Chat component to pass messages to enhancePrompt function.
- Added OpenRouter API documentation to available documents.
- Implemented functions to detect libraries mentioned in chat history and enhance prompts accordingly.
- Updated API enhancer to utilize chat history for prompt enhancement.
- Added tests for new functionality in llms-docs.spec.ts.
This commit is contained in:
J Chris Anderson 2025-03-18 21:10:07 -07:00
parent 1d1f9d6b03
commit 3dd0b3c5a2
No known key found for this signature in database
9 changed files with 5919 additions and 27 deletions

View File

@ -60,7 +60,7 @@ interface BaseChatProps {
handleStop?: () => void;
sendMessage?: (event: React.UIEvent, messageInput?: string) => void;
handleInputChange?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
enhancePrompt?: () => void;
enhancePrompt?: (input?: string) => void;
importChat?: (description: string, messages: Message[]) => Promise<void>;
exportChat?: () => void;
uploadedFiles?: File[];
@ -550,7 +550,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
disabled={input.length === 0 || enhancingPrompt}
className={classNames('transition-all', enhancingPrompt ? 'opacity-100' : '')}
onClick={() => {
enhancePrompt?.();
enhancePrompt?.(input);
toast.success('Prompt enhanced!');
}}
>

View File

@ -233,14 +233,6 @@ export const ChatImpl = memo(
});
}, [messages, isLoading, parseMessages]);
const scrollTextArea = () => {
const textarea = textareaRef.current;
if (textarea) {
textarea.scrollTop = textarea.scrollHeight;
}
};
const abort = () => {
stop();
chatStore.setKey('aborted', true);
@ -526,17 +518,8 @@ export const ChatImpl = memo(
content: parsedMessages[i] || '',
};
})}
enhancePrompt={() => {
enhancePrompt(
input,
(input) => {
setInput(input);
scrollTextArea();
},
model,
provider,
apiKeys,
);
enhancePrompt={(_inputText) => {
enhancePrompt(input || '', setInput, model, provider, apiKeys, messages);
}}
uploadedFiles={uploadedFiles}
setUploadedFiles={setUploadedFiles}

View File

@ -6,6 +6,7 @@
*/
import fireproofDocs from './fireproof.txt';
import openrouterDocs from './openrouter.txt';
interface LlmsDoc {
id: string;
@ -22,6 +23,11 @@ export const availableDocuments: LlmsDoc[] = [
name: 'Fireproof Database',
content: fireproofDocs,
},
{
id: 'openrouter',
name: 'OpenRouter API',
content: openrouterDocs,
},
// Add more docs here as they are added
];
@ -92,3 +98,57 @@ export function autoEnhancePrompt(userPrompt: string): string {
return enhancedPrompt;
}
/**
* Detect libraries mentioned in chat history
* Returns all libraries found in the chat history
*
* @param messages Array of messages in the chat history
* @returns Array of library IDs found in history
*/
export function detectLibraryInHistory(messages: Array<{ content: string }>): string[] {
const detectedLibraries = new Set<string>();
// Go through messages from newest to oldest
for (const message of [...messages].reverse()) {
// Skip non-user messages or messages without content
if (!message.content) {
continue;
}
// Check each library
for (const doc of availableDocuments) {
if (message.content.toLowerCase().includes(doc.id.toLowerCase())) {
detectedLibraries.add(doc.id);
}
}
}
return Array.from(detectedLibraries);
}
/**
* Enhance prompt with libraries from chat history
* If the current prompt doesn't mention a library but previous messages did,
* this will enhance the prompt with that library's documentation
*
* @param userPrompt The original user prompt
* @param messages Array of messages in the chat history
* @returns Enhanced prompt with relevant library documentation from history
*/
export function enhancePromptFromHistory(userPrompt: string, messages: Array<{ content: string }>): string {
// First check if the current prompt already mentions a library
let enhancedPrompt = autoEnhancePrompt(userPrompt);
// If the prompt wasn't enhanced but there are libraries in history, use those
if (enhancedPrompt === userPrompt) {
const historyLibraryIds = detectLibraryInHistory(messages);
if (historyLibraryIds.length > 0) {
// Start with just the first detected library to avoid token overload
enhancedPrompt = enhancePromptWithLibrary(userPrompt, historyLibraryIds[0]);
}
}
return enhancedPrompt;
}

View File

@ -5,6 +5,8 @@ import {
listAvailableDocuments,
enhancePromptWithLibrary,
autoEnhancePrompt,
detectLibraryInHistory,
enhancePromptFromHistory,
} from './index';
describe('llms-docs', () => {
@ -13,6 +15,7 @@ describe('llms-docs', () => {
expect(docs).toBeInstanceOf(Array);
expect(docs.length).toBeGreaterThan(0);
expect(docs).toContain('fireproof');
expect(docs).toContain('openrouter');
});
it('should get document by ID', () => {
@ -70,4 +73,76 @@ describe('llms-docs', () => {
expect(enhancedPrompt).toBe(promptWithoutLibrary);
});
it('should detect library mentioned in chat history', () => {
const chatHistory = [
{ content: 'Hello' },
{ content: 'Create a todo app' },
{ content: 'Can I use fireproof for this?' },
];
const libraryIds = detectLibraryInHistory(chatHistory);
expect(libraryIds).toBeInstanceOf(Array);
expect(libraryIds).toContain('fireproof');
expect(libraryIds.length).toBe(1);
});
it('should detect multiple libraries mentioned in chat history', () => {
/* Test with multiple different libraries in the chat history */
const chatHistory = [
{ content: 'Hello' },
{ content: 'Can I use fireproof for this todo app?' },
{ content: 'Or maybe I should use openrouter to connect to Claude?' },
];
const libraryIds = detectLibraryInHistory(chatHistory);
expect(libraryIds).toBeInstanceOf(Array);
expect(libraryIds).toContain('fireproof');
expect(libraryIds).toContain('openrouter');
expect(libraryIds.length).toBe(2);
});
it('should enhance prompt using library from chat history', () => {
const currentPrompt = 'Add a feature to save todos';
const chatHistory = [
{ content: 'Hello' },
{ content: 'Create a todo app with fireproof' },
{ content: 'Show me how to query data' },
];
const enhancedPrompt = enhancePromptFromHistory(currentPrompt, chatHistory);
expect(enhancedPrompt).toContain('I want to use the Fireproof Database in my project');
expect(enhancedPrompt).toContain('API documentation');
expect(enhancedPrompt).toContain(`Now, with that in mind, please help me with: ${currentPrompt}`);
});
it('should use the first detected library when multiple are found in history', () => {
const currentPrompt = 'Add a feature to save user preferences';
const chatHistory = [
{ content: 'Hello' },
{ content: 'Can I use fireproof for storage?' },
{ content: 'And maybe openrouter for AI features?' },
];
const enhancedPrompt = enhancePromptFromHistory(currentPrompt, chatHistory);
// Should use the first library (depends on array order from Set which is insertion order)
const libraryIds = detectLibraryInHistory(chatHistory);
const firstLibrary = libraryIds[0];
const firstLibraryName = getDocumentById(firstLibrary)?.name;
expect(enhancedPrompt).toContain(`I want to use the ${firstLibraryName} in my project`);
});
it('should not enhance from history if current prompt already mentions library', () => {
const currentPrompt = 'How do I use fireproof to store user preferences?';
const chatHistory = [{ content: 'Hello' }, { content: 'What about using openrouter for AI?' }];
const enhancedPrompt = enhancePromptFromHistory(currentPrompt, chatHistory);
// The enhancement should come from the current prompt, not history
expect(enhancedPrompt).toContain('I want to use the Fireproof Database in my project');
expect(enhancedPrompt).toContain('API documentation');
expect(enhancedPrompt).toContain(`Now, with that in mind, please help me with: ${currentPrompt}`);
});
});

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,5 @@
import { useState } from 'react';
import type { Message } from 'ai';
import type { ProviderInfo } from '~/types/model';
import { createScopedLogger } from '~/utils/logger';
@ -19,6 +20,7 @@ export function usePromptEnhancer() {
model: string,
provider: ProviderInfo,
apiKeys?: Record<string, string>,
chatHistory?: Message[],
) => {
setEnhancingPrompt(true);
setPromptEnhanced(false);
@ -33,6 +35,14 @@ export function usePromptEnhancer() {
requestBody.apiKeys = apiKeys;
}
// Include chat history for library detection if available
if (chatHistory && chatHistory.length > 0) {
// Convert the Message[] to simpler format with just content
requestBody.chatHistory = chatHistory.map((msg) => ({
content: typeof msg.content === 'string' ? msg.content : '',
}));
}
const response = await fetch('/api/enhancer', {
method: 'POST',
body: JSON.stringify(requestBody),

View File

@ -4,7 +4,7 @@ import { stripIndents } from '~/utils/stripIndent';
import type { ProviderInfo } from '~/types/model';
import { getApiKeysFromCookie, getProviderSettingsFromCookie } from '~/lib/api/cookies';
import { createScopedLogger } from '~/utils/logger';
import { autoEnhancePrompt } from '~/lib/common/llms-docs';
import { autoEnhancePrompt, enhancePromptFromHistory } from '~/lib/common/llms-docs';
export async function action(args: ActionFunctionArgs) {
return enhancerAction(args);
@ -13,11 +13,12 @@ export async function action(args: ActionFunctionArgs) {
const logger = createScopedLogger('api.enhancher');
async function enhancerAction({ context, request }: ActionFunctionArgs) {
const { message, model, provider } = await request.json<{
const { message, model, provider, chatHistory } = await request.json<{
message: string;
model: string;
provider: ProviderInfo;
apiKeys?: Record<string, string>;
chatHistory?: Array<{ content: string }>;
}>();
const { name: providerName } = provider;
@ -41,8 +42,13 @@ async function enhancerAction({ context, request }: ActionFunctionArgs) {
const apiKeys = getApiKeysFromCookie(cookieHeader);
const providerSettings = getProviderSettingsFromCookie(cookieHeader);
// Check if the message mentions any libraries we have documentation for
const enhancedMessage = autoEnhancePrompt(message);
/*
* Check if the message mentions any libraries we have documentation for
* If chat history is provided, also check if previous messages mentioned libraries
*/
const enhancedMessage = chatHistory?.length
? enhancePromptFromHistory(message, chatHistory)
: autoEnhancePrompt(message);
try {
const result = await streamText({

View File

@ -69,4 +69,4 @@
.diff-removed {
@apply bg-red-500/20 border-l-4 border-red-500;
}
}

View File

@ -19,6 +19,11 @@ const sources = [
url: 'https://use-fireproof.com/llms.txt',
outputPath: path.join(docsDir, 'fireproof.txt'),
},
{
name: 'openrouter',
url: 'https://openrouter.ai/docs/llms-full.txt',
outputPath: path.join(docsDir, 'openrouter.txt'),
},
// Add more sources here as needed
];
@ -52,4 +57,4 @@ async function updateAll() {
}
}
updateAll().then(() => console.log('Done updating llms docs.'));
updateAll().then(() => console.log('Done updating llms docs.'));