Compare commits

..

6 Commits

Author SHA1 Message Date
github-actions[bot] 2e536dca36 Release 0.5.9 (#577)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-18 19:35:10 +07:00
Huu Le 4bc53ac24e feat: support UI generator for TS (#566) 2025-04-18 19:14:29 +07:00
github-actions[bot] 2deb63a6cc chore(release): bump version to 0.1.14 (#567)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-18 17:54:50 +07:00
github-actions[bot] 2ffa057f77 Release 0.5.8 (#573)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-18 17:51:19 +07:00
Huu Le 64f151dd66 bump chat ui (#575) 2025-04-18 17:43:22 +07:00
Thuc Pham 765181adb0 chore: test typescript e2e with node 20 and 22 (#572)
* chore: test typescript e2e with node 20 and 22

* Create sixty-chefs-search.md
2025-04-17 10:06:35 +02:00
11 changed files with 442 additions and 409 deletions
+1 -1
View File
@@ -89,7 +89,7 @@ jobs:
strategy:
fail-fast: true
matrix:
node-version: [18, 20]
node-version: [20, 22]
python-version: ["3.11"]
os: [macos-latest, windows-latest, ubuntu-22.04]
frameworks: ["nextjs"]
+13
View File
@@ -1,5 +1,18 @@
# create-llama
## 0.5.9
### Patch Changes
- 4bc53ac: Bump new chat ui and update deep research component
- 4bc53ac: Support generate UI for deep research use case (Typescript)
## 0.5.8
### Patch Changes
- 765181a: chore: test typescript e2e with node 20 and 22
## 0.5.7
### Patch Changes
@@ -5,7 +5,7 @@ from typing import Optional
import requests
CHAT_UI_VERSION = "0.1.2"
CHAT_UI_VERSION = "0.1.5"
def download_chat_ui(
+1 -1
View File
@@ -26,7 +26,7 @@ license = "MIT"
name = "llama-index-server"
packages = [{include = "llama_index/"}]
readme = "README.md"
version = "0.1.13"
version = "0.1.14"
[tool.poetry.dependencies]
python = ">=3.9,<4.0"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "create-llama",
"version": "0.5.7",
"version": "0.5.9",
"description": "Create LlamaIndex-powered apps with one command",
"keywords": [
"rag",
@@ -1,408 +1,375 @@
export default function DeepResearchComponent({ events }) {
// Aggregate events by type and track their state progression
const aggregateEvents = () => {
const retrieveEvents = events.filter((e) => e.event === "retrieve");
const analyzeEvents = events.filter((e) => e.event === "analyze");
const answerEvents = events.filter((e) => e.event === "answer");
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { Badge } from "@/components/ui/badge";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Progress } from "@/components/ui/progress";
import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils";
import { Markdown } from "@llamaindex/chat-ui/widgets";
import {
AlertCircle,
Brain,
CheckCircle,
Clock,
Database,
Loader2,
MessageSquare,
} from "lucide-react";
import { useEffect, useState } from "react";
// Get the latest state for retrieve and analyze events
const retrieveState =
retrieveEvents.length > 0
? retrieveEvents[retrieveEvents.length - 1].state
: null;
const analyzeState =
analyzeEvents.length > 0
? analyzeEvents[analyzeEvents.length - 1].state
: null;
export default function Component({ events }) {
const aggregateEvents = (events) => {
if (!events || events.length === 0)
return { retrieve: null, analyze: null, answers: [] };
// Group answer events by their ID
const answerGroups = {};
for (const event of answerEvents) {
if (!event.id) continue;
if (!answerGroups[event.id]) {
answerGroups[event.id] = {
id: event.id,
question: event.question,
answer: event.answer,
states: [],
};
}
const lastState =
answerGroups[event.id].states[answerGroups[event.id].states.length - 1];
if (lastState !== event.state) {
answerGroups[event.id].states.push(event.state);
}
if (event.answer) {
answerGroups[event.id].answer = event.answer;
}
}
return {
retrieveState,
analyzeState,
answerGroups: Object.values(answerGroups),
// Initialize the result structure
const result = {
retrieve: null,
analyze: null,
answers: [],
};
// Process each event
events.forEach((event) => {
const { event: eventType, state, id, question, answer } = event;
if (eventType === "retrieve") {
// Update retrieve status
result.retrieve = { state };
} else if (eventType === "analyze") {
// Update analyze status
result.analyze = { state };
} else if (eventType === "answer" && id) {
// Find existing answer with the same id or create a new one
const existingAnswerIndex = result.answers.findIndex(
(a) => a.id === id,
);
if (existingAnswerIndex >= 0) {
// Update existing answer
result.answers[existingAnswerIndex] = {
...result.answers[existingAnswerIndex],
state,
question: question || result.answers[existingAnswerIndex].question,
answer: answer || result.answers[existingAnswerIndex].answer,
};
} else {
// Add new answer
result.answers.push({
id,
state,
question,
answer,
});
}
}
});
return result;
};
const { retrieveState, analyzeState, answerGroups } = aggregateEvents();
const [aggregatedEvents, setAggregatedEvents] = useState({
retrieve: null,
analyze: null,
answers: [],
});
// Styles
const styles = {
container: {
maxWidth: "900px",
margin: "0 auto",
padding: "20px",
backgroundColor: "#FFFFFF",
borderRadius: "8px",
boxShadow: "0 2px 8px rgba(0, 0, 0, 0.15)",
},
header: {
fontSize: "24px",
fontWeight: "bold",
textAlign: "center",
marginBottom: "20px",
color: "#333",
},
cardsContainer: {
display: "flex",
gap: "16px",
marginBottom: "30px",
flexWrap: "wrap",
},
card: {
flex: "1",
minWidth: "250px",
backgroundColor: "#FFFFFF",
borderRadius: "8px",
boxShadow: "0 2px 4px rgba(0, 0, 0, 0.1)",
padding: "16px",
border: "1px solid #E5E7EB",
transition: "all 0.3s ease",
},
cardHeader: {
display: "flex",
alignItems: "center",
marginBottom: "12px",
},
cardTitle: {
fontSize: "18px",
fontWeight: "bold",
marginLeft: "8px",
},
cardContent: {
display: "flex",
justifyContent: "space-between",
alignItems: "center",
},
badge: {
padding: "4px 8px",
borderRadius: "9999px",
fontSize: "12px",
fontWeight: "bold",
flexShrink: 0,
},
questionsList: {
marginTop: "30px",
},
questionItem: {
backgroundColor: "#F9FAFB",
border: "1px solid #E5E7EB",
borderRadius: "8px",
marginBottom: "12px",
overflow: "hidden",
},
questionHeader: {
padding: "12px 16px",
cursor: "pointer",
display: "flex",
justifyContent: "space-between",
alignItems: "flex-start",
userSelect: "none",
},
questionTitle: {
fontWeight: "medium",
marginLeft: "12px",
},
answerContainer: {
padding: "0",
backgroundColor: "#F3F4F6",
overflow: "hidden",
maxHeight: "0",
transition: "max-height 0.3s ease-out",
whiteSpace: "pre-line",
fontSize: "13px",
},
answerContent: {
padding: "16px",
},
answerContainerExpanded: {
maxHeight: "1000px", // Adjust based on content
},
arrow: {
marginLeft: "8px",
transition: "transform 0.3s ease",
display: "inline-block",
},
loadingContainer: {
display: "flex",
justifyContent: "center",
alignItems: "center",
padding: "24px",
color: "#6B7280",
},
stateIconContainer: {
display: "flex",
alignItems: "center",
gap: "8px",
},
};
useEffect(() => {
setAggregatedEvents(aggregateEvents(events));
}, [events]);
// Helper function to get color for a state
const getStateColor = (state) => {
const { retrieve, analyze, answers } = aggregatedEvents;
// Helper function to get status icon
const getStatusIcon = (state) => {
switch (state) {
case "inprogress":
return "#FCD34D";
case "done":
return "#34D399";
case "pending":
return "#9CA3AF";
return <Clock className="h-4 w-4 text-gray-400" />;
case "inprogress":
return <Loader2 className="h-4 w-4 text-blue-500 animate-spin" />;
case "done":
return <CheckCircle className="h-4 w-4 text-green-500" />;
case "error":
return <AlertCircle className="h-4 w-4 text-red-500" />;
default:
return "#D1D5DB";
return null;
}
};
// Helper function to get badge styles based on state
const getBadgeStyles = (state) => {
const colors = {
inprogress: { background: "#FEF3C7", color: "#92400E" },
done: { background: "#D1FAE5", color: "#065F46" },
pending: { background: "#F3F4F6", color: "#1F2937" },
};
const stateColors = colors[state] || colors.pending;
return {
...styles.badge,
backgroundColor: stateColors.background,
color: stateColors.color,
};
};
// Helper function to render state icon
const renderStateIcon = (state) => {
const color = getStateColor(state);
if (state === "inprogress") {
return (
<div style={{ color, animation: "spin 1s linear infinite" }}></div>
);
} else if (state === "done") {
return <div style={{ color }}></div>;
} else if (state === "pending") {
return <div style={{ color }}></div>;
// Helper function to get status text
const getStatusText = (state) => {
switch (state) {
case "pending":
return "Pending";
case "inprogress":
return "In Progress";
case "done":
return "Complete";
case "error":
return "Error";
default:
return "";
}
return <div style={{ color }}>?</div>;
};
// State for toggling question answers
const [expandedQuestions, setExpandedQuestions] = React.useState({});
const toggleQuestion = (questionId) => {
setExpandedQuestions((prev) => ({
...prev,
[questionId]: !prev[questionId],
}));
// Helper function to get status color class
const getStatusColorClass = (state) => {
switch (state) {
case "pending":
return "bg-gray-200";
case "inprogress":
return "bg-blue-500";
case "done":
return "bg-green-500";
case "error":
return "bg-red-500";
default:
return "bg-gray-200";
}
};
return (
<div style={styles.container}>
<h1 style={styles.header}>Research Progress</h1>
{/* Status Cards */}
<div style={styles.cardsContainer}>
{/* Retrieve Card */}
<div
style={{
...styles.card,
borderColor:
retrieveState === "done"
? "#A7F3D0"
: retrieveState === "inprogress"
? "#FDE68A"
: "#E5E7EB",
}}
>
<div style={styles.cardHeader}>
<span style={{ color: "#8B5CF6" }}>🔍</span>
<div style={styles.cardTitle}>Data Retrieval</div>
</div>
<div style={styles.cardContent}>
<span style={{ fontSize: "14px", color: "#6B7280" }}>Status:</span>
<div style={styles.stateIconContainer}>
{retrieveState && (
<span style={getBadgeStyles(retrieveState)}>
{retrieveState === "inprogress"
? "In Progress"
: retrieveState === "done"
? "Completed"
: "Pending"}
</span>
)}
{renderStateIcon(retrieveState)}
</div>
</div>
</div>
{/* Analyze Card */}
<div
style={{
...styles.card,
borderColor:
analyzeState === "done"
? "#A7F3D0"
: analyzeState === "inprogress"
? "#FDE68A"
: "#E5E7EB",
}}
>
<div style={styles.cardHeader}>
<span style={{ color: "#06B6D4" }}>📊</span>
<div style={styles.cardTitle}>Data Analysis</div>
</div>
<div style={styles.cardContent}>
<span style={{ fontSize: "14px", color: "#6B7280" }}>Status:</span>
<div style={styles.stateIconContainer}>
{analyzeState && (
<span style={getBadgeStyles(analyzeState)}>
{analyzeState === "inprogress"
? "In Progress"
: analyzeState === "done"
? "Completed"
: "Pending"}
</span>
)}
{renderStateIcon(analyzeState)}
</div>
</div>
</div>
{/* Questions Card */}
<div style={styles.card}>
<div style={styles.cardHeader}>
<span style={{ color: "#10B981" }}>💬</span>
<div style={styles.cardTitle}>Questions</div>
</div>
<div style={styles.cardContent}>
<span style={{ fontSize: "14px", color: "#6B7280" }}>Status:</span>
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<span
style={{
...styles.badge,
backgroundColor: "#F3F4F6",
color: "#1F2937",
}}
>
{answerGroups.length} Questions
</span>
<span
style={{
fontSize: "14px",
fontWeight: "500",
color: "#059669",
}}
>
{answerGroups.filter((g) => g.states.includes("done")).length}{" "}
Answered
</span>
</div>
</div>
<div className="w-full max-w-4xl mx-auto space-y-6 p-4">
{/* Header */}
<div className="flex items-center justify-between mb-6">
<h1 className="text-2xl font-bold">DeepResearch Workflow</h1>
<div className="flex items-center space-x-2">
<Badge
variant={retrieve?.state === "done" ? "default" : "outline"}
className={cn(
"transition-all",
retrieve?.state === "done" ? "bg-green-500" : "",
)}
>
Retrieve
</Badge>
<Separator className="h-4 w-px bg-gray-300" orientation="vertical" />
<Badge
variant={analyze?.state === "done" ? "default" : "outline"}
className={cn(
"transition-all",
analyze?.state === "done" ? "bg-green-500" : "",
)}
>
Analyze
</Badge>
<Separator className="h-4 w-px bg-gray-300" orientation="vertical" />
<Badge
variant={
answers.length > 0 && answers.every((a) => a.state === "done")
? "default"
: "outline"
}
className={cn(
"transition-all",
answers.length > 0 && answers.every((a) => a.state === "done")
? "bg-green-500"
: "",
)}
>
Answer
</Badge>
</div>
</div>
{/* Questions List */}
{answerGroups.length > 0 && (
<div style={styles.questionsList}>
<h2
style={{
fontSize: "20px",
fontWeight: "600",
marginBottom: "16px",
}}
>
Research Questions
</h2>
{answerGroups.map((group, index) => {
const latestState = group.states[group.states.length - 1];
const questionId = group.id || `question-${index}`;
const isExpanded = expandedQuestions[questionId];
{/* Retrieve Panel */}
<Card
className={cn(
"border-2 transition-all duration-300",
retrieve?.state === "inprogress"
? "border-blue-500 shadow-blue-100 shadow-lg"
: retrieve?.state === "done"
? "border-green-500"
: retrieve?.state === "error"
? "border-red-500"
: "border-gray-200",
)}
>
<CardHeader>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<Database className="h-5 w-5 text-gray-700" />
<CardTitle>Retrieve Information</CardTitle>
</div>
<Badge
variant="outline"
className={cn(
"flex items-center space-x-1",
retrieve?.state === "inprogress"
? "text-blue-500"
: retrieve?.state === "done"
? "text-green-500"
: retrieve?.state === "error"
? "text-red-500"
: "text-gray-500",
)}
>
{getStatusIcon(retrieve?.state)}
<span>{getStatusText(retrieve?.state)}</span>
</Badge>
</div>
<CardDescription>
Retrieving relevant information from the knowledge base
</CardDescription>
</CardHeader>
</Card>
const answerWithoutCitation = group.answer?.replace(
/\[citation:[a-f0-9-]+\]/g,
"",
);
return (
<div key={questionId} style={styles.questionItem}>
<div
style={styles.questionHeader}
onClick={() => toggleQuestion(questionId)}
>
<div
style={{
display: "flex",
alignItems: "flex-start",
gap: "12px",
fontSize: "14px",
}}
>
{renderStateIcon(latestState)}
<span style={styles.questionTitle}>{group.question}</span>
<span
style={{
...styles.arrow,
transform: isExpanded
? "rotate(180deg)"
: "rotate(0deg)",
}}
>
</span>
</div>
<span style={getBadgeStyles(latestState)}>
{latestState === "inprogress"
? "In Progress"
: latestState === "done"
? "Answered"
: "Pending"}
</span>
</div>
<div
style={{
...styles.answerContainer,
...(isExpanded ? styles.answerContainerExpanded : {}),
}}
>
{answerWithoutCitation ? (
<div style={styles.answerContent}>
{answerWithoutCitation}
</div>
) : (
<div
style={{
...styles.loadingContainer,
...styles.answerContent,
}}
>
<span style={{ marginLeft: "8px" }}>
Generating answer...
</span>
</div>
)}
</div>
{/* Analyze Panel */}
{retrieve?.state === "done" && (
<Card
className={cn(
"border-2 transition-all duration-300",
analyze?.state === "inprogress"
? "border-blue-500 shadow-blue-100 shadow-lg"
: analyze?.state === "done"
? "border-green-500"
: analyze?.state === "error"
? "border-red-500"
: "border-gray-200",
)}
>
<CardHeader>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<Brain className="h-5 w-5 text-gray-700" />
<CardTitle>Analyze Information</CardTitle>
</div>
);
})}
</div>
<Badge
variant="outline"
className={cn(
"flex items-center space-x-1",
analyze?.state === "inprogress"
? "text-blue-500"
: analyze?.state === "done"
? "text-green-500"
: analyze?.state === "error"
? "text-red-500"
: "text-gray-500",
)}
>
{getStatusIcon(analyze?.state)}
<span>{getStatusText(analyze?.state)}</span>
</Badge>
</div>
<CardDescription>
Analyzing retrieved information and generating questions
</CardDescription>
</CardHeader>
</Card>
)}
{/* Answer Panel */}
{analyze?.state === "done" && answers.length > 0 && (
<Card>
<CardHeader>
<div className="flex items-center space-x-2">
<MessageSquare className="h-5 w-5 text-gray-700" />
<CardTitle>Answers</CardTitle>
</div>
<CardDescription>
Detailed answers to the generated questions
</CardDescription>
</CardHeader>
<CardContent>
<Accordion type="multiple" className="w-full">
{answers.map((answer, index) => (
<AccordionItem
key={answer.id}
value={answer.id}
className={cn(
"mb-4 border rounded-lg overflow-hidden",
answer.state === "inprogress"
? "border-blue-500 shadow-blue-100 shadow-sm"
: answer.state === "done"
? "border-green-100"
: answer.state === "error"
? "border-red-100"
: "border-gray-200",
)}
>
<AccordionTrigger className="px-4 py-3 hover:bg-gray-50">
<div className="flex items-center space-x-3 text-left">
<Badge className="shrink-0 bg-gray-700 text-white">
Q{index + 1}
</Badge>
<div className="flex-1">
<p className="font-medium">{answer.question}</p>
</div>
<Badge
variant="outline"
className={cn(
"ml-auto flex items-center space-x-1 shrink-0",
answer.state === "inprogress"
? "text-blue-500"
: answer.state === "done"
? "text-green-500"
: answer.state === "error"
? "text-red-500"
: "text-gray-500",
)}
>
{getStatusIcon(answer.state)}
<span>{getStatusText(answer.state)}</span>
</Badge>
</div>
</AccordionTrigger>
<AccordionContent className="px-4 pb-4 pt-1">
<div
className={cn(
"p-3 rounded-md",
answer.state === "done"
? "bg-green-50"
: answer.state === "inprogress"
? "bg-blue-50"
: "bg-gray-50",
)}
>
{answer.answer ? (
<Markdown content={answer.answer} />
) : (
<div className="flex items-center justify-center p-4 text-gray-500">
{answer.state === "inprogress" ? (
<div className="flex items-center space-x-2">
<Loader2 className="h-4 w-4 animate-spin" />
<span>Generating answer...</span>
</div>
) : (
<span>Waiting for answer</span>
)}
</div>
)}
</div>
</AccordionContent>
</AccordionItem>
))}
</Accordion>
</CardContent>
<CardFooter className="flex justify-between">
<div className="text-sm text-gray-500">
{answers.filter((a) => a.state === "done").length} of{" "}
{answers.length} questions answered
</div>
<Progress
value={
(answers.filter((a) => a.state === "done").length /
answers.length) *
100
}
className="h-2 w-1/3 bg-gray-200"
/>
</CardFooter>
</Card>
)}
</div>
);
@@ -33,6 +33,14 @@ You can configure [LLM model](https://ts.llamaindex.ai/docs/llamaindex/modules/l
For Deep Research, we have a custom component located in `components/deep_research_event.jsx`. This is used to display the results of the deep research workflow in a more user-friendly way
### Generate a new UI Component from workflow event
You can regenerate a new UI component from the workflow event schema by running the following command:
```
npm run generate:ui
```
## Use Case
We have prepared an [example workflow](./src/app/workflow.ts) for the Deep Research use case, where you can request a detailed answer about the example documents in the [./data](./data) directory.
@@ -119,17 +119,30 @@ class ResearchEvent extends WorkflowEvent<ResearchQuestion[]> {}
class ReportEvent extends WorkflowEvent<{}> {}
class StopEvent extends StopEventBase<AsyncGenerator<ChatResponseChunk>> {}
type DeepResearchEventData = {
event: "retrieve" | "analyze" | "answer";
state: "pending" | "inprogress" | "done" | "error";
id?: string;
question?: string;
answer?: string;
};
export const UIEventSchema = z
.object({
event: z
.enum(["retrieve", "analyze", "answer"])
.describe(
"The type of event. DeepResearch has 3 main stages:\n1. retrieve: Retrieve the context from the vector store\n2. analyze: Analyze the context and generate a research questions to answer\n3. answer: Answer the provided questions. Each question has a unique id, when the state is done, the event will have the answer for the question.",
),
state: z
.enum(["pending", "inprogress", "done", "error"])
.describe("The state for each event"),
id: z.string().optional().describe("The id of the question"),
question: z
.string()
.optional()
.describe("The question generated by the LLM"),
answer: z.string().optional().describe("The answer generated by the LLM"),
})
.describe("DeepResearchEvent");
class DeepResearchEvent extends WorkflowEvent<{
type UIEventData = z.infer<typeof UIEventSchema>;
class UIEvent extends WorkflowEvent<{
type: "ui_event";
data: DeepResearchEventData;
data: UIEventData;
}> {}
// workflow definition
@@ -200,7 +213,7 @@ class DeepResearchWorkflow extends Workflow<
await this.initWorkflow(ev.data);
ctx.sendEvent(
new DeepResearchEvent({
new UIEvent({
type: "ui_event",
data: { event: "retrieve", state: "inprogress" },
}),
@@ -211,7 +224,7 @@ class DeepResearchWorkflow extends Workflow<
ctx.sendEvent(toSourceEvent(retrievedNodes));
ctx.sendEvent(
new DeepResearchEvent({
new UIEvent({
type: "ui_event",
data: { event: "retrieve", state: "done" },
}),
@@ -227,7 +240,7 @@ class DeepResearchWorkflow extends Workflow<
ev: PlanResearchEvent,
): Promise<ResearchEvent | ReportEvent | StopEvent> => {
ctx.sendEvent(
new DeepResearchEvent({
new UIEvent({
type: "ui_event",
data: { event: "analyze", state: "inprogress" },
}),
@@ -239,7 +252,7 @@ class DeepResearchWorkflow extends Workflow<
// Stop workflow due to decision from LLM
if (decision === "cancel") {
ctx.sendEvent(
new DeepResearchEvent({
new UIEvent({
type: "ui_event",
data: { event: "analyze", state: "done" },
}),
@@ -262,7 +275,7 @@ class DeepResearchWorkflow extends Workflow<
researchQuestions.forEach(({ questionId: id, question }) => {
ctx.sendEvent(
new DeepResearchEvent({
new UIEvent({
type: "ui_event",
data: { event: "answer", state: "pending", id, question },
}),
@@ -279,7 +292,7 @@ class DeepResearchWorkflow extends Workflow<
});
ctx.sendEvent(
new DeepResearchEvent({
new UIEvent({
type: "ui_event",
data: { event: "analyze", state: "done" },
}),
@@ -298,7 +311,7 @@ class DeepResearchWorkflow extends Workflow<
const researchResults: ResearchResult[] = await Promise.all(
researchQuestions.map(async ({ questionId: id, question }) => {
ctx.sendEvent(
new DeepResearchEvent({
new UIEvent({
type: "ui_event",
data: { event: "answer", state: "inprogress", id, question },
}),
@@ -307,7 +320,7 @@ class DeepResearchWorkflow extends Workflow<
const answer = await this.answerQuestion(question);
ctx.sendEvent(
new DeepResearchEvent({
new UIEvent({
type: "ui_event",
data: { event: "answer", state: "done", id, question, answer },
}),
@@ -19,7 +19,7 @@ python-dotenv = "^1.0.0"
pydantic = "<2.10"
aiostream = "^0.5.2"
llama-index-core = "^0.12.28"
llama-index-server = "^0.1.13"
llama-index-server = "^0.1.14"
[tool.poetry.group.dev.dependencies]
mypy = "^1.8.0"
@@ -2,18 +2,20 @@
"name": "llamaindex-server",
"version": "0.1.0",
"scripts": {
"generate": "tsx src/generate.ts",
"generate": "tsx src/generate.ts datasource",
"generate:datasource": "tsx src/generate.ts datasource",
"generate:ui": "tsx src/generate.ts ui",
"dev": "tsx watch src/index.ts",
"start": "tsx src/index.ts"
},
"dependencies": {
"@llamaindex/openai": "0.2.0",
"@llamaindex/readers": "^2.0.0",
"@llamaindex/server": "0.1.4",
"@llamaindex/server": "0.1.5",
"@llamaindex/tools": "0.0.4",
"dotenv": "^16.4.7",
"llamaindex": "0.10.1",
"zod": "^3.23.8"
"zod": "^3.23.8",
"llamaindex": "0.10.2"
},
"devDependencies": {
"@types/node": "^20.10.3",
@@ -1,7 +1,14 @@
import "dotenv/config";
import { SimpleDirectoryReader } from "@llamaindex/readers/directory";
import { storageContextFromDefaults, VectorStoreIndex } from "llamaindex";
import {
OpenAI,
storageContextFromDefaults,
VectorStoreIndex,
} from "llamaindex";
import { initSettings } from "./app/settings";
import fs from "fs";
import { generateEventComponent } from "@llamaindex/server";
import { UIEventSchema } from "./app/workflow";
async function generateDatasource() {
console.log(`Generating storage context...`);
@@ -19,7 +26,30 @@ async function generateDatasource() {
console.log("Storage context successfully generated.");
}
async function generateUi() {
// Also works well with Claude 3.5 Sonnet and Google Gemini 2.5 Pro
const llm = new OpenAI({ model: "gpt-4.1" });
// You can also generate for other workflow events
const generatedCode = await generateEventComponent(UIEventSchema, llm);
// Write the generated code to components/ui_event.ts
fs.writeFileSync("components/ui_event.jsx", generatedCode);
}
(async () => {
const args = process.argv.slice(2);
const command = args[0];
initSettings();
await generateDatasource();
if (command === "datasource") {
await generateDatasource();
} else if (command === "ui") {
await generateUi();
} else {
console.error(
'Invalid command. Please use "datasource" or "ui". Running "datasource" by default.',
);
await generateDatasource(); // Default behavior or could throw an error
}
})();