Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified bun.lockb
100755 → 100644
Binary file not shown.
42 changes: 38 additions & 4 deletions lib/prompt-templates/create-local-circuit-prompt.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
fp,
getFootprintNamesByType,
getFootprintSizes,
fp,
} from "@tscircuit/footprinter"

async function fetchFileContent(url: string): Promise<string> {
Expand All @@ -19,6 +19,26 @@ async function fetchFileContent(url: string): Promise<string> {
}
}

const GENERATED_DOCS_URL = "https://docs.tscircuit.com/ai.txt"
let generatedDocsPromise: Promise<string> | null = null

async function fetchGeneratedDocs(): Promise<string> {
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()
Expand All @@ -33,17 +53,29 @@ 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")
.filter((line) => !line.startsWith("#"))
.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.

Expand Down Expand Up @@ -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}
Expand Down
72 changes: 72 additions & 0 deletions tests/create-local-circuit-prompt.test.ts
Original file line number Diff line number Diff line change
@@ -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
<resistor resistance="1k" />
`
const generatedDocs = `# tscircuit generated docs
Use <resistor /> 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 <resistor /> with resistance")
expect(prompt).toContain('<resistor resistance="1k" />')
})

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('<resistor resistance="1k" />')
})

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)
})
})
77 changes: 41 additions & 36 deletions tests/tscircuitCoder.test.ts
Original file line number Diff line number Diff line change
@@ -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)
},
)
4 changes: 3 additions & 1 deletion tests/utils/generate-random-prompts.test.ts
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
Loading