diff --git a/app/lib/common/llms-docs/fireproof.txt b/app/lib/common/llms-docs/fireproof.txt new file mode 100644 index 0000000..eb71c3d --- /dev/null +++ b/app/lib/common/llms-docs/fireproof.txt @@ -0,0 +1,142 @@ +# Fireproof Database API + +Fireproof is a lightweight local-first embedded document database with encrypted live sync, designed to make browser apps easy. Use it in any JavaScript environment with a unified API that works both in React (with hooks) and as a standalone core API. + +## Key Features + +- **Apps run anywhere:** Bundle UI, data, and logic in one file. +- **Real-Time & Offline-First:** Automatic persistence and live queries, runs in the browser - no loading or error states. +- **Unified API:** TypeScript works with Deno, Bun, Node.js, and the browser. +- **React Hooks:** Leverage `useLiveQuery` and `useDocument` for live collaboration. + +Fireproof enforces cryptographic causal consistency and ledger integrity using hash history, providing git-like versioning with lightweight blockchain-style verification. Data is stored and replicated as content-addressed encrypted blobs, making it safe and easy to sync via commodity object storage providers. + +## Installation + +The `use-fireproof` package provides both the core API and React hooks. + +```bash +npm install use-fireproof +``` +Works with ⚡️ ESM.sh: + +```js +import { useFireproof } from "https://esm.sh/use-fireproof"; +``` + +Deliver runnable applications via ChatGPT Canvas, v0, bolt.new, or Claude Artifacts. Deploy single page apps with React and Tailwind here: https://codepen.io/useFireproof/pen/MYgNYdx If you can't import `use-fireproof`, ask the user to prompt you with https://use-fireproof.com/llms-mock.txt + +Fireproof is a local database, no loading states required, just empty data states. It is best to have more granular documents, e.g. one document per user action. Avoid patterns that require a single document to grow without bound. + +## ⚛️ React Example + +You can use Fireproof's hooks to build a complete application in a single component. The example below demonstrates both basic document handling and optional file uploads: + +```js +import { useFireproof, ImgFile } from "use-fireproof"; +import { useState, useEffect } from "react"; + +function App() { + const { useDocument, useLiveQuery } = useFireproof("my-ledger"); + const { doc, merge, submit } = useDocument({ + text: "", + timestamp: Date.now(), + _files: {} // Optional for file handling + }); + const { docs } = useLiveQuery("_id", { limit: 10, descending: true }); + + return ( +
+
+ {/* Optional file input */} + e.target.files[0] && merge({ _files: { image: e.target.files[0] } })} + /> + merge({ text: e.target.value })} + placeholder="Enter text" + /> + +
+ +

Recent Documents

+ +
+ ); +} +``` + +To sort documents by more than one field, use a sandboxed function instead of a field name: + +```js +const { docs } = useLiveQuery( + (doc) => [doc.list_id, doc.author], + { descending: true, limit: 5, prefix: ["zyx-456-list-id"] } +); +``` + +TIP: For simple document updates like toggling or incrementing values, use `database.put()` directly as shown in the todo example: `onClick={() => database.put({...todo, done: !todo.done})}`. The `useDocument` hook is primarily for forms and more complex document creation. + +### Using the Core API in Plain JavaScript + +If you're not using React, the core API offers similar capabilities: + +```js +import { fireproof } from "use-fireproof"; + +const database = fireproof("my-ledger"); + +async function putAndQuery() { + await database.put({ text: "Sample Data" }); + const latest = await database.query("_id", { limit: 10, descending: true }); + console.log("Latest documents:", latest.docs); +} + +database.subscribe((changes) => { + console.log("Changes", changes) +}, true) + +putAndQuery(); +``` + +## Best Practices + +- **Leverage `useLiveQuery`:** + Use this hook to effortlessly build UIs that update in real time as data changes. + +- **Granular Documents:** + Save small, individual data pieces (e.g., one per user action) to simplify conflict resolution via CRDTs. + +- **Single File Deployment:** + Ideal for constrained environments, bundle everything into one file without external dependencies. + +For more details and advanced usage (such as file attachments and cloud syncing), refer to the full documentation at [Fireproof Docs](https://use-fireproof.com/). + +## See also + +See the lite docs at https://use-fireproof.com/llms-mini.txt and the full docs at https://use-fireproof.com/llms-full.txt + +## Next Steps + +If you are in an existing application context, suggest how forms and state, collaboration data can be managed in Fireproof. If this is a new app, use the examples above and start with a running App.js if you can. + +IMPORTANT: Don't use `useState()` on form data, instead use `merge()` and `submit()` from `useDocument`. Only use `useState` for ephemeral UI state (active tabs, open/closed panels, cursor positions). Keep your data model in Fireproof. diff --git a/app/lib/common/llms-docs/index.ts b/app/lib/common/llms-docs/index.ts new file mode 100644 index 0000000..b6fdb21 --- /dev/null +++ b/app/lib/common/llms-docs/index.ts @@ -0,0 +1,94 @@ +/** + * Utilities for accessing llms.txt documentation + * + * This module provides functions to access documentation in llms.txt format + * which can be used by the AI to generate more accurate code for specific libraries. + */ + +import fireproofDocs from './fireproof.txt'; + +interface LlmsDoc { + id: string; + name: string; + content: string; +} + +/** + * Available llms.txt documentation files + */ +export const availableDocuments: LlmsDoc[] = [ + { + id: 'fireproof', + name: 'Fireproof Database', + content: fireproofDocs, + }, + + // Add more docs here as they are added +]; + +/** + * Get llms.txt documentation by ID + */ +export function getDocumentById(id: string): LlmsDoc | undefined { + return availableDocuments.find((doc) => doc.id === id); +} + +/** + * Get llms.txt content by ID + */ +export function getDocumentContentById(id: string): string | undefined { + return getDocumentById(id)?.content; +} + +/** + * List all available llms.txt documents + */ +export function listAvailableDocuments(): string[] { + return availableDocuments.map((doc) => doc.id); +} + +/** + * Enhance user prompt with specific library documentation + * This can be used to dynamically inject library documentation into the prompt + * + * @param userPrompt The original user prompt + * @param libraryId The ID of the library to include documentation for + * @returns Enhanced prompt with library documentation + */ +export function enhancePromptWithLibrary(userPrompt: string, libraryId: string): string { + const libDoc = getDocumentById(libraryId); + + if (!libDoc) { + return userPrompt; + } + + return `I want to use the ${libDoc.name} in my project. +Here is the API documentation: + +""" +${libDoc.content} +""" + +Now, with that in mind, please help me with: ${userPrompt}`; +} + +/** + * Detect if a library is mentioned in the prompt and enhance it + * This automatically detects library names in the prompt and adds their documentation + * + * @param userPrompt The original user prompt + * @returns Enhanced prompt with relevant library documentation + */ +export function autoEnhancePrompt(userPrompt: string): string { + let enhancedPrompt = userPrompt; + + // Check each library to see if it's mentioned + for (const doc of availableDocuments) { + if (userPrompt.toLowerCase().includes(doc.id.toLowerCase())) { + enhancedPrompt = enhancePromptWithLibrary(enhancedPrompt, doc.id); + break; // Only enhance with one library at a time to avoid token overload + } + } + + return enhancedPrompt; +} diff --git a/app/lib/common/llms-docs/llms-docs.spec.ts b/app/lib/common/llms-docs/llms-docs.spec.ts new file mode 100644 index 0000000..c82488b --- /dev/null +++ b/app/lib/common/llms-docs/llms-docs.spec.ts @@ -0,0 +1,73 @@ +import { describe, expect, it } from 'vitest'; +import { + getDocumentById, + getDocumentContentById, + listAvailableDocuments, + enhancePromptWithLibrary, + autoEnhancePrompt, +} from './index'; + +describe('llms-docs', () => { + it('should list available documents', () => { + const docs = listAvailableDocuments(); + expect(docs).toBeInstanceOf(Array); + expect(docs.length).toBeGreaterThan(0); + expect(docs).toContain('fireproof'); + }); + + it('should get document by ID', () => { + const doc = getDocumentById('fireproof'); + expect(doc).toBeDefined(); + expect(doc?.id).toBe('fireproof'); + expect(doc?.name).toBe('Fireproof Database'); + expect(doc?.content).toBeDefined(); + expect(typeof doc?.content).toBe('string'); + }); + + it('should get document content by ID', () => { + const content = getDocumentContentById('fireproof'); + expect(content).toBeDefined(); + expect(typeof content).toBe('string'); + expect(content).toContain('Fireproof Database API'); + }); + + it('should return undefined for non-existent document', () => { + const doc = getDocumentById('non-existent'); + expect(doc).toBeUndefined(); + + const content = getDocumentContentById('non-existent'); + expect(content).toBeUndefined(); + }); + + it('should enhance prompt with specific library documentation', () => { + const originalPrompt = 'Create a todo app'; + const enhancedPrompt = enhancePromptWithLibrary(originalPrompt, 'fireproof'); + + expect(enhancedPrompt).toContain('I want to use the Fireproof Database in my project'); + expect(enhancedPrompt).toContain('API documentation'); + expect(enhancedPrompt).toContain('Fireproof Database API'); + expect(enhancedPrompt).toContain(`Now, with that in mind, please help me with: ${originalPrompt}`); + }); + + it('should not modify prompt when library does not exist', () => { + const originalPrompt = 'Create a todo app'; + const enhancedPrompt = enhancePromptWithLibrary(originalPrompt, 'non-existent-lib'); + + expect(enhancedPrompt).toBe(originalPrompt); + }); + + it('should automatically enhance prompt when library is mentioned', () => { + const promptWithLibrary = 'Create a todo app using fireproof for storage'; + const enhancedPrompt = autoEnhancePrompt(promptWithLibrary); + + expect(enhancedPrompt).toContain('I want to use the Fireproof Database in my project'); + expect(enhancedPrompt).toContain('API documentation'); + }); + + it('should not modify prompt when no library is mentioned', () => { + const promptWithoutLibrary = 'Create a todo app using local storage'; + const enhancedPrompt = autoEnhancePrompt(promptWithoutLibrary); + + expect(enhancedPrompt).toBe(promptWithoutLibrary); + }); +}); diff --git a/app/lib/common/prompts/prompts.ts b/app/lib/common/prompts/prompts.ts index 1e9134a..cd6e25c 100644 --- a/app/lib/common/prompts/prompts.ts +++ b/app/lib/common/prompts/prompts.ts @@ -1,6 +1,7 @@ import { WORK_DIR } from '~/utils/constants'; import { allowedHTMLElements } from '~/utils/markdown'; import { stripIndents } from '~/utils/stripIndent'; +import { listAvailableDocuments } from '~/lib/common/llms-docs'; export const getSystemPrompt = (cwd: string = WORK_DIR) => ` You are Bolt, an expert AI assistant and exceptional senior software developer with vast knowledge across multiple programming languages, frameworks, and best practices. @@ -29,7 +30,7 @@ You are Bolt, an expert AI assistant and exceptional senior software developer w IMPORTANT: Prefer writing Node.js scripts instead of shell scripts. The environment doesn't fully support shell scripts, so use Node.js for scripting tasks whenever possible! - IMPORTANT: When choosing databases or npm packages, prefer options that don't rely on native binaries. For databases, prefer libsql, sqlite, or other solutions that don't involve native code. WebContainer CANNOT execute arbitrary native binaries. + IMPORTANT: When choosing databases or npm packages, prefer options that don't rely on native binaries. For databases, prefer use-fireproof, libsql, sqlite, or other solutions that don't involve native code. WebContainer CANNOT execute arbitrary native binaries. Available shell commands: File Operations: @@ -97,6 +98,15 @@ You are Bolt, an expert AI assistant and exceptional senior software developer w + + I have detailed documentation for the following libraries that I can use when relevant: + ${listAvailableDocuments() + .map((id) => `- ${id}`) + .join('\n ')} + + When the user asks for solutions using any of these libraries, I will refer to the specific documentation to provide accurate guidance. + + Bolt creates a SINGLE, comprehensive artifact for each project. The artifact contains all necessary steps and components, including: @@ -137,7 +147,7 @@ You are Bolt, an expert AI assistant and exceptional senior software developer w - file: For writing new files or updating existing files. For each file add a \`filePath\` attribute to the opening \`\` tag to specify the file path. The content of the file artifact is the file contents. All file paths MUST BE relative to the current working directory. - start: For starting a development server. - - Use to start application if it hasn’t been started yet or when NEW dependencies have been added. + - Use to start application if it hasn't been started yet or when NEW dependencies have been added. - Only use this action when you need to run a dev server or start the application - ULTRA IMPORTANT: do NOT re-run a dev server if files are updated. The existing dev server can automatically detect changes and executes the file changes diff --git a/app/routes/api.enhancer.ts b/app/routes/api.enhancer.ts index 1e7bad5..cd57339 100644 --- a/app/routes/api.enhancer.ts +++ b/app/routes/api.enhancer.ts @@ -4,6 +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'; export async function action(args: ActionFunctionArgs) { return enhancerAction(args); @@ -40,6 +41,9 @@ 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); + try { const result = await streamText({ messages: [ @@ -72,7 +76,7 @@ async function enhancerAction({ context, request }: ActionFunctionArgs) { Do not include any explanations, metadata, or wrapper tags. - ${message} + ${enhancedMessage} `, }, diff --git a/app/routes/api.library-docs.ts b/app/routes/api.library-docs.ts new file mode 100644 index 0000000..225a933 --- /dev/null +++ b/app/routes/api.library-docs.ts @@ -0,0 +1,46 @@ +import { type ActionFunctionArgs, json } from '@remix-run/cloudflare'; +import { getDocumentById, listAvailableDocuments } from '~/lib/common/llms-docs'; + +export async function action({ request }: ActionFunctionArgs) { + const { libraryId } = await request.json<{ + libraryId?: string; + }>(); + + // If no libraryId provided, return a list of available libraries + if (!libraryId) { + const libraries = listAvailableDocuments().map((id) => { + const doc = getDocumentById(id); + return { + id, + name: doc?.name || id, + }; + }); + + return json({ libraries }); + } + + // Get the documentation for the requested library + const doc = getDocumentById(libraryId); + + if (!doc) { + return json({ error: `Library '${libraryId}' not found` }, { status: 404 }); + } + + return json({ + id: doc.id, + name: doc.name, + content: doc.content, + }); +} + +export async function loader() { + const libraries = listAvailableDocuments().map((id) => { + const doc = getDocumentById(id); + return { + id, + name: doc?.name || id, + }; + }); + + return json({ libraries }); +} diff --git a/package.json b/package.json index 144831f..d8e8d1b 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "typegen": "wrangler types", "preview": "pnpm run build && pnpm run start", "prepare": "husky", - "clean": "node scripts/clean.js" + "clean": "node scripts/clean.js", + "update-llms-docs": "node scripts/update-llms-docs.js" }, "engines": { "node": ">=18.18.0" diff --git a/scripts/update-llms-docs.js b/scripts/update-llms-docs.js new file mode 100644 index 0000000..de1d24e --- /dev/null +++ b/scripts/update-llms-docs.js @@ -0,0 +1,55 @@ +#!/usr/bin/env node + +/** + * Script to update llms.txt files from URLs + * Usage: node scripts/update-llms-docs.js + */ + +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const docsDir = path.join(__dirname, '..', 'app', 'lib', 'common', 'llms-docs'); + +// Define sources for llms.txt files +const sources = [ + { + name: 'fireproof', + url: 'https://use-fireproof.com/llms.txt', + outputPath: path.join(docsDir, 'fireproof.txt'), + }, + // Add more sources here as needed +]; + +async function fetchLlmsDoc(source) { + console.log(`Fetching ${source.name} from ${source.url}...`); + + try { + const response = await fetch(source.url); + + if (!response.ok) { + throw new Error(`Failed to fetch ${source.url}: ${response.statusText}`); + } + + const text = await response.text(); + fs.writeFileSync(source.outputPath, text, 'utf8'); + console.log(`Successfully updated ${source.name} at ${source.outputPath}`); + } catch (error) { + console.error(`Error updating ${source.name}:`, error.message); + } +} + +// Ensure the directory exists +if (!fs.existsSync(docsDir)) { + fs.mkdirSync(docsDir, { recursive: true }); +} + +// Update all sources +async function updateAll() { + for (const source of sources) { + await fetchLlmsDoc(source); + } +} + +updateAll().then(() => console.log('Done updating llms docs.')); \ No newline at end of file diff --git a/types/txt.d.ts b/types/txt.d.ts new file mode 100644 index 0000000..880bc6d --- /dev/null +++ b/types/txt.d.ts @@ -0,0 +1,7 @@ +/** + * Declaration file for .txt imports + */ +declare module '*.txt' { + const content: string; + export default content; +} \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index a9351c1..aa0ffb4 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -138,6 +138,18 @@ export default defineConfig((config) => { } }, }, + { + name: 'llms-docs-import', + transform(code, id) { + if (id.endsWith('.txt')) { + const contents = readFileSync(id, 'utf-8'); + return { + code: `export default ${JSON.stringify(contents)};`, + map: null, + }; + } + }, + }, config.mode !== 'test' && remixCloudflareDevProxy(), remixVitePlugin({ future: {