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
31 changes: 27 additions & 4 deletions lib/prompt-templates/create-local-circuit-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> {
try {
const response = await fetch(url)
Expand All @@ -19,6 +23,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,22 +47,31 @@ 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")
.filter((line) => !line.startsWith("#"))
.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:
Expand Down
65 changes: 65 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,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<string, Response>) {
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")
})
75 changes: 40 additions & 35 deletions tests/tscircuitCoder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
},
)
4 changes: 3 additions & 1 deletion tests/utils/generate-random-prompts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading