Compare commits

...

12 Commits

Author SHA1 Message Date
leehuwuj 66ee88719e refactor: remove redundant UI event handling in workflows 2025-05-15 13:40:43 +07:00
leehuwuj 2cbad524ce better handler typing 2025-05-15 13:36:37 +07:00
Marcus Schiesser fd0df23906 docs: word smith 2025-05-15 10:42:29 +07:00
leehuwuj 511f0da60e fix: improve type handling and clean up UI event component
- Removed unnecessary string conversion for userInput in code_generator and deep_research workflows.
- Updated userRequest type to MessageContent for better type safety.
- Cleaned up the UI event component by removing redundant indicatorClassName logic.
2025-05-15 09:12:51 +07:00
leehuwuj 6062b7b68e Merge remote-tracking branch 'origin/main' into lee/separate-artifacts 2025-05-14 13:39:12 +07:00
leehuwuj 9871920dc0 fix typing 2025-05-14 13:31:26 +07:00
leehuwuj e51d28cd37 fix package 2025-05-14 11:36:03 +07:00
leehuwuj e55f93fa34 bump openai 2025-05-14 11:04:54 +07:00
leehuwuj d0ae22eb90 fix typing 2025-05-14 10:41:07 +07:00
leehuwuj 808b392949 fix package version 2025-05-14 10:07:09 +07:00
leehuwuj ecd0a9865b add changeset 2025-05-14 09:56:57 +07:00
leehuwuj 960cdeb326 split artifacts use case to code generator and document generator 2025-05-14 09:56:19 +07:00
22 changed files with 337 additions and 85 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"create-llama": patch
---
Split artifacts use case to document generator and code generator
@@ -18,7 +18,8 @@ const useCases: TemplateUseCase[] = [
"agentic_rag",
"deep_research",
"financial_report",
"artifacts",
"code_generator",
"document_generator",
];
const dataSource: string = process.env.DATASOURCE
? process.env.DATASOURCE
@@ -23,7 +23,8 @@ const useCases: TemplateUseCase[] = [
"agentic_rag",
"deep_research",
"financial_report",
"artifacts",
"code_generator",
"document_generator",
];
const dataSource: string = process.env.DATASOURCE
? process.env.DATASOURCE
@@ -82,7 +83,7 @@ test.describe("Test resolve TS dependencies", () => {
});
});
// Skipping llamacloud for the use case doesn't use index.
if (useCase !== "artifacts") {
if (useCase !== "code_generator" && useCase !== "document_generator") {
test(`llamaParse - ${optionDescription}`, async () => {
await runTest({
templateType: templateType,
+2 -1
View File
@@ -58,7 +58,8 @@ export type TemplateUseCase =
| "extractor"
| "contract_review"
| "agentic_rag"
| "artifacts";
| "code_generator"
| "document_generator";
// Config for both file and folder
export type FileSourceConfig =
| {
+3 -3
View File
@@ -77,7 +77,7 @@ const installLlamaIndexServerTemplate = async ({
}
// Simplify use case code
if (useCase === "artifacts") {
if (useCase === "code_generator" || useCase === "document_generator") {
// Artifact use case doesn't use index.
// We don't need data.ts, generate.ts
await fs.rm(path.join(root, "src", "app", "data.ts"));
@@ -387,7 +387,7 @@ const providerDependencies: {
[key in ModelProvider]?: Record<string, string>;
} = {
openai: {
"@llamaindex/openai": "^0.2.0",
"@llamaindex/openai": "^0.3.7",
},
gemini: {
"@llamaindex/google": "^0.2.0",
@@ -513,7 +513,7 @@ async function updatePackageJson({
if (backend) {
packageJson.dependencies = {
...packageJson.dependencies,
"@llamaindex/readers": "^3.0.0",
"@llamaindex/readers": "^3.1.3",
};
if (vectorDb && vectorDb in vectorDbDependencies) {
+18 -7
View File
@@ -10,7 +10,8 @@ type AppType =
| "agentic_rag"
| "financial_report"
| "deep_research"
| "artifacts";
| "code_generator"
| "document_generator";
type SimpleAnswers = {
appType: AppType;
@@ -47,10 +48,14 @@ export const askSimpleQuestions = async (
"Researches and analyzes provided documents from multiple perspectives, generating a comprehensive report with citations to support key findings and insights.",
},
{
title: "Artifacts",
value: "artifacts",
description:
"Build your own Vercel's v0 or OpenAI's canvas-styled UI.",
title: "Code Generator",
value: "code_generator",
description: "Build a Vercel v0 styled code generator.",
},
{
title: "Document Generator",
value: "document_generator",
description: "Build a OpenAI canvas-styled document generator.",
},
],
},
@@ -76,7 +81,7 @@ export const askSimpleQuestions = async (
);
language = newLanguage;
if (appType !== "artifacts") {
if (appType !== "code_generator" && appType !== "document_generator") {
const { useLlamaCloud: newUseLlamaCloud } = await prompts(
{
type: "toggle",
@@ -153,7 +158,13 @@ const convertAnswers = async (
tools: [],
modelConfig: MODEL_GPT41,
},
artifacts: {
code_generator: {
template: "llamaindexserver",
dataSources: [],
tools: [],
modelConfig: MODEL_GPT41,
},
document_generator: {
template: "llamaindexserver",
dataSources: [],
tools: [],
@@ -0,0 +1,132 @@
import { Badge } from "@/components/ui/badge";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Progress } from "@/components/ui/progress";
import { Skeleton } from "@/components/ui/skeleton";
import { cn } from "@/lib/utils";
import { Markdown } from "@llamaindex/chat-ui/widgets";
import { ListChecks, Loader2, Wand2 } from "lucide-react";
import { useEffect, useState } from "react";
const STAGE_META = {
plan: {
icon: ListChecks,
badgeText: "Step 1/2: Planning",
gradient: "from-blue-100 via-blue-50 to-white",
progress: 33,
iconBg: "bg-blue-100 text-blue-600",
badge: "bg-blue-100 text-blue-700",
},
generate: {
icon: Wand2,
badgeText: "Step 2/2: Generating",
gradient: "from-violet-100 via-violet-50 to-white",
progress: 66,
iconBg: "bg-violet-100 text-violet-600",
badge: "bg-violet-100 text-violet-700",
},
};
function ArtifactWorkflowCard({ event }) {
const [visible, setVisible] = useState(event?.state !== "completed");
const [fade, setFade] = useState(false);
useEffect(() => {
if (event?.state === "completed") {
setVisible(false);
} else {
setVisible(true);
setFade(false);
}
}, [event?.state]);
if (!event || !visible) return null;
const { state, requirement } = event;
const meta = STAGE_META[state];
if (!meta) return null;
return (
<div className="flex min-h-[180px] w-full items-center justify-center py-2">
<Card
className={cn(
"w-full rounded-xl shadow-md transition-all duration-500",
"border-0",
fade && "pointer-events-none opacity-0",
`bg-gradient-to-br ${meta.gradient}`,
)}
style={{
boxShadow:
"0 2px 12px 0 rgba(80, 80, 120, 0.08), 0 1px 3px 0 rgba(80, 80, 120, 0.04)",
}}
>
<CardHeader className="flex flex-row items-center gap-2 px-3 pb-1 pt-2">
<div
className={cn(
"flex items-center justify-center rounded-full p-1",
meta.iconBg,
)}
>
<meta.icon className="h-5 w-5" />
</div>
<CardTitle className="flex items-center gap-2 text-base font-semibold">
<Badge className={cn("ml-1", meta.badge, "px-2 py-0.5 text-xs")}>
{meta.badgeText}
</Badge>
</CardTitle>
</CardHeader>
<CardContent className="px-3 py-1">
{state === "plan" && (
<div className="flex flex-col items-center gap-2 py-2">
<Loader2 className="mb-1 h-6 w-6 animate-spin text-blue-400" />
<div className="text-center text-sm font-medium text-blue-900">
Analyzing your request...
</div>
<Skeleton className="mt-1 h-3 w-1/2 rounded-full" />
</div>
)}
{state === "generate" && (
<div className="flex flex-col gap-2 py-2">
<div className="flex items-center gap-1">
<Loader2 className="h-4 w-4 animate-spin text-violet-400" />
<span className="text-sm font-medium text-violet-900">
Working on the requirement:
</span>
</div>
<div className="max-h-24 overflow-auto rounded-lg border border-violet-200 bg-violet-50 px-2 py-1 text-xs">
{requirement ? (
<Markdown content={requirement} />
) : (
<span className="italic text-violet-400">
No requirements available yet.
</span>
)}
</div>
</div>
)}
</CardContent>
<div className="px-3 pb-2 pt-1">
<Progress
value={meta.progress}
className={cn(
"h-1 rounded-full bg-gray-200",
state === "plan" && "bg-blue-200",
state === "generate" && "bg-violet-200",
)}
/>
</div>
</Card>
</div>
);
}
export default function Component({ events }) {
const aggregateEvents = () => {
if (!events || events.length === 0) return null;
return events[events.length - 1];
};
const event = aggregateEvents();
return <ArtifactWorkflowCard event={event} />;
}
@@ -1,15 +0,0 @@
from app.code_workflow import CodeArtifactWorkflow
# from app.document_workflow import DocumentArtifactWorkflow to generate documents
from llama_index.core.workflow import Workflow
from llama_index.llms.openai import OpenAI
from llama_index.server.api.models import ChatRequest
def create_workflow(chat_request: ChatRequest) -> Workflow:
workflow = CodeArtifactWorkflow(
llm=OpenAI(model="gpt-4.1"),
chat_request=chat_request,
timeout=120.0,
)
return workflow
@@ -0,0 +1,65 @@
This is a [LlamaIndex](https://www.llamaindex.ai/) project using [Workflows](https://docs.llamaindex.ai/en/stable/understanding/workflows/).
## Getting Started
First, setup the environment with uv:
> **_Note:_** This step is not needed if you are using the dev-container.
```shell
uv sync
```
Then check the parameters that have been pre-configured in the `.env` file in this directory.
Make sure you have set the `OPENAI_API_KEY` for the LLM.
Then, run the development server:
```shell
uv run fastapi dev
```
Then open [http://localhost:8000](http://localhost:8000) with your browser to start the chat UI.
To start the app optimized for **production**, run:
```
uv run fastapi run
```
## Configure LLM and Embedding Model
You can configure [LLM model](https://docs.llamaindex.ai/en/stable/module_guides/models/llms) and [embedding model](https://docs.llamaindex.ai/en/stable/module_guides/models/embeddings) in [settings.py](app/settings.py).
## Use Case
AI-powered code generator that can help you generate app with a chat interface, code editor and app preview.
To update the workflow, you can modify the code in [`workflow.py`](app/workflow.py).
You can start by sending an request on the [chat UI](http://localhost:8000) or you can test the `/api/chat` endpoint with the following curl request:
```
curl --location 'localhost:8000/api/chat' \
--header 'Content-Type: application/json' \
--data '{ "messages": [{ "role": "user", "content": "Create a report comparing the finances of Apple and Tesla" }] }'
```
## Customize the UI
To customize the UI, you can start by modifying the [./components/ui_event.jsx](./components/ui_event.jsx) file.
You can also generate a new code for the workflow using LLM by running the following command:
```
uv run generate_ui
```
## Learn More
To learn more about LlamaIndex, take a look at the following resources:
- [LlamaIndex Documentation](https://docs.llamaindex.ai) - learn about LlamaIndex.
- [Workflows Introduction](https://docs.llamaindex.ai/en/stable/understanding/workflows/) - learn about LlamaIndex workflows.
- [LlamaIndex Server](https://pypi.org/project/llama-index-server/)
You can check out [the LlamaIndex GitHub repository](https://github.com/run-llama/llama_index) - your feedback and contributions are welcome!
@@ -6,6 +6,7 @@ from llama_index.core.chat_engine.types import ChatMessage
from llama_index.core.llms import LLM
from llama_index.core.memory import ChatMemoryBuffer
from llama_index.core.prompts import PromptTemplate
from llama_index.llms.openai import OpenAI
from llama_index.core.workflow import (
Context,
Event,
@@ -26,6 +27,15 @@ from llama_index.server.api.utils import get_last_artifact
from pydantic import BaseModel, Field
def create_workflow(chat_request: ChatRequest) -> Workflow:
workflow = CodeArtifactWorkflow(
llm=OpenAI(model="gpt-4.1"),
chat_request=chat_request,
timeout=120.0,
)
return workflow
class Requirement(BaseModel):
next_step: Literal["answering", "coding"]
language: Optional[str] = None
@@ -33,12 +33,9 @@ You can configure [LLM model](https://docs.llamaindex.ai/en/stable/module_guides
## Use Case
We have prepared two artifact workflows:
AI-powered document generator that can help you generate documents with a chat interface and simple markdown editor.
- [Code Workflow](app/code_workflow.py): To generate code and display it in the UI like Vercel's v0.
- [Document Workflow](app/document_workflow.py): Generate and update a document like OpenAI's canvas.
Modify the factory method in [`workflow.py`](app/workflow.py) to decide which artifact workflow to use. Without any changes the Code Workflow is used.
To update the workflow, you can modify the code in [`workflow.py`](app/workflow.py).
You can start by sending an request on the [chat UI](http://localhost:8000) or you can test the `/api/chat` endpoint with the following curl request:
@@ -4,6 +4,7 @@ from typing import Any, Literal, Optional
from llama_index.core.chat_engine.types import ChatMessage
from llama_index.core.llms import LLM
from llama_index.llms.openai import OpenAI
from llama_index.core.memory import ChatMemoryBuffer
from llama_index.core.prompts import PromptTemplate
from llama_index.core.workflow import (
@@ -26,6 +27,15 @@ from llama_index.server.api.utils import get_last_artifact
from pydantic import BaseModel, Field
def create_workflow(chat_request: ChatRequest) -> Workflow:
workflow = DocumentArtifactWorkflow(
llm=OpenAI(model="gpt-4.1"),
chat_request=chat_request,
timeout=120.0,
)
return workflow
class DocumentRequirement(BaseModel):
type: Literal["markdown", "html"]
title: str
@@ -1,12 +0,0 @@
import { createCodeArtifactWorkflow, UIEventSchema } from "./code-workflow";
// import { createDocumentArtifactWorkflow, UIEventSchema } from "./doc-workflow";
export const workflowFactory = async (reqBody: any) => {
// Uncomment the import and change to createDocumentArtifactWorkflow to use the document workflow
const workflow = createCodeArtifactWorkflow(reqBody);
return workflow;
};
// Re-export the UIEventSchema for generating the UI by `pnpm generate:ui` command
export { UIEventSchema };
@@ -30,12 +30,9 @@ npm run generate:ui
## Use Case
We have prepared two artifact workflows:
AI-powered code generator that can help you generate app with a chat interface, code editor and app preview.
- [Code Workflow](app/code_workflow.ts): To generate code and display it in the UI like Vercel's v0.
- [Document Workflow](app/document_workflow.ts): Generate and update a document like OpenAI's canvas.
Modify the factory method in [`workflow.ts`](app/workflow.ts) to decide which artifact workflow to use. Without any changes the Code Workflow is used.
To update the workflow, you can modify the code in [`workflow.ts`](app/workflow.ts).
You can start by sending an request on the [chat UI](http://localhost:3000) or you can test the `/api/chat` endpoint with the following curl request:
@@ -1,5 +1,5 @@
import { extractLastArtifact } from "@llamaindex/server";
import { ChatMemoryBuffer, LLM, Settings } from "llamaindex";
import { ChatMemoryBuffer, LLM, MessageContent, Settings } from "llamaindex";
import {
agentStreamEvent,
@@ -12,6 +12,12 @@ import {
import { z } from "zod";
export const workflowFactory = async (reqBody: any) => {
const workflow = createCodeArtifactWorkflow(reqBody);
return workflow;
};
export const RequirementSchema = z.object({
next_step: z.enum(["answering", "coding"]),
language: z.string().nullable().optional(),
@@ -40,7 +46,7 @@ export const UIEventSchema = z.object({
export type UIEvent = z.infer<typeof UIEventSchema>;
const planEvent = workflowEvent<{
userInput: string;
userInput: MessageContent;
context?: string | undefined;
}>();
@@ -92,7 +98,7 @@ export function createCodeArtifactWorkflow(reqBody: any, llm?: LLM) {
content: userInput,
});
return planEvent.with({
userInput,
userInput: userInput,
});
});
@@ -177,15 +183,6 @@ ${user_msg}
throw new Error("No JSON block found in the response.");
}
const requirement = RequirementSchema.parse(JSON.parse(jsonBlock[1]));
sendEvent(
uiEvent.with({
type: "ui_event",
data: {
state: "generate",
requirement: requirement.requirement,
},
}),
);
state.memory.put({
role: "assistant",
content: `The plan for next step: \n${response.text}`,
@@ -10,12 +10,14 @@ import {
import {
ChatMemoryBuffer,
LlamaCloudIndex,
MessageContent,
Metadata,
MetadataMode,
NodeWithScore,
PromptTemplate,
Settings,
VectorStoreIndex,
extractText,
} from "llamaindex";
import { randomUUID } from "node:crypto";
import { z } from "zod";
@@ -153,7 +155,7 @@ export function getWorkflow(index: VectorStoreIndex | LlamaCloudIndex) {
chatHistory: [],
}),
contextNodes: [] as NodeWithScore<Metadata>[],
userRequest: "",
userRequest: "" as MessageContent,
totalQuestions: 0,
researchResults: [] as ResearchResult[],
};
@@ -178,7 +180,7 @@ export function getWorkflow(index: VectorStoreIndex | LlamaCloudIndex) {
}),
);
const retrievedNodes = await retriever.retrieve(userInput);
const retrievedNodes = await retriever.retrieve({ query: userInput });
sendEvent(toSourceEvent(retrievedNodes));
sendEvent(
@@ -349,7 +351,7 @@ const createResearchPlan = async (
memory: ChatMemoryBuffer,
contextStr: string,
enhancedPrompt: string,
userRequest: string,
userRequest: MessageContent,
) => {
const chatHistory = await memory.getMessages();
@@ -361,7 +363,7 @@ const createResearchPlan = async (
context_str: contextStr,
conversation_context: conversationContext,
enhanced_prompt: enhancedPrompt,
user_request: userRequest,
user_request: extractText(userRequest),
});
const responseFormat = z.object({
@@ -0,0 +1,53 @@
This is a [LlamaIndex](https://www.llamaindex.ai/) project bootstrapped with [`create-llama`](https://github.com/run-llama/LlamaIndexTS/tree/main/packages/create-llama).
## Getting Started
First, install the dependencies:
```
npm install
```
Third, run the development server:
```
npm run dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the chat UI.
## Configure LLM and Embedding Model
You can configure [LLM model](https://ts.llamaindex.ai/docs/llamaindex/modules/llms) in the [settings file](src/app/settings.ts).
## Custom UI Components
We have a custom component located in `components/ui_event.jsx`. This is used to display the state of artifact workflows in UI. You can regenerate a new UI component from the workflow event schema by running the following command:
```
npm run generate:ui
```
## Use Case
AI-powered document generator that can help you generate documents with a chat interface and simple markdown editor.
To update the workflow, you can modify the code in [`workflow.ts`](app/workflow.ts).
You can start by sending an request on the [chat UI](http://localhost:3000) or you can test the `/api/chat` endpoint with the following curl request:
```shell
curl --location 'localhost:3000/api/chat' \
--header 'Content-Type: application/json' \
--data '{ "messages": [{ "role": "user", "content": "Compare the financial performance of Apple and Tesla" }] }'
```
## Learn More
To learn more about LlamaIndex, take a look at the following resources:
- [LlamaIndex Documentation](https://docs.llamaindex.ai) - learn about LlamaIndex (Python features).
- [LlamaIndexTS Documentation](https://ts.llamaindex.ai/docs/llamaindex) - learn about LlamaIndex (Typescript features).
- [Workflows Introduction](https://ts.llamaindex.ai/docs/llamaindex/modules/workflows) - learn about LlamaIndexTS workflows.
You can check out [the LlamaIndexTS GitHub repository](https://github.com/run-llama/LlamaIndexTS) - your feedback and contributions are welcome!
@@ -1,5 +1,5 @@
import { extractLastArtifact } from "@llamaindex/server";
import { ChatMemoryBuffer, LLM, Settings } from "llamaindex";
import { ChatMemoryBuffer, LLM, MessageContent, Settings } from "llamaindex";
import {
agentStreamEvent,
@@ -12,6 +12,12 @@ import {
import { z } from "zod";
export const workflowFactory = async (reqBody: any) => {
const workflow = createDocumentArtifactWorkflow(reqBody);
return workflow;
};
export const DocumentRequirementSchema = z.object({
type: z.enum(["markdown", "html"]),
title: z.string(),
@@ -40,7 +46,7 @@ export const UIEventSchema = z.object({
export type UIEvent = z.infer<typeof UIEventSchema>;
const planEvent = workflowEvent<{
userInput: string;
userInput: MessageContent;
context?: string | undefined;
}>();
@@ -175,15 +181,6 @@ export function createDocumentArtifactWorkflow(reqBody: any, llm?: LLM) {
role: "assistant",
content: `Planning for the document generation: \n${response.text}`,
});
sendEvent(
uiEvent.with({
type: "ui_event",
data: {
state: "generate",
requirement: requirement.requirement,
},
}),
);
return generateArtifactEvent.with({
requirement,
});
@@ -9,13 +9,13 @@
"start": "tsx src/index.ts"
},
"dependencies": {
"@llamaindex/openai": "0.2.0",
"@llamaindex/server": "0.2.0",
"@llamaindex/workflow": "1.1.1",
"@llamaindex/tools": "0.0.9",
"@llamaindex/openai": "^0.3.7",
"@llamaindex/server": "^0.2.0",
"@llamaindex/workflow": "^1.1.2",
"@llamaindex/tools": "^0.0.10",
"llamaindex": "^0.10.6",
"dotenv": "^16.4.7",
"zod": "^3.23.8",
"llamaindex": "0.10.5"
"zod": "^3.23.8"
},
"devDependencies": {
"@types/node": "^20.10.3",
@@ -3,7 +3,7 @@ import { Settings } from "llamaindex";
export function initSettings() {
Settings.llm = new OpenAI({
model: "gpt-4o-mini",
model: "gpt-4.1",
});
Settings.embedModel = new OpenAIEmbedding({
model: "text-embedding-3-small",