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
26 changes: 21 additions & 5 deletions lib/prompt-templates/create-local-circuit-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ async function fetchFileContent(url: string): Promise<string> {
}
}

async function fetchOptionalFileContent(url: string): Promise<string> {
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()
Expand All @@ -33,23 +43,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",
)) || ""
),
fetchOptionalFileContent("https://docs.tscircuit.com/ai.txt"),
])

const cleanedPropsDoc = propsDoc
const cleanedPropsDoc = (propsDoc || "")
.split("\n")
.filter((line) => !line.startsWith("#"))
.join("\n")
.replace(/\n\n+/g, "\n\n")

const generatedDocsSection = generatedDocs
? `## Auto-Generated tscircuit Documentation\n\n${generatedDocs.trim()}\n\n`
: ""

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

## tscircuit API overview
${generatedDocsSection}## tscircuit API overview

Here's an overview of the tscircuit API:

Expand Down
86 changes: 86 additions & 0 deletions tests/prompt-templates/create-local-circuit-prompt.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { afterEach, describe, expect, it } from "bun:test"
import { createLocalCircuitPrompt } from "../../lib/prompt-templates/create-local-circuit-prompt"

const originalFetch = globalThis.fetch

afterEach(() => {
globalThis.fetch = originalFetch
})

describe("createLocalCircuitPrompt", () => {
it("includes auto-generated docs when ai.txt is available", async () => {
globalThis.fetch = (async (url) => {
const href = url.toString()
if (href.includes("COMPONENT_TYPES.md")) {
return new Response("# Components\n\n<resistor />", { status: 200 })
}
if (href === "https://docs.tscircuit.com/ai.txt") {
return new Response("Generated docs: use <smtpad /> carefully.", {
status: 200,
})
}
throw new Error(`Unexpected fetch URL: ${href}`)
}) as typeof fetch

const prompt = await createLocalCircuitPrompt()

expect(prompt).toContain("Auto-Generated tscircuit Documentation")
expect(prompt).toContain("Generated docs: use <smtpad /> carefully.")
})

it("still creates a prompt when ai.txt returns 404", async () => {
globalThis.fetch = (async (url) => {
const href = url.toString()
if (href.includes("COMPONENT_TYPES.md")) {
return new Response("# Components\n\n<capacitor />", { status: 200 })
}
if (href === "https://docs.tscircuit.com/ai.txt") {
return new Response("Not Found", { status: 404, statusText: "Not Found" })
}
throw new Error(`Unexpected fetch URL: ${href}`)
}) as typeof fetch

const prompt = await createLocalCircuitPrompt()

expect(prompt).toContain("## tscircuit API overview")
expect(prompt).not.toContain("Auto-Generated tscircuit Documentation")
})

it("still creates a prompt when ai.txt fetch throws", async () => {
globalThis.fetch = (async (url) => {
const href = url.toString()
if (href.includes("COMPONENT_TYPES.md")) {
return new Response("# Components\n\n<capacitor />", { status: 200 })
}
if (href === "https://docs.tscircuit.com/ai.txt") {
throw new Error("Network error")
}
throw new Error(`Unexpected fetch URL: ${href}`)
}) as typeof fetch

const prompt = await createLocalCircuitPrompt()

expect(prompt).toContain("## tscircuit API overview")
expect(prompt).not.toContain("Auto-Generated tscircuit Documentation")
})

it("places generated docs before the API overview section", async () => {
globalThis.fetch = (async (url) => {
const href = url.toString()
if (href.includes("COMPONENT_TYPES.md")) {
return new Response("# Components\n\n<resistor />", { status: 200 })
}
if (href === "https://docs.tscircuit.com/ai.txt") {
return new Response("Docs content here.", { status: 200 })
}
throw new Error(`Unexpected fetch URL: ${href}`)
}) as typeof fetch

const prompt = await createLocalCircuitPrompt()

const generatedDocsIndex = prompt.indexOf("Auto-Generated tscircuit Documentation")
const apiOverviewIndex = prompt.indexOf("## tscircuit API overview")
expect(generatedDocsIndex).toBeGreaterThan(-1)
expect(generatedDocsIndex).toBeLessThan(apiOverviewIndex)
})
})
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 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",
})

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)
},
)
6 changes: 4 additions & 2 deletions 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 { describe, expect, it } from "bun:test"
import { generateRandomPrompts } from "../../lib/utils/generate-random-prompts"

const itWithOpenAi = process.env.OPENAI_API_KEY ? it : it.skip

describe("generateRandomPrompts", () => {
it("should return an array of prompts", async () => {
itWithOpenAi("should return an array of prompts", async () => {
const prompts = await generateRandomPrompts(3)

expect(Array.isArray(prompts)).toBe(true)
Expand Down
Loading