Skip to content

feat: Custom JSX runtime #20

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Dec 20, 2024
13 changes: 0 additions & 13 deletions .github/workflows/conventional-label.yml

This file was deleted.

2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
"source.fixAll.eslint": "always",
"source.removeUnusedImports": "always"
},
"cSpell.words": ["gensx"]
"cSpell.words": ["gensx", "jsxs"]
}
16 changes: 11 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"packageManager": "[email protected]",
"type": "module",
"scripts": {
"build:watch": "pnpm build:clean && pnpm generate-dist --watch",
"dev": "nodemon",
"prepublishOnly": "pnpm i && pnpm build",
"build": "pnpm validate-typescript && pnpm build:clean && pnpm generate-dist",
Expand All @@ -38,22 +39,19 @@
"lint:fix": "eslint --ignore-path .gitignore . --ext .js,.ts --fix",
"lint:file": "eslint --ignore-path .gitignore",
"validate-typescript": "tsc -p tsconfig.prod.json --noEmit",
"generate-dist": "tsup src/index.ts --minify --tsconfig tsconfig.prod.json --dts --format cjs,esm --out-dir dist",
"generate-dist": "tsup src/index.ts --minify --tsconfig tsconfig.prod.json --dts --format cjs,esm --out-dir dist --entry.jsx-runtime=src/jsx-runtime.ts --entry.jsx-dev-runtime=src/jsx-dev-runtime.ts --entry.index=src/index.ts",
"build:clean": "rimraf dist; exit 0",
"prepare": "[ -f .husky/install.mjs ] && node .husky/install.mjs || true"
},
"dependencies": {
"openai": "^4.76.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
"openai": "^4.76.0"
},
"devDependencies": {
"@swc/cli": "^0.5.2",
"@swc/core": "^1.10.1",
"@types/fs-extra": "^11.0.4",
"@types/node": "^22.10.2",
"@types/react": "^19.0.1",
"@types/react-dom": "^18.3.2",
"@typescript-eslint/eslint-plugin": "^8.18.1",
"@typescript-eslint/parser": "^8.18.1",
"@vitest/coverage-istanbul": "^2.1.8",
Expand Down Expand Up @@ -94,6 +92,14 @@
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
},
"./jsx-runtime": {
"import": "./dist/jsx-runtime.js",
"require": "./dist/jsx-runtime.cjs"
},
"./jsx-dev-runtime": {
"import": "./dist/jsx-dev-runtime.js",
"require": "./dist/jsx-dev-runtime.cjs"
}
}
}
27 changes: 0 additions & 27 deletions playground/blog/BlogWritingWorkflow.tsx

This file was deleted.

160 changes: 89 additions & 71 deletions playground/index.tsx
Original file line number Diff line number Diff line change
@@ -1,85 +1,103 @@
import { Workflow } from "@/src/components/Workflow";
import { WorkflowContext } from "@/src/components/Workflow";
import { createWorkflowOutput } from "@/src/hooks/useWorkflowOutput";
import { Component, gensx } from "@/index";

import { BlogWritingWorkflow } from "./blog/BlogWritingWorkflow";
import { TweetWritingWorkflow } from "./tweet/TweetWritingWorkflow";
// Pure workflow steps
const pureResearchBrainstorm = async ({ prompt }: { prompt: string }) => {
console.log("🔍 Starting research for:", prompt);
const topics = await Promise.resolve(["topic 1", "topic 2", "topic 3"]);
return topics;
};

async function runParallelWorkflow() {
const title = "Programmatic Secrets with ESC";
const prompt = "Write an article...";
const pureWriter = async ({
research,
prompt,
}: {
research: string;
prompt: string;
}): Promise<string> => {
console.log("✍️ Writing draft based on research");
return await Promise.resolve(`**draft\n${research}\n${prompt}\n**end draft`);
};

const [blogPost, setBlogPost] = createWorkflowOutput("");
const [tweet, setTweet] = createWorkflowOutput("");
const pureEditor = async ({ draft }: { draft: string }) => {
console.log("✨ Polishing final draft");
return await Promise.resolve(`edited result: ${draft}`);
};

const workflow = (
<Workflow>
<BlogWritingWorkflow
title={title}
prompt={prompt}
setOutput={setBlogPost}
/>
<TweetWritingWorkflow content={blogPost} setOutput={setTweet} />
</Workflow>
);

const context = new WorkflowContext(workflow);
await context.execute();
// Wrapped workflow steps
const LLMResearchBrainstorm = Component(pureResearchBrainstorm);
const LLMResearch = Component(async ({ topic }: { topic: string }) => {
console.log("📚 Researching topic:", topic);
return await Promise.resolve(`research results for ${topic}`);
});
const LLMWriter = Component(pureWriter);
const LLMEditor = Component(pureEditor);
const WebResearcher = Component(async ({ prompt }: { prompt: string }) => {
console.log("🌐 Researching web for:", prompt);
const results = await Promise.resolve([
"web result 1",
"web result 2",
"web result 3",
]);
return results;
});

console.log("\n=== Parallel Workflow Results ===");
console.log("Blog Post:", await blogPost);
console.log("Tweet:", await tweet);
}
// When building a workflow out of components, there are two options:
// 1. Use the Component function to wrap it and specify the input and output types. This gets you a function with type safe inputs and outputs (if you just call it as a function).
// 2. Don't wrap it in the Component function, and do not specify the output type (see BlogWritingWorkflow below). You get a function that is the same type as the JSX.Element signature, so it has an unknown output type. If you try to specify the output type on the function signature, you get a type error (unknown is not assignable to X).
// If you choose not to wrap it in a Component, you can't pass children to it, but we could easily expose the types for that to enable it, similar to the React.PropsWithChildren type.
const ParallelResearch = Component<{ prompt: string }, [string[], string[]]>(
({ prompt }: { prompt: string }) => (
<>
<LLMResearchBrainstorm prompt={prompt}>
{topics => (
<>
{topics.map(topic => (
<LLMResearch topic={topic} />
))}
</>
)}
</LLMResearchBrainstorm>
<WebResearcher prompt={prompt} />
</>
),
);

async function runNestedWorkflow() {
const title = "Programmatic Secrets with ESC";
const prompt = "Write an article...";
const BlogWritingWorkflow = async ({ prompt }: { prompt: string }) => (
<ParallelResearch prompt={prompt}>
{([catalogResearch, webResearch]) => {
console.log("🧠 Research:", { catalogResearch, webResearch });
return (
<LLMWriter research={catalogResearch.join("\n")} prompt={prompt}>
{draft => <LLMEditor draft={draft} />}
</LLMWriter>
);
}}
</ParallelResearch>
);

let blogPost = "";
let tweet = "";
async function main() {
console.log("🚀 Starting blog writing workflow");

const workflow = (
<Workflow>
<BlogWritingWorkflow
title={
new Promise(resolve => {
resolve(title);
})
}
prompt={prompt}
>
{blogPostResult => (
<TweetWritingWorkflow content={blogPostResult}>
{tweetResult => {
blogPost = blogPostResult;
tweet = tweetResult;
return null;
}}
</TweetWritingWorkflow>
)}
</BlogWritingWorkflow>
</Workflow>
// Use the gensx function to execute the workflow and annotate with the output type.
const result = await gensx<string>(
<BlogWritingWorkflow prompt="Write a blog post about the future of AI" />,
);

const context = new WorkflowContext(workflow);
await context.execute();
// Or just call the workflow as a function, and cast to the output type.
const result2 = (await (
<BlogWritingWorkflow prompt="Write a blog post about using GenSX to 10x your productivity." />
)) as string;

console.log("\n=== Nested Workflow Results ===");
console.log("Blog Post:", blogPost);
console.log("Tweet:", tweet);
}
// Still need to cast here, because we didn't use the Component helper to wrap the workflow.
const result3 = (await BlogWritingWorkflow({
prompt: "Write a blog post about the future of AI",
})) as string;

async function main() {
try {
await runParallelWorkflow();
await runNestedWorkflow();
} catch (error) {
console.error("Workflow execution failed:", error);
process.exit(1);
}
// Don't need to cast here, because we used the Component helper to wrap the workflow.
const researchResult = await ParallelResearch({
prompt: "Write a blog post about the future of AI",
});
console.log("✅ Final result:", { result, result2, result3, researchResult });
}

main().catch((error: unknown) => {
console.error("Unhandled error:", error);
process.exit(1);
});
await main();
12 changes: 0 additions & 12 deletions playground/shared/components/LLMEditor.tsx

This file was deleted.

25 changes: 0 additions & 25 deletions playground/shared/components/LLMResearcher.tsx

This file was deleted.

32 changes: 0 additions & 32 deletions playground/shared/components/LLMWriter.tsx

This file was deleted.

21 changes: 0 additions & 21 deletions playground/tweet/TweetWritingWorkflow.tsx

This file was deleted.

Loading
Loading