diff --git a/lib/prompt-templates/create-local-circuit-prompt.ts b/lib/prompt-templates/create-local-circuit-prompt.ts index a93f11f..9917d1d 100644 --- a/lib/prompt-templates/create-local-circuit-prompt.ts +++ b/lib/prompt-templates/create-local-circuit-prompt.ts @@ -4,6 +4,10 @@ import { fp, } from "@tscircuit/footprinter" +const COMPONENT_TYPES_DOC_URL = + "https://raw.githubusercontent.com/tscircuit/props/main/generated/COMPONENT_TYPES.md" +const GENERATED_DOCS_URL = "https://docs.tscircuit.com/ai.txt" + async function fetchFileContent(url: string): Promise { try { const response = await fetch(url) @@ -19,6 +23,16 @@ async function fetchFileContent(url: string): Promise { } } +async function fetchOptionalFileContent(url: string): Promise { + try { + const response = await fetch(url) + if (!response.ok) return "" + return await response.text() + } catch { + return "" + } +} + export const createLocalCircuitPrompt = async () => { const footprintNamesByType = getFootprintNamesByType() const footprintSizes = getFootprintSizes() @@ -33,10 +47,10 @@ export const createLocalCircuitPrompt = async () => { "", ) - const propsDoc = - (await fetchFileContent( - "https://raw.githubusercontent.com/tscircuit/props/main/generated/COMPONENT_TYPES.md", - )) || "" + const [propsDoc, generatedDocs] = await Promise.all([ + fetchFileContent(COMPONENT_TYPES_DOC_URL), + fetchOptionalFileContent(GENERATED_DOCS_URL), + ]) const cleanedPropsDoc = propsDoc .split("\n") @@ -44,11 +58,20 @@ export const createLocalCircuitPrompt = async () => { .join("\n") .replace(/\n\n+/g, "\n\n") + const generatedDocsSection = generatedDocs.trim() + ? `## Generated tscircuit docs + +${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. YOU MUST ABIDE BY THE RULES IN THE RULES SECTION +${generatedDocsSection} ## tscircuit API overview Here's an overview of the tscircuit API: diff --git a/tests/prompt-templates/create-local-circuit-prompt.test.ts b/tests/prompt-templates/create-local-circuit-prompt.test.ts new file mode 100644 index 0000000..659a2a3 --- /dev/null +++ b/tests/prompt-templates/create-local-circuit-prompt.test.ts @@ -0,0 +1,65 @@ +import { afterEach, expect, mock, test } from "bun:test" + +mock.module("@tscircuit/footprinter", () => ({ + getFootprintNamesByType: () => ({ + normalFootprintNames: ["0402"], + }), + getFootprintSizes: () => [{ imperial: "0402" }], + fp: { + string: () => ({ + json: () => ({ footprint: "0402" }), + }), + }, +})) + +const originalFetch = globalThis.fetch + +afterEach(() => { + globalThis.fetch = originalFetch +}) + +function mockFetchByUrl(responses: Record) { + globalThis.fetch = (async (input: RequestInfo | URL) => { + const response = responses[String(input)] + if (response) return response + return new Response("Not found", { status: 404, statusText: "Not Found" }) + }) as typeof fetch +} + +test("createLocalCircuitPrompt includes generated tscircuit docs", async () => { + const { createLocalCircuitPrompt } = await import( + "lib/prompt-templates/create-local-circuit-prompt" + ) + + mockFetchByUrl({ + "https://raw.githubusercontent.com/tscircuit/props/main/generated/COMPONENT_TYPES.md": + new Response("# Components\nprops docs content"), + "https://docs.tscircuit.com/ai.txt": new Response("generated docs content"), + }) + + const prompt = await createLocalCircuitPrompt() + + expect(prompt).toContain("## Generated tscircuit docs") + expect(prompt).toContain("generated docs content") + expect(prompt).toContain("props docs content") +}) + +test("createLocalCircuitPrompt continues when generated docs are unavailable", async () => { + const { createLocalCircuitPrompt } = await import( + "lib/prompt-templates/create-local-circuit-prompt" + ) + + mockFetchByUrl({ + "https://raw.githubusercontent.com/tscircuit/props/main/generated/COMPONENT_TYPES.md": + new Response("# Components\nprops docs content"), + "https://docs.tscircuit.com/ai.txt": new Response("Unavailable", { + status: 503, + statusText: "Service Unavailable", + }), + }) + + const prompt = await createLocalCircuitPrompt() + + expect(prompt).not.toContain("## Generated tscircuit docs") + expect(prompt).toContain("props docs content") +}) diff --git a/tests/tscircuitCoder.test.ts b/tests/tscircuitCoder.test.ts index d66c022..7879cd2 100644 --- a/tests/tscircuitCoder.test.ts +++ b/tests/tscircuitCoder.test.ts @@ -2,38 +2,43 @@ import { createTscircuitCoder } from "lib/tscircuit-coder/tscircuitCoder" import { expect, test } from "bun:test" 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 testWithOpenAi = process.env.OPENAI_API_KEY ? test : test.skip + +testWithOpenAi( + "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) + }, +) diff --git a/tests/utils/generate-random-prompts.test.ts b/tests/utils/generate-random-prompts.test.ts index 41a061c..988517e 100644 --- a/tests/utils/generate-random-prompts.test.ts +++ b/tests/utils/generate-random-prompts.test.ts @@ -2,7 +2,9 @@ import { describe, it, expect } from "bun:test" import { generateRandomPrompts } from "../../lib/utils/generate-random-prompts" describe("generateRandomPrompts", () => { - it("should return an array of prompts", async () => { + const itWithOpenAi = process.env.OPENAI_API_KEY ? it : it.skip + + itWithOpenAi("should return an array of prompts", async () => { const prompts = await generateRandomPrompts(3) expect(Array.isArray(prompts)).toBe(true)