diff --git a/bun.lockb b/bun.lockb old mode 100755 new mode 100644 index e9debd0..0751fab Binary files a/bun.lockb and b/bun.lockb differ diff --git a/lib/prompt-templates/create-local-circuit-prompt.ts b/lib/prompt-templates/create-local-circuit-prompt.ts index a93f11f..dcb7ca7 100644 --- a/lib/prompt-templates/create-local-circuit-prompt.ts +++ b/lib/prompt-templates/create-local-circuit-prompt.ts @@ -1,7 +1,7 @@ import { + fp, getFootprintNamesByType, getFootprintSizes, - fp, } from "@tscircuit/footprinter" async function fetchFileContent(url: string): Promise { @@ -19,6 +19,26 @@ async function fetchFileContent(url: string): Promise { } } +const GENERATED_DOCS_URL = "https://docs.tscircuit.com/ai.txt" +let generatedDocsPromise: Promise | null = null + +async function fetchGeneratedDocs(): Promise { + if (!generatedDocsPromise) { + generatedDocsPromise = fetchFileContent(GENERATED_DOCS_URL).catch( + (error) => { + console.error("Error fetching generated tscircuit docs:", error) + return "" + }, + ) + } + + return generatedDocsPromise +} + +export const resetGeneratedDocsCacheForTesting = () => { + generatedDocsPromise = null +} + export const createLocalCircuitPrompt = async () => { const footprintNamesByType = getFootprintNamesByType() const footprintSizes = getFootprintSizes() @@ -33,10 +53,12 @@ export const createLocalCircuitPrompt = async () => { "", ) - const propsDoc = - (await fetchFileContent( + const [propsDoc, generatedDocs] = await Promise.all([ + fetchFileContent( "https://raw.githubusercontent.com/tscircuit/props/main/generated/COMPONENT_TYPES.md", - )) || "" + ), + fetchGeneratedDocs(), + ]) const cleanedPropsDoc = propsDoc .split("\n") @@ -44,6 +66,16 @@ export const createLocalCircuitPrompt = async () => { .join("\n") .replace(/\n\n+/g, "\n\n") + const generatedDocsSection = generatedDocs + ? `### Generated tscircuit docs + +The following generated docs are the current reference for tscircuit APIs, components, props, and usage patterns: + +${generatedDocs.trim()} + +` + : "" + return ` You are an expert in electronic circuit design and tscircuit, and your job is to create a circuit board in tscircuit with the user-provided description. @@ -114,6 +146,8 @@ keep in mind that num_pins can be replaced with a number directly infront of the ### Components and Props +${generatedDocsSection} + - Here is a documentation of all available components and their types: ${cleanedPropsDoc} diff --git a/tests/create-local-circuit-prompt.test.ts b/tests/create-local-circuit-prompt.test.ts new file mode 100644 index 0000000..8cfccf0 --- /dev/null +++ b/tests/create-local-circuit-prompt.test.ts @@ -0,0 +1,72 @@ +import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test" +import { + createLocalCircuitPrompt, + resetGeneratedDocsCacheForTesting, +} from "lib/prompt-templates/create-local-circuit-prompt" + +const originalFetch = globalThis.fetch + +const propsDoc = `# Component Types + +` +const generatedDocs = `# tscircuit generated docs +Use with resistance and footprint props. +` + +describe("createLocalCircuitPrompt generated docs", () => { + beforeEach(() => { + resetGeneratedDocsCacheForTesting() + }) + + afterEach(() => { + resetGeneratedDocsCacheForTesting() + globalThis.fetch = originalFetch + }) + + test("includes generated docs from ai.txt in the system prompt", async () => { + globalThis.fetch = mock(async (url: string | URL | Request) => { + const urlString = url.toString() + if (urlString === "https://docs.tscircuit.com/ai.txt") { + return new Response(generatedDocs) + } + return new Response(propsDoc) + }) as typeof fetch + + const prompt = await createLocalCircuitPrompt() + + expect(prompt).toContain("### Generated tscircuit docs") + expect(prompt).toContain("Use with resistance") + expect(prompt).toContain('') + }) + + test("keeps prompt creation working when generated docs fail", async () => { + globalThis.fetch = mock(async (url: string | URL | Request) => { + const urlString = url.toString() + if (urlString === "https://docs.tscircuit.com/ai.txt") { + return new Response("missing", { status: 503 }) + } + return new Response(propsDoc) + }) as typeof fetch + + const prompt = await createLocalCircuitPrompt() + + expect(prompt).not.toContain("### Generated tscircuit docs") + expect(prompt).toContain('') + }) + + test("caches generated docs during the process", async () => { + const generatedDocsFetch = mock(async () => new Response(generatedDocs)) + globalThis.fetch = mock(async (url: string | URL | Request) => { + const urlString = url.toString() + if (urlString === "https://docs.tscircuit.com/ai.txt") { + return generatedDocsFetch() + } + return new Response(propsDoc) + }) as typeof fetch + + await createLocalCircuitPrompt() + await createLocalCircuitPrompt() + + expect(generatedDocsFetch).toHaveBeenCalledTimes(1) + }) +}) diff --git a/tests/tscircuitCoder.test.ts b/tests/tscircuitCoder.test.ts index d66c022..9437e7f 100644 --- a/tests/tscircuitCoder.test.ts +++ b/tests/tscircuitCoder.test.ts @@ -1,39 +1,44 @@ -import { createTscircuitCoder } from "lib/tscircuit-coder/tscircuitCoder" import { expect, test } from "bun:test" +import { createTscircuitCoder } from "lib/tscircuit-coder/tscircuitCoder" import { getPrimarySourceCodeFromVfs } from "lib/utils/get-primary-source-code-from-vfs" -test("TscircuitCoder submitPrompt streams and updates vfs", async () => { - const streamedChunks: string[] = [] - let vfsUpdated = false - const tscircuitCoder = createTscircuitCoder() - tscircuitCoder.on("streamedChunk", (chunk: string) => { - streamedChunks.push(chunk) - }) - tscircuitCoder.on("vfsChanged", () => { - vfsUpdated = true - }) - - await tscircuitCoder.submitPrompt({ - prompt: "create bridge rectifier circuit", - }) - - await tscircuitCoder.submitPrompt({ - prompt: "add a transistor component", - }) - - let codeWithTransistor = getPrimarySourceCodeFromVfs(tscircuitCoder.vfs) - expect(codeWithTransistor).toInclude("transistor") - - await tscircuitCoder.submitPrompt({ - prompt: "add a tssop20 chip", - }) - - let codeWithChip = getPrimarySourceCodeFromVfs(tscircuitCoder.vfs) - expect(codeWithChip).toInclude("tssop20") - expect(codeWithChip).toInclude("transistor") - - expect(streamedChunks.length).toBeGreaterThan(0) - const vfsKeys = Object.keys(tscircuitCoder.vfs) - expect(vfsKeys.length).toBeGreaterThan(0) - expect(vfsUpdated).toBe(true) -}) +const testWithOpenAiKey = process.env.OPENAI_API_KEY ? test : test.skip + +testWithOpenAiKey( + "TscircuitCoder submitPrompt streams and updates vfs", + async () => { + const streamedChunks: string[] = [] + let vfsUpdated = false + const tscircuitCoder = createTscircuitCoder() + tscircuitCoder.on("streamedChunk", (chunk: string) => { + streamedChunks.push(chunk) + }) + tscircuitCoder.on("vfsChanged", () => { + vfsUpdated = true + }) + + await tscircuitCoder.submitPrompt({ + prompt: "create bridge rectifier circuit", + }) + + await tscircuitCoder.submitPrompt({ + prompt: "add a transistor component", + }) + + const codeWithTransistor = getPrimarySourceCodeFromVfs(tscircuitCoder.vfs) + expect(codeWithTransistor).toInclude("transistor") + + await tscircuitCoder.submitPrompt({ + prompt: "add a tssop20 chip", + }) + + const codeWithChip = getPrimarySourceCodeFromVfs(tscircuitCoder.vfs) + expect(codeWithChip).toInclude("tssop20") + expect(codeWithChip).toInclude("transistor") + + expect(streamedChunks.length).toBeGreaterThan(0) + const vfsKeys = Object.keys(tscircuitCoder.vfs) + expect(vfsKeys.length).toBeGreaterThan(0) + expect(vfsUpdated).toBe(true) + }, +) diff --git a/tests/utils/generate-random-prompts.test.ts b/tests/utils/generate-random-prompts.test.ts index 41a061c..559386f 100644 --- a/tests/utils/generate-random-prompts.test.ts +++ b/tests/utils/generate-random-prompts.test.ts @@ -1,8 +1,10 @@ import { describe, it, expect } from "bun:test" import { generateRandomPrompts } from "../../lib/utils/generate-random-prompts" +const itWithOpenAiKey = process.env.OPENAI_API_KEY ? it : it.skip + describe("generateRandomPrompts", () => { - it("should return an array of prompts", async () => { + itWithOpenAiKey("should return an array of prompts", async () => { const prompts = await generateRandomPrompts(3) expect(Array.isArray(prompts)).toBe(true)