merge main and remove book resturant and accommodation tools/uis
This commit is contained in:
4
.dockerignore
Normal file
4
.dockerignore
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
node_modules
|
||||||
|
.next
|
||||||
|
.git
|
||||||
|
.env
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
# agent-ui demo
|
# Chat LangGraph
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> This repo is still a work in progress and is not intended for use. Estimated launch date 03/11. Thank you for your patience.
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,13 @@ import { stockbrokerGraph } from "./stockbroker";
|
|||||||
import { ChatOpenAI } from "@langchain/openai";
|
import { ChatOpenAI } from "@langchain/openai";
|
||||||
import { tripPlannerGraph } from "./trip-planner";
|
import { tripPlannerGraph } from "./trip-planner";
|
||||||
import { formatMessages } from "./utils/format-messages";
|
import { formatMessages } from "./utils/format-messages";
|
||||||
|
import { graph as openCodeGraph } from "./open-code";
|
||||||
|
import { graph as orderPizzaGraph } from "./pizza-orderer";
|
||||||
|
|
||||||
const allToolDescriptions = `- stockbroker: can fetch the price of a ticker, purchase/sell a ticker, or get the user's portfolio
|
const allToolDescriptions = `- stockbroker: can fetch the price of a ticker, purchase/sell a ticker, or get the user's portfolio
|
||||||
- tripPlanner: helps the user plan their trip. it can suggest restaurants, and places to stay in any given location.`;
|
- tripPlanner: helps the user plan their trip. it can suggest restaurants, and places to stay in any given location.
|
||||||
|
- openCode: can write code for the user. call this tool when the user asks you to write code
|
||||||
|
- orderPizza: can order a pizza for the user`;
|
||||||
|
|
||||||
async function router(
|
async function router(
|
||||||
state: GenerativeUIState,
|
state: GenerativeUIState,
|
||||||
@@ -19,7 +23,13 @@ ${allToolDescriptions}
|
|||||||
`;
|
`;
|
||||||
const routerSchema = z.object({
|
const routerSchema = z.object({
|
||||||
route: z
|
route: z
|
||||||
.enum(["stockbroker", "tripPlanner", "generalInput"])
|
.enum([
|
||||||
|
"stockbroker",
|
||||||
|
"tripPlanner",
|
||||||
|
"openCode",
|
||||||
|
"orderPizza",
|
||||||
|
"generalInput",
|
||||||
|
])
|
||||||
.describe(routerDescription),
|
.describe(routerDescription),
|
||||||
});
|
});
|
||||||
const routerTool = {
|
const routerTool = {
|
||||||
@@ -73,7 +83,7 @@ Please pick the proper route based on the most recent message, in the context of
|
|||||||
|
|
||||||
function handleRoute(
|
function handleRoute(
|
||||||
state: GenerativeUIState,
|
state: GenerativeUIState,
|
||||||
): "stockbroker" | "tripPlanner" | "generalInput" {
|
): "stockbroker" | "tripPlanner" | "openCode" | "orderPizza" | "generalInput" {
|
||||||
return state.next;
|
return state.next;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,16 +114,22 @@ const builder = new StateGraph(GenerativeUIAnnotation)
|
|||||||
.addNode("router", router)
|
.addNode("router", router)
|
||||||
.addNode("stockbroker", stockbrokerGraph)
|
.addNode("stockbroker", stockbrokerGraph)
|
||||||
.addNode("tripPlanner", tripPlannerGraph)
|
.addNode("tripPlanner", tripPlannerGraph)
|
||||||
|
.addNode("openCode", openCodeGraph)
|
||||||
|
.addNode("orderPizza", orderPizzaGraph)
|
||||||
.addNode("generalInput", handleGeneralInput)
|
.addNode("generalInput", handleGeneralInput)
|
||||||
|
|
||||||
.addConditionalEdges("router", handleRoute, [
|
.addConditionalEdges("router", handleRoute, [
|
||||||
"stockbroker",
|
"stockbroker",
|
||||||
"tripPlanner",
|
"tripPlanner",
|
||||||
|
"openCode",
|
||||||
|
"orderPizza",
|
||||||
"generalInput",
|
"generalInput",
|
||||||
])
|
])
|
||||||
.addEdge(START, "router")
|
.addEdge(START, "router")
|
||||||
.addEdge("stockbroker", END)
|
.addEdge("stockbroker", END)
|
||||||
.addEdge("tripPlanner", END)
|
.addEdge("tripPlanner", END)
|
||||||
|
.addEdge("openCode", END)
|
||||||
|
.addEdge("orderPizza", END)
|
||||||
.addEdge("generalInput", END);
|
.addEdge("generalInput", END);
|
||||||
|
|
||||||
export const graph = builder.compile();
|
export const graph = builder.compile();
|
||||||
45
agent/open-code/index.ts
Normal file
45
agent/open-code/index.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import {
|
||||||
|
END,
|
||||||
|
LangGraphRunnableConfig,
|
||||||
|
START,
|
||||||
|
StateGraph,
|
||||||
|
} from "@langchain/langgraph";
|
||||||
|
import { OpenCodeAnnotation, OpenCodeState } from "./types";
|
||||||
|
import { planner } from "./nodes/planner";
|
||||||
|
import {
|
||||||
|
executor,
|
||||||
|
SUCCESSFULLY_COMPLETED_STEPS_CONTENT,
|
||||||
|
} from "./nodes/executor";
|
||||||
|
import { AIMessage } from "@langchain/langgraph-sdk";
|
||||||
|
|
||||||
|
function conditionallyEnd(
|
||||||
|
state: OpenCodeState,
|
||||||
|
config: LangGraphRunnableConfig,
|
||||||
|
): typeof END | "planner" {
|
||||||
|
const fullWriteAccess = !!config.configurable?.permissions?.full_write_access;
|
||||||
|
const lastAiMessage = state.messages.findLast(
|
||||||
|
(m) => m.getType() === "ai",
|
||||||
|
) as unknown as AIMessage;
|
||||||
|
|
||||||
|
// If the user did not grant full write access, or the last AI message is the success message, end
|
||||||
|
// otherwise, loop back to the start.
|
||||||
|
if (
|
||||||
|
(typeof lastAiMessage.content === "string" &&
|
||||||
|
lastAiMessage.content === SUCCESSFULLY_COMPLETED_STEPS_CONTENT) ||
|
||||||
|
!fullWriteAccess
|
||||||
|
) {
|
||||||
|
return END;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "planner";
|
||||||
|
}
|
||||||
|
|
||||||
|
const workflow = new StateGraph(OpenCodeAnnotation)
|
||||||
|
.addNode("planner", planner)
|
||||||
|
.addNode("executor", executor)
|
||||||
|
.addEdge(START, "planner")
|
||||||
|
.addEdge("planner", "executor")
|
||||||
|
.addConditionalEdges("executor", conditionallyEnd, ["planner", END]);
|
||||||
|
|
||||||
|
export const graph = workflow.compile();
|
||||||
|
graph.name = "Open Code Graph";
|
||||||
127
agent/open-code/nodes/executor.ts
Normal file
127
agent/open-code/nodes/executor.ts
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
import fs from "fs/promises";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
import { AIMessage } from "@langchain/langgraph-sdk";
|
||||||
|
import { OpenCodeState, OpenCodeUpdate } from "../types";
|
||||||
|
import { LangGraphRunnableConfig } from "@langchain/langgraph";
|
||||||
|
import ComponentMap from "../../uis";
|
||||||
|
import { typedUi } from "@langchain/langgraph-sdk/react-ui/server";
|
||||||
|
|
||||||
|
export const SUCCESSFULLY_COMPLETED_STEPS_CONTENT =
|
||||||
|
"Successfully completed all the steps in the plan. Please let me know if you need anything else!";
|
||||||
|
|
||||||
|
export async function executor(
|
||||||
|
state: OpenCodeState,
|
||||||
|
config: LangGraphRunnableConfig,
|
||||||
|
): Promise<OpenCodeUpdate> {
|
||||||
|
const ui = typedUi<typeof ComponentMap>(config);
|
||||||
|
|
||||||
|
const lastPlanToolCall = state.messages.findLast(
|
||||||
|
(m) =>
|
||||||
|
m.getType() === "ai" &&
|
||||||
|
(m as unknown as AIMessage).tool_calls?.some((tc) => tc.name === "plan"),
|
||||||
|
) as AIMessage | undefined;
|
||||||
|
const planToolCallArgs = lastPlanToolCall?.tool_calls?.[0]?.args;
|
||||||
|
const nextPlanItem = planToolCallArgs?.remainingPlans?.[0] as
|
||||||
|
| string
|
||||||
|
| undefined;
|
||||||
|
const numSeenPlans =
|
||||||
|
[
|
||||||
|
...(planToolCallArgs?.executedPlans ?? []),
|
||||||
|
...(planToolCallArgs?.rejectedPlans ?? []),
|
||||||
|
]?.length ?? 0;
|
||||||
|
|
||||||
|
if (!nextPlanItem) {
|
||||||
|
// All plans have been executed
|
||||||
|
const successfullyFinishedMsg: AIMessage = {
|
||||||
|
type: "ai",
|
||||||
|
id: uuidv4(),
|
||||||
|
content: SUCCESSFULLY_COMPLETED_STEPS_CONTENT,
|
||||||
|
};
|
||||||
|
return { messages: [successfullyFinishedMsg] };
|
||||||
|
}
|
||||||
|
|
||||||
|
let updateFileContents = "";
|
||||||
|
switch (numSeenPlans) {
|
||||||
|
case 0:
|
||||||
|
updateFileContents = await fs.readFile(
|
||||||
|
"agent/open-code/nodes/plan-code/step-1.txt",
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
updateFileContents = await fs.readFile(
|
||||||
|
"agent/open-code/nodes/plan-code/step-2.txt",
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
updateFileContents = await fs.readFile(
|
||||||
|
"agent/open-code/nodes/plan-code/step-3.txt",
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
updateFileContents = await fs.readFile(
|
||||||
|
"agent/open-code/nodes/plan-code/step-4.txt",
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
updateFileContents = await fs.readFile(
|
||||||
|
"agent/open-code/nodes/plan-code/step-5.txt",
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
updateFileContents = await fs.readFile(
|
||||||
|
"agent/open-code/nodes/plan-code/step-6.txt",
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
updateFileContents = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!updateFileContents) {
|
||||||
|
throw new Error("No file updates found!");
|
||||||
|
}
|
||||||
|
|
||||||
|
const toolCallId = uuidv4();
|
||||||
|
const aiMessage: AIMessage = {
|
||||||
|
type: "ai",
|
||||||
|
id: uuidv4(),
|
||||||
|
content: "",
|
||||||
|
tool_calls: [
|
||||||
|
{
|
||||||
|
name: "update_file",
|
||||||
|
args: {
|
||||||
|
new_file_content: updateFileContents,
|
||||||
|
executed_plan_item: nextPlanItem,
|
||||||
|
},
|
||||||
|
id: toolCallId,
|
||||||
|
type: "tool_call",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const fullWriteAccess = !!config.configurable?.permissions?.full_write_access;
|
||||||
|
|
||||||
|
ui.push(
|
||||||
|
{
|
||||||
|
name: "proposed-change",
|
||||||
|
content: {
|
||||||
|
toolCallId,
|
||||||
|
change: updateFileContents,
|
||||||
|
planItem: nextPlanItem,
|
||||||
|
fullWriteAccess,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ message: aiMessage },
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
messages: [aiMessage],
|
||||||
|
ui: ui.items,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
};
|
||||||
|
}
|
||||||
5
agent/open-code/nodes/plan-code/step-1.txt
Normal file
5
agent/open-code/nodes/plan-code/step-1.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
```bash
|
||||||
|
npx create-react-app todo-app --template typescript
|
||||||
|
cd todo-app
|
||||||
|
mkdir -p src/{components,styles,utils}
|
||||||
|
```
|
||||||
21
agent/open-code/nodes/plan-code/step-2.txt
Normal file
21
agent/open-code/nodes/plan-code/step-2.txt
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
```tsx
|
||||||
|
// src/components/TodoItem.tsx
|
||||||
|
import React from 'react';
|
||||||
|
import styles from '../styles/TodoItem.module.css';
|
||||||
|
|
||||||
|
interface TodoItemProps {
|
||||||
|
id: string;
|
||||||
|
text: string;
|
||||||
|
completed: boolean;
|
||||||
|
onToggle: (id: string) => void;
|
||||||
|
onDelete: (id: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TodoItem: React.FC<TodoItemProps> = ({ id, text, completed, onToggle, onDelete }) => (
|
||||||
|
<div className={styles.todoItem}>
|
||||||
|
<input type='checkbox' checked={completed} onChange={() => onToggle(id)} />
|
||||||
|
<span className={completed ? styles.completed : ''}>{text}</span>
|
||||||
|
<button onClick={() => onDelete(id)}>Delete</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
```
|
||||||
22
agent/open-code/nodes/plan-code/step-3.txt
Normal file
22
agent/open-code/nodes/plan-code/step-3.txt
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
```tsx
|
||||||
|
// src/context/TodoContext.tsx
|
||||||
|
import React, { createContext, useContext, useReducer } from 'react';
|
||||||
|
|
||||||
|
type Todo = { id: string; text: string; completed: boolean; };
|
||||||
|
|
||||||
|
type TodoState = { todos: Todo[]; };
|
||||||
|
type TodoAction =
|
||||||
|
| { type: 'ADD_TODO'; payload: string }
|
||||||
|
| { type: 'TOGGLE_TODO'; payload: string }
|
||||||
|
| { type: 'DELETE_TODO'; payload: string };
|
||||||
|
|
||||||
|
const TodoContext = createContext<{
|
||||||
|
state: TodoState;
|
||||||
|
dispatch: React.Dispatch<TodoAction>;
|
||||||
|
} | undefined>(undefined);
|
||||||
|
|
||||||
|
export const TodoProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
|
const [state, dispatch] = useReducer(todoReducer, { todos: [] });
|
||||||
|
return <TodoContext.Provider value={{ state, dispatch }}>{children}</TodoContext.Provider>;
|
||||||
|
};
|
||||||
|
```
|
||||||
33
agent/open-code/nodes/plan-code/step-4.txt
Normal file
33
agent/open-code/nodes/plan-code/step-4.txt
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
```tsx
|
||||||
|
// src/components/AddTodo.tsx
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import styles from '../styles/AddTodo.module.css';
|
||||||
|
|
||||||
|
export const AddTodo: React.FC<{ onAdd: (text: string) => void }> = ({ onAdd }) => {
|
||||||
|
const [text, setText] = useState('');
|
||||||
|
const [error, setError] = useState('');
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!text.trim()) {
|
||||||
|
setError('Todo text cannot be empty');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onAdd(text.trim());
|
||||||
|
setText('');
|
||||||
|
setError('');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit} className={styles.form}>
|
||||||
|
<input
|
||||||
|
value={text}
|
||||||
|
onChange={(e) => setText(e.target.value)}
|
||||||
|
placeholder='Add a new todo'
|
||||||
|
/>
|
||||||
|
{error && <div className={styles.error}>{error}</div>}
|
||||||
|
<button type='submit'>Add Todo</button>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
22
agent/open-code/nodes/plan-code/step-5.txt
Normal file
22
agent/open-code/nodes/plan-code/step-5.txt
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
```tsx
|
||||||
|
// src/components/TodoFilters.tsx
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
type FilterType = 'all' | 'active' | 'completed';
|
||||||
|
|
||||||
|
export const TodoFilters: React.FC<{
|
||||||
|
currentFilter: FilterType;
|
||||||
|
onFilterChange: (filter: FilterType) => void;
|
||||||
|
onSortChange: (ascending: boolean) => void;
|
||||||
|
}> = ({ currentFilter, onFilterChange, onSortChange }) => (
|
||||||
|
<div>
|
||||||
|
<select value={currentFilter} onChange={(e) => onFilterChange(e.target.value as FilterType)}>
|
||||||
|
<option value='all'>All</option>
|
||||||
|
<option value='active'>Active</option>
|
||||||
|
<option value='completed'>Completed</option>
|
||||||
|
</select>
|
||||||
|
<button onClick={() => onSortChange(true)}>Sort A-Z</button>
|
||||||
|
<button onClick={() => onSortChange(false)}>Sort Z-A</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
```
|
||||||
13
agent/open-code/nodes/plan-code/step-6.txt
Normal file
13
agent/open-code/nodes/plan-code/step-6.txt
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
```tsx
|
||||||
|
// src/utils/storage.ts
|
||||||
|
const STORAGE_KEY = 'todos';
|
||||||
|
|
||||||
|
export const saveTodos = (todos: Todo[]) => {
|
||||||
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(todos));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const loadTodos = (): Todo[] => {
|
||||||
|
const stored = localStorage.getItem(STORAGE_KEY);
|
||||||
|
return stored ? JSON.parse(stored) : [];
|
||||||
|
};
|
||||||
|
```
|
||||||
114
agent/open-code/nodes/planner.ts
Normal file
114
agent/open-code/nodes/planner.ts
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
import { AIMessage, ToolMessage } from "@langchain/langgraph-sdk";
|
||||||
|
import { OpenCodeState, OpenCodeUpdate } from "../types";
|
||||||
|
import { DO_NOT_RENDER_ID_PREFIX } from "@/lib/ensure-tool-responses";
|
||||||
|
import { LangGraphRunnableConfig } from "@langchain/langgraph";
|
||||||
|
import ComponentMap from "../../uis";
|
||||||
|
import { typedUi } from "@langchain/langgraph-sdk/react-ui/server";
|
||||||
|
|
||||||
|
const PLAN = [
|
||||||
|
"Set up project scaffolding using Create React App and implement basic folder structure for components, styles, and utilities.",
|
||||||
|
"Create reusable UI components for TodoItem, including styling with CSS modules.",
|
||||||
|
"Implement state management using React Context to handle todo items, including actions for adding, updating, and deleting todos.",
|
||||||
|
"Add form functionality for creating new todos with input validation and error handling.",
|
||||||
|
"Create filtering and sorting capabilities to allow users to view completed, active, or all todos.",
|
||||||
|
"Implement local storage integration to persist todo items between page refreshes.",
|
||||||
|
];
|
||||||
|
|
||||||
|
export async function planner(
|
||||||
|
state: OpenCodeState,
|
||||||
|
config: LangGraphRunnableConfig,
|
||||||
|
): Promise<OpenCodeUpdate> {
|
||||||
|
const ui = typedUi<typeof ComponentMap>(config);
|
||||||
|
|
||||||
|
const lastUpdateCodeToolCall = state.messages.findLast(
|
||||||
|
(m) =>
|
||||||
|
m.getType() === "ai" &&
|
||||||
|
(m as unknown as AIMessage).tool_calls?.some(
|
||||||
|
(tc) => tc.name === "update_file",
|
||||||
|
),
|
||||||
|
) as AIMessage | undefined;
|
||||||
|
const lastUpdateToolCallResponse = state.messages.findLast(
|
||||||
|
(m) =>
|
||||||
|
m.getType() === "tool" &&
|
||||||
|
(m as unknown as ToolMessage).tool_call_id ===
|
||||||
|
lastUpdateCodeToolCall?.tool_calls?.[0]?.id,
|
||||||
|
) as ToolMessage | undefined;
|
||||||
|
const lastPlanToolCall = state.messages.findLast(
|
||||||
|
(m) =>
|
||||||
|
m.getType() === "ai" &&
|
||||||
|
(m as unknown as AIMessage).tool_calls?.some((tc) => tc.name === "plan"),
|
||||||
|
) as AIMessage | undefined;
|
||||||
|
|
||||||
|
const wasPlanRejected = (
|
||||||
|
lastUpdateToolCallResponse?.content as string | undefined
|
||||||
|
)
|
||||||
|
?.toLowerCase()
|
||||||
|
.includes("rejected");
|
||||||
|
|
||||||
|
const planToolCallArgs = lastPlanToolCall?.tool_calls?.[0]?.args;
|
||||||
|
const executedPlans: string[] = planToolCallArgs?.executedPlans ?? [];
|
||||||
|
const rejectedPlans: string[] = planToolCallArgs?.rejectedPlans ?? [];
|
||||||
|
let remainingPlans: string[] = planToolCallArgs?.remainingPlans ?? PLAN;
|
||||||
|
|
||||||
|
const proposedChangePlanItem: string | undefined =
|
||||||
|
lastUpdateCodeToolCall?.tool_calls?.[0]?.args?.executed_plan_item;
|
||||||
|
if (proposedChangePlanItem) {
|
||||||
|
if (wasPlanRejected) {
|
||||||
|
rejectedPlans.push(proposedChangePlanItem);
|
||||||
|
} else {
|
||||||
|
executedPlans.push(proposedChangePlanItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
remainingPlans = remainingPlans.filter((p) => p !== proposedChangePlanItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = proposedChangePlanItem
|
||||||
|
? `I've updated the plan list based on the last proposed change.`
|
||||||
|
: `I've come up with a detailed plan for building the todo app.`;
|
||||||
|
|
||||||
|
const toolCallId = uuidv4();
|
||||||
|
const aiMessage: AIMessage = {
|
||||||
|
type: "ai",
|
||||||
|
id: uuidv4(),
|
||||||
|
content,
|
||||||
|
tool_calls: [
|
||||||
|
{
|
||||||
|
name: "plan",
|
||||||
|
args: {
|
||||||
|
executedPlans,
|
||||||
|
rejectedPlans,
|
||||||
|
remainingPlans,
|
||||||
|
},
|
||||||
|
id: toolCallId,
|
||||||
|
type: "tool_call",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.push(
|
||||||
|
{
|
||||||
|
name: "code-plan",
|
||||||
|
content: {
|
||||||
|
toolCallId,
|
||||||
|
executedPlans,
|
||||||
|
rejectedPlans,
|
||||||
|
remainingPlans,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ message: aiMessage },
|
||||||
|
);
|
||||||
|
|
||||||
|
const toolMessage: ToolMessage = {
|
||||||
|
type: "tool",
|
||||||
|
id: `${DO_NOT_RENDER_ID_PREFIX}${uuidv4()}`,
|
||||||
|
tool_call_id: toolCallId,
|
||||||
|
content: "User has approved the plan.",
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
messages: [aiMessage, toolMessage],
|
||||||
|
ui: ui.items,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
};
|
||||||
|
}
|
||||||
11
agent/open-code/types.ts
Normal file
11
agent/open-code/types.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Annotation } from "@langchain/langgraph";
|
||||||
|
import { GenerativeUIAnnotation } from "../types";
|
||||||
|
|
||||||
|
export const OpenCodeAnnotation = Annotation.Root({
|
||||||
|
messages: GenerativeUIAnnotation.spec.messages,
|
||||||
|
ui: GenerativeUIAnnotation.spec.ui,
|
||||||
|
timestamp: GenerativeUIAnnotation.spec.timestamp,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type OpenCodeState = typeof OpenCodeAnnotation.State;
|
||||||
|
export type OpenCodeUpdate = typeof OpenCodeAnnotation.Update;
|
||||||
113
agent/pizza-orderer/index.ts
Normal file
113
agent/pizza-orderer/index.ts
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import { ChatAnthropic } from "@langchain/anthropic";
|
||||||
|
import { Annotation, END, START, StateGraph } from "@langchain/langgraph";
|
||||||
|
import { GenerativeUIAnnotation } from "../types";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { AIMessage, ToolMessage } from "@langchain/langgraph-sdk";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
|
||||||
|
const PizzaOrdererAnnotation = Annotation.Root({
|
||||||
|
messages: GenerativeUIAnnotation.spec.messages,
|
||||||
|
});
|
||||||
|
|
||||||
|
async function sleep(ms = 5000) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
const workflow = new StateGraph(PizzaOrdererAnnotation)
|
||||||
|
.addNode("findStore", async (state) => {
|
||||||
|
const findShopSchema = z
|
||||||
|
.object({
|
||||||
|
location: z
|
||||||
|
.string()
|
||||||
|
.describe(
|
||||||
|
"The location the user is in. E.g. 'San Francisco' or 'New York'",
|
||||||
|
),
|
||||||
|
pizza_company: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.describe(
|
||||||
|
"The name of the pizza company. E.g. 'Dominos' or 'Papa John's'. Optional, if not defined it will search for all pizza shops",
|
||||||
|
),
|
||||||
|
})
|
||||||
|
.describe("The schema for finding a pizza shop for the user");
|
||||||
|
const model = new ChatAnthropic({
|
||||||
|
model: "claude-3-5-sonnet-latest",
|
||||||
|
temperature: 0,
|
||||||
|
}).withStructuredOutput(findShopSchema, {
|
||||||
|
name: "find_pizza_shop",
|
||||||
|
includeRaw: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await model.invoke([
|
||||||
|
{
|
||||||
|
role: "system",
|
||||||
|
content:
|
||||||
|
"You are a helpful AI assistant, tasked with extracting information from the conversation between you, and the user, in order to find a pizza shop for them.",
|
||||||
|
},
|
||||||
|
...state.messages,
|
||||||
|
]);
|
||||||
|
|
||||||
|
await sleep();
|
||||||
|
|
||||||
|
const toolResponse: ToolMessage = {
|
||||||
|
type: "tool",
|
||||||
|
id: uuidv4(),
|
||||||
|
content:
|
||||||
|
"I've found a pizza shop at 1119 19th St, San Francisco, CA 94107. The phone number for the shop is 415-555-1234.",
|
||||||
|
tool_call_id:
|
||||||
|
(response.raw as unknown as AIMessage).tool_calls?.[0].id ?? "",
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
messages: [response.raw, toolResponse],
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.addNode("orderPizza", async (state) => {
|
||||||
|
await sleep(1500);
|
||||||
|
|
||||||
|
const placeOrderSchema = z
|
||||||
|
.object({
|
||||||
|
address: z
|
||||||
|
.string()
|
||||||
|
.describe("The address of the store to order the pizza from"),
|
||||||
|
phone_number: z
|
||||||
|
.string()
|
||||||
|
.describe("The phone number of the store to order the pizza from"),
|
||||||
|
order: z.string().describe("The full pizza order for the user"),
|
||||||
|
})
|
||||||
|
.describe("The schema for ordering a pizza for the user");
|
||||||
|
const model = new ChatAnthropic({
|
||||||
|
model: "claude-3-5-sonnet-latest",
|
||||||
|
temperature: 0,
|
||||||
|
}).withStructuredOutput(placeOrderSchema, {
|
||||||
|
name: "place_pizza_order",
|
||||||
|
includeRaw: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await model.invoke([
|
||||||
|
{
|
||||||
|
role: "system",
|
||||||
|
content:
|
||||||
|
"You are a helpful AI assistant, tasked with placing an order for a pizza for the user.",
|
||||||
|
},
|
||||||
|
...state.messages,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const toolResponse: ToolMessage = {
|
||||||
|
type: "tool",
|
||||||
|
id: uuidv4(),
|
||||||
|
content: "Pizza order successfully placed.",
|
||||||
|
tool_call_id:
|
||||||
|
(response.raw as unknown as AIMessage).tool_calls?.[0].id ?? "",
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
messages: [response.raw, toolResponse],
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.addEdge(START, "findStore")
|
||||||
|
.addEdge("findStore", "orderPizza")
|
||||||
|
.addEdge("orderPizza", END);
|
||||||
|
|
||||||
|
export const graph = workflow.compile();
|
||||||
|
graph.name = "Order Pizza Graph";
|
||||||
@@ -8,6 +8,23 @@ import { findToolCall } from "../../find-tool-call";
|
|||||||
import { format, subDays } from "date-fns";
|
import { format, subDays } from "date-fns";
|
||||||
import { Price, Snapshot } from "../../types";
|
import { Price, Snapshot } from "../../types";
|
||||||
|
|
||||||
|
async function getNextPageData(url: string) {
|
||||||
|
if (!process.env.FINANCIAL_DATASETS_API_KEY) {
|
||||||
|
throw new Error("Financial datasets API key not set");
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
method: "GET",
|
||||||
|
headers: { "X-API-KEY": process.env.FINANCIAL_DATASETS_API_KEY },
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await fetch(url, options);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Failed to fetch prices");
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
async function getPricesForTicker(ticker: string): Promise<{
|
async function getPricesForTicker(ticker: string): Promise<{
|
||||||
oneDayPrices: Price[];
|
oneDayPrices: Price[];
|
||||||
thirtyDayPrices: Price[];
|
thirtyDayPrices: Price[];
|
||||||
@@ -54,7 +71,21 @@ async function getPricesForTicker(ticker: string): Promise<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { prices: pricesOneDay } = await resOneDay.json();
|
const { prices: pricesOneDay } = await resOneDay.json();
|
||||||
const { prices: pricesThirtyDays } = await resThirtyDays.json();
|
const { prices: pricesThirtyDays, next_page_url } =
|
||||||
|
await resThirtyDays.json();
|
||||||
|
|
||||||
|
let nextPageUrlThirtyDays = next_page_url;
|
||||||
|
|
||||||
|
let iters = 0;
|
||||||
|
while (nextPageUrlThirtyDays) {
|
||||||
|
if (iters > 10) {
|
||||||
|
throw new Error("MAX ITERS REACHED");
|
||||||
|
}
|
||||||
|
const nextPageData = await getNextPageData(nextPageUrlThirtyDays);
|
||||||
|
pricesThirtyDays.push(...nextPageData.prices);
|
||||||
|
nextPageUrlThirtyDays = nextPageData.next_page_url;
|
||||||
|
iters += 1;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
oneDayPrices: pricesOneDay,
|
oneDayPrices: pricesOneDay,
|
||||||
@@ -145,28 +176,37 @@ export async function callTools(
|
|||||||
|
|
||||||
if (stockbrokerToolCall) {
|
if (stockbrokerToolCall) {
|
||||||
const prices = await getPricesForTicker(stockbrokerToolCall.args.ticker);
|
const prices = await getPricesForTicker(stockbrokerToolCall.args.ticker);
|
||||||
ui.write("stock-price", {
|
ui.push(
|
||||||
ticker: stockbrokerToolCall.args.ticker,
|
{
|
||||||
...prices,
|
name: "stock-price",
|
||||||
});
|
content: { ticker: stockbrokerToolCall.args.ticker, ...prices },
|
||||||
|
},
|
||||||
|
{ message },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (portfolioToolCall) {
|
if (portfolioToolCall) {
|
||||||
ui.write("portfolio", {});
|
ui.push({ name: "portfolio", content: {} }, { message });
|
||||||
}
|
}
|
||||||
if (buyStockToolCall) {
|
if (buyStockToolCall) {
|
||||||
const snapshot = await getPriceSnapshotForTicker(
|
const snapshot = await getPriceSnapshotForTicker(
|
||||||
buyStockToolCall.args.ticker,
|
buyStockToolCall.args.ticker,
|
||||||
);
|
);
|
||||||
ui.write("buy-stock", {
|
ui.push(
|
||||||
toolCallId: buyStockToolCall.id ?? "",
|
{
|
||||||
snapshot,
|
name: "buy-stock",
|
||||||
quantity: buyStockToolCall.args.quantity,
|
content: {
|
||||||
});
|
toolCallId: buyStockToolCall.id ?? "",
|
||||||
|
snapshot,
|
||||||
|
quantity: buyStockToolCall.args.quantity,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ message },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
messages: [message],
|
messages: [message],
|
||||||
ui: ui.collect as StockbrokerUpdate["ui"],
|
ui: ui.items,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,9 @@ export async function extraction(
|
|||||||
.describe("The end date of the trip. Should be in YYYY-MM-DD format"),
|
.describe("The end date of the trip. Should be in YYYY-MM-DD format"),
|
||||||
numberOfGuests: z
|
numberOfGuests: z
|
||||||
.number()
|
.number()
|
||||||
.describe("The number of guests for the trip. Should default to 2 if not specified"),
|
.describe(
|
||||||
|
"The number of guests for the trip. Should default to 2 if not specified",
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
const model = new ChatOpenAI({ model: "gpt-4o", temperature: 0 }).bindTools([
|
const model = new ChatOpenAI({ model: "gpt-4o", temperature: 0 }).bindTools([
|
||||||
@@ -126,6 +128,6 @@ Extract only what is specified by the user. It is okay to leave fields blank if
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
tripDetails: extractionDetailsWithDefaults,
|
tripDetails: extractionDetailsWithDefaults,
|
||||||
messages: [response, extractToolResponse]
|
messages: [response, extractToolResponse],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,12 @@ import { LangGraphRunnableConfig } from "@langchain/langgraph";
|
|||||||
import { getAccommodationsListProps } from "../utils/get-accommodations";
|
import { getAccommodationsListProps } from "../utils/get-accommodations";
|
||||||
import { findToolCall } from "../../find-tool-call";
|
import { findToolCall } from "../../find-tool-call";
|
||||||
|
|
||||||
const listAccommodationsSchema = z.object({}).describe("A tool to list accommodations for the user")
|
const listAccommodationsSchema = z
|
||||||
const bookAccommodationSchema = z.object({
|
.object({})
|
||||||
accommodationName: z.string().describe("The name of the accommodation to book a reservation for"),
|
.describe("A tool to list accommodations for the user");
|
||||||
}).describe("A tool to book a reservation for an accommodation");
|
const listRestaurantsSchema = z
|
||||||
const listRestaurantsSchema = z.object({}).describe("A tool to list restaurants for the user");
|
.object({})
|
||||||
const bookRestaurantSchema = z.object({
|
.describe("A tool to list restaurants for the user");
|
||||||
restaurantName: z.string().describe("The name of the restaurant to book a reservation for"),
|
|
||||||
}).describe("A tool to book a reservation for a restaurant");
|
|
||||||
|
|
||||||
const ACCOMMODATIONS_TOOLS = [
|
const ACCOMMODATIONS_TOOLS = [
|
||||||
{
|
{
|
||||||
@@ -22,21 +20,11 @@ const ACCOMMODATIONS_TOOLS = [
|
|||||||
description: "A tool to list accommodations for the user",
|
description: "A tool to list accommodations for the user",
|
||||||
schema: listAccommodationsSchema,
|
schema: listAccommodationsSchema,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "book-accommodation",
|
|
||||||
description: "A tool to book a reservation for an accommodation",
|
|
||||||
schema: bookAccommodationSchema,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "list-restaurants",
|
name: "list-restaurants",
|
||||||
description: "A tool to list restaurants for the user",
|
description: "A tool to list restaurants for the user",
|
||||||
schema: listRestaurantsSchema,
|
schema: listRestaurantsSchema,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "book-restaurant",
|
|
||||||
description: "A tool to book a reservation for a restaurant",
|
|
||||||
schema: bookRestaurantSchema,
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export async function callTools(
|
export async function callTools(
|
||||||
@@ -49,7 +37,9 @@ export async function callTools(
|
|||||||
|
|
||||||
const ui = typedUi<typeof ComponentMap>(config);
|
const ui = typedUi<typeof ComponentMap>(config);
|
||||||
|
|
||||||
const llm = new ChatOpenAI({ model: "gpt-4o", temperature: 0 }).bindTools(ACCOMMODATIONS_TOOLS);
|
const llm = new ChatOpenAI({ model: "gpt-4o", temperature: 0 }).bindTools(
|
||||||
|
ACCOMMODATIONS_TOOLS,
|
||||||
|
);
|
||||||
|
|
||||||
const response = await llm.invoke([
|
const response = await llm.invoke([
|
||||||
{
|
{
|
||||||
@@ -63,47 +53,40 @@ export async function callTools(
|
|||||||
const listAccommodationsToolCall = response.tool_calls?.find(
|
const listAccommodationsToolCall = response.tool_calls?.find(
|
||||||
findToolCall("list-accommodations")<typeof listAccommodationsSchema>,
|
findToolCall("list-accommodations")<typeof listAccommodationsSchema>,
|
||||||
);
|
);
|
||||||
const bookAccommodationToolCall = response.tool_calls?.find(
|
|
||||||
findToolCall("book-accommodation")<typeof bookAccommodationSchema>,
|
|
||||||
);
|
|
||||||
const listRestaurantsToolCall = response.tool_calls?.find(
|
const listRestaurantsToolCall = response.tool_calls?.find(
|
||||||
findToolCall("list-restaurants")<typeof listRestaurantsSchema>,
|
findToolCall("list-restaurants")<typeof listRestaurantsSchema>,
|
||||||
);
|
);
|
||||||
const bookRestaurantToolCall = response.tool_calls?.find(
|
|
||||||
findToolCall("book-restaurant")<typeof bookRestaurantSchema>,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!listAccommodationsToolCall && !bookAccommodationToolCall && !listRestaurantsToolCall && !bookRestaurantToolCall) {
|
if (!listAccommodationsToolCall && !listRestaurantsToolCall) {
|
||||||
throw new Error("No tool calls found");
|
throw new Error("No tool calls found");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (listAccommodationsToolCall) {
|
if (listAccommodationsToolCall) {
|
||||||
ui.write("accommodations-list", {
|
ui.push(
|
||||||
toolCallId: listAccommodationsToolCall.id ?? "",
|
{
|
||||||
...getAccommodationsListProps(state.tripDetails),
|
name: "accommodations-list",
|
||||||
});
|
content: {
|
||||||
}
|
toolCallId: listAccommodationsToolCall.id ?? "",
|
||||||
if (bookAccommodationToolCall && bookAccommodationToolCall.args.accommodationName) {
|
...getAccommodationsListProps(state.tripDetails),
|
||||||
ui.write("book-accommodation", {
|
},
|
||||||
tripDetails: state.tripDetails,
|
},
|
||||||
accommodationName: bookAccommodationToolCall.args.accommodationName,
|
{ message: response },
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (listRestaurantsToolCall) {
|
if (listRestaurantsToolCall) {
|
||||||
ui.write("restaurants-list", { tripDetails: state.tripDetails });
|
ui.push(
|
||||||
}
|
{
|
||||||
|
name: "restaurants-list",
|
||||||
if (bookRestaurantToolCall && bookRestaurantToolCall.args.restaurantName) {
|
content: { tripDetails: state.tripDetails },
|
||||||
ui.write("book-restaurant", {
|
},
|
||||||
tripDetails: state.tripDetails,
|
{ message: response },
|
||||||
restaurantName: bookRestaurantToolCall.args.restaurantName,
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
messages: [response],
|
messages: [response],
|
||||||
ui: ui.collect as TripPlannerUpdate["ui"],
|
ui: ui.items,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ export const GenerativeUIAnnotation = Annotation.Root({
|
|||||||
UIMessage | RemoveUIMessage | (UIMessage | RemoveUIMessage)[]
|
UIMessage | RemoveUIMessage | (UIMessage | RemoveUIMessage)[]
|
||||||
>({ default: () => [], reducer: uiMessageReducer }),
|
>({ default: () => [], reducer: uiMessageReducer }),
|
||||||
timestamp: Annotation<number>,
|
timestamp: Annotation<number>,
|
||||||
next: Annotation<"stockbroker" | "tripPlanner" | "generalInput">(),
|
next: Annotation<
|
||||||
|
"stockbroker" | "tripPlanner" | "openCode" | "orderPizza" | "generalInput"
|
||||||
|
>(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type GenerativeUIState = typeof GenerativeUIAnnotation.State;
|
export type GenerativeUIState = typeof GenerativeUIAnnotation.State;
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import BookAccommodation from "./trip-planner/book-accommodation";
|
|||||||
import RestaurantsList from "./trip-planner/restaurants-list";
|
import RestaurantsList from "./trip-planner/restaurants-list";
|
||||||
import BookRestaurant from "./trip-planner/book-restaurant";
|
import BookRestaurant from "./trip-planner/book-restaurant";
|
||||||
import BuyStock from "./stockbroker/buy-stock";
|
import BuyStock from "./stockbroker/buy-stock";
|
||||||
|
import Plan from "./open-code/plan";
|
||||||
|
import ProposedChange from "./open-code/proposed-change";
|
||||||
|
|
||||||
const ComponentMap = {
|
const ComponentMap = {
|
||||||
"stock-price": StockPrice,
|
"stock-price": StockPrice,
|
||||||
@@ -14,5 +16,7 @@ const ComponentMap = {
|
|||||||
"restaurants-list": RestaurantsList,
|
"restaurants-list": RestaurantsList,
|
||||||
"book-restaurant": BookRestaurant,
|
"book-restaurant": BookRestaurant,
|
||||||
"buy-stock": BuyStock,
|
"buy-stock": BuyStock,
|
||||||
|
"code-plan": Plan,
|
||||||
|
"proposed-change": ProposedChange,
|
||||||
} as const;
|
} as const;
|
||||||
export default ComponentMap;
|
export default ComponentMap;
|
||||||
|
|||||||
76
agent/uis/open-code/plan/index.tsx
Normal file
76
agent/uis/open-code/plan/index.tsx
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import "./index.css";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { ChevronDown } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
interface PlanProps {
|
||||||
|
toolCallId: string;
|
||||||
|
executedPlans: string[];
|
||||||
|
rejectedPlans: string[];
|
||||||
|
remainingPlans: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Plan(props: PlanProps) {
|
||||||
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col w-full max-w-4xl border-[1px] rounded-xl border-slate-200 overflow-hidden">
|
||||||
|
<div className="p-6">
|
||||||
|
<h2 className="text-2xl font-semibold text-left">Code Plan</h2>
|
||||||
|
</div>
|
||||||
|
<motion.div
|
||||||
|
className="relative overflow-hidden"
|
||||||
|
animate={{
|
||||||
|
height: isExpanded ? "auto" : "200px",
|
||||||
|
opacity: isExpanded ? 1 : 0.7,
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
height: { duration: 0.3, ease: [0.4, 0, 0.2, 1] },
|
||||||
|
opacity: { duration: 0.2 },
|
||||||
|
}}
|
||||||
|
initial={false}
|
||||||
|
>
|
||||||
|
<div className="grid grid-cols-3 divide-x divide-slate-300 w-full border-t border-slate-200 px-6 pt-4 pb-4">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<h3 className="text-lg font-medium mb-4 text-slate-700">
|
||||||
|
Remaining Plans
|
||||||
|
</h3>
|
||||||
|
{props.remainingPlans.map((step, index) => (
|
||||||
|
<p key={index} className="font-mono text-sm">
|
||||||
|
{index + 1}. {step}
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2 px-6">
|
||||||
|
<h3 className="text-lg font-medium mb-4 text-slate-700">
|
||||||
|
Executed Plans
|
||||||
|
</h3>
|
||||||
|
{props.executedPlans.map((step, index) => (
|
||||||
|
<p key={index} className="font-mono text-sm">
|
||||||
|
{step}
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2 px-6">
|
||||||
|
<h3 className="text-lg font-medium mb-4 text-slate-700">
|
||||||
|
Rejected Plans
|
||||||
|
</h3>
|
||||||
|
{props.rejectedPlans.map((step, index) => (
|
||||||
|
<p key={index} className="font-mono text-sm">
|
||||||
|
{step}
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
<motion.button
|
||||||
|
className="w-full py-2 border-t border-slate-200 flex items-center justify-center hover:bg-slate-50 transition-colors"
|
||||||
|
onClick={() => setIsExpanded(!isExpanded)}
|
||||||
|
animate={{ rotate: isExpanded ? 180 : 0 }}
|
||||||
|
transition={{ duration: 0.3, ease: [0.4, 0, 0.2, 1] }}
|
||||||
|
>
|
||||||
|
<ChevronDown className="w-5 h-5 text-slate-600" />
|
||||||
|
</motion.button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
122
agent/uis/open-code/proposed-change/index.css
Normal file
122
agent/uis/open-code/proposed-change/index.css
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
@custom-variant dark (&:is(.dark *));
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--background: oklch(1 0 0);
|
||||||
|
--foreground: oklch(0.145 0 0);
|
||||||
|
--card: oklch(1 0 0);
|
||||||
|
--card-foreground: oklch(0.145 0 0);
|
||||||
|
--popover: oklch(1 0 0);
|
||||||
|
--popover-foreground: oklch(0.145 0 0);
|
||||||
|
--primary: oklch(0.205 0 0);
|
||||||
|
--primary-foreground: oklch(0.985 0 0);
|
||||||
|
--secondary: oklch(0.97 0 0);
|
||||||
|
--secondary-foreground: oklch(0.205 0 0);
|
||||||
|
--muted: oklch(0.97 0 0);
|
||||||
|
--muted-foreground: oklch(0.556 0 0);
|
||||||
|
--accent: oklch(0.97 0 0);
|
||||||
|
--accent-foreground: oklch(0.205 0 0);
|
||||||
|
--destructive: oklch(0.577 0.245 27.325);
|
||||||
|
--destructive-foreground: oklch(0.577 0.245 27.325);
|
||||||
|
--border: oklch(0.922 0 0);
|
||||||
|
--input: oklch(0.922 0 0);
|
||||||
|
--ring: oklch(0.708 0 0);
|
||||||
|
--chart-1: oklch(0.646 0.222 41.116);
|
||||||
|
--chart-2: oklch(0.6 0.118 184.704);
|
||||||
|
--chart-3: oklch(0.398 0.07 227.392);
|
||||||
|
--chart-4: oklch(0.828 0.189 84.429);
|
||||||
|
--chart-5: oklch(0.769 0.188 70.08);
|
||||||
|
--radius: 0.625rem;
|
||||||
|
--sidebar: oklch(0.985 0 0);
|
||||||
|
--sidebar-foreground: oklch(0.145 0 0);
|
||||||
|
--sidebar-primary: oklch(0.205 0 0);
|
||||||
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-accent: oklch(0.97 0 0);
|
||||||
|
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||||
|
--sidebar-border: oklch(0.922 0 0);
|
||||||
|
--sidebar-ring: oklch(0.708 0 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--background: oklch(0.145 0 0);
|
||||||
|
--foreground: oklch(0.985 0 0);
|
||||||
|
--card: oklch(0.145 0 0);
|
||||||
|
--card-foreground: oklch(0.985 0 0);
|
||||||
|
--popover: oklch(0.145 0 0);
|
||||||
|
--popover-foreground: oklch(0.985 0 0);
|
||||||
|
--primary: oklch(0.985 0 0);
|
||||||
|
--primary-foreground: oklch(0.205 0 0);
|
||||||
|
--secondary: oklch(0.269 0 0);
|
||||||
|
--secondary-foreground: oklch(0.985 0 0);
|
||||||
|
--muted: oklch(0.269 0 0);
|
||||||
|
--muted-foreground: oklch(0.708 0 0);
|
||||||
|
--accent: oklch(0.269 0 0);
|
||||||
|
--accent-foreground: oklch(0.985 0 0);
|
||||||
|
--destructive: oklch(0.396 0.141 25.723);
|
||||||
|
--destructive-foreground: oklch(0.637 0.237 25.331);
|
||||||
|
--border: oklch(0.269 0 0);
|
||||||
|
--input: oklch(0.269 0 0);
|
||||||
|
--ring: oklch(0.439 0 0);
|
||||||
|
--chart-1: oklch(0.488 0.243 264.376);
|
||||||
|
--chart-2: oklch(0.696 0.17 162.48);
|
||||||
|
--chart-3: oklch(0.769 0.188 70.08);
|
||||||
|
--chart-4: oklch(0.627 0.265 303.9);
|
||||||
|
--chart-5: oklch(0.645 0.246 16.439);
|
||||||
|
--sidebar: oklch(0.205 0 0);
|
||||||
|
--sidebar-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||||
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-accent: oklch(0.269 0 0);
|
||||||
|
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-border: oklch(0.269 0 0);
|
||||||
|
--sidebar-ring: oklch(0.439 0 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@theme inline {
|
||||||
|
--color-background: var(--background);
|
||||||
|
--color-foreground: var(--foreground);
|
||||||
|
--color-card: var(--card);
|
||||||
|
--color-card-foreground: var(--card-foreground);
|
||||||
|
--color-popover: var(--popover);
|
||||||
|
--color-popover-foreground: var(--popover-foreground);
|
||||||
|
--color-primary: var(--primary);
|
||||||
|
--color-primary-foreground: var(--primary-foreground);
|
||||||
|
--color-secondary: var(--secondary);
|
||||||
|
--color-secondary-foreground: var(--secondary-foreground);
|
||||||
|
--color-muted: var(--muted);
|
||||||
|
--color-muted-foreground: var(--muted-foreground);
|
||||||
|
--color-accent: var(--accent);
|
||||||
|
--color-accent-foreground: var(--accent-foreground);
|
||||||
|
--color-destructive: var(--destructive);
|
||||||
|
--color-destructive-foreground: var(--destructive-foreground);
|
||||||
|
--color-border: var(--border);
|
||||||
|
--color-input: var(--input);
|
||||||
|
--color-ring: var(--ring);
|
||||||
|
--color-chart-1: var(--chart-1);
|
||||||
|
--color-chart-2: var(--chart-2);
|
||||||
|
--color-chart-3: var(--chart-3);
|
||||||
|
--color-chart-4: var(--chart-4);
|
||||||
|
--color-chart-5: var(--chart-5);
|
||||||
|
--radius-sm: calc(var(--radius) - 4px);
|
||||||
|
--radius-md: calc(var(--radius) - 2px);
|
||||||
|
--radius-lg: var(--radius);
|
||||||
|
--radius-xl: calc(var(--radius) + 4px);
|
||||||
|
--color-sidebar: var(--sidebar);
|
||||||
|
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||||
|
--color-sidebar-primary: var(--sidebar-primary);
|
||||||
|
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||||
|
--color-sidebar-accent: var(--sidebar-accent);
|
||||||
|
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||||
|
--color-sidebar-border: var(--sidebar-border);
|
||||||
|
--color-sidebar-ring: var(--sidebar-ring);
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
@apply border-border outline-ring/50;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply bg-background text-foreground;
|
||||||
|
}
|
||||||
|
}
|
||||||
189
agent/uis/open-code/proposed-change/index.tsx
Normal file
189
agent/uis/open-code/proposed-change/index.tsx
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
import "./index.css";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import ReactMarkdown from "react-markdown";
|
||||||
|
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||||
|
import { coldarkDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
|
||||||
|
import { UIMessage, useStreamContext } from "@langchain/langgraph-sdk/react-ui";
|
||||||
|
import { Message } from "@langchain/langgraph-sdk";
|
||||||
|
import { DO_NOT_RENDER_ID_PREFIX } from "@/lib/ensure-tool-responses";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { getToolResponse } from "../../utils/get-tool-response";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
interface ProposedChangeProps {
|
||||||
|
toolCallId: string;
|
||||||
|
change: string;
|
||||||
|
planItem: string;
|
||||||
|
/**
|
||||||
|
* Whether or not to show the "Accept"/"Reject" buttons
|
||||||
|
* If true, this means the user selected the "Accept, don't ask again"
|
||||||
|
* button for this session.
|
||||||
|
*/
|
||||||
|
fullWriteAccess: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ACCEPTED_CHANGE_CONTENT =
|
||||||
|
"User accepted the proposed change. Please continue.";
|
||||||
|
const REJECTED_CHANGE_CONTENT =
|
||||||
|
"User rejected the proposed change. Please continue.";
|
||||||
|
|
||||||
|
export default function ProposedChange(props: ProposedChangeProps) {
|
||||||
|
const [isAccepted, setIsAccepted] = useState(false);
|
||||||
|
const [isRejected, setIsRejected] = useState(false);
|
||||||
|
|
||||||
|
const thread = useStreamContext<
|
||||||
|
{ messages: Message[]; ui: UIMessage[] },
|
||||||
|
{ MetaType: { ui: UIMessage | undefined } }
|
||||||
|
>();
|
||||||
|
|
||||||
|
const handleReject = () => {
|
||||||
|
thread.submit({
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
type: "tool",
|
||||||
|
tool_call_id: props.toolCallId,
|
||||||
|
id: `${DO_NOT_RENDER_ID_PREFIX}${uuidv4()}`,
|
||||||
|
name: "update_file",
|
||||||
|
content: REJECTED_CHANGE_CONTENT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "human",
|
||||||
|
content: `Rejected change.`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
setIsRejected(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAccept = (shouldGrantFullWriteAccess = false) => {
|
||||||
|
const humanMessageContent = `Accepted change. ${shouldGrantFullWriteAccess ? "Granted full write access." : ""}`;
|
||||||
|
thread.submit(
|
||||||
|
{
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
type: "tool",
|
||||||
|
tool_call_id: props.toolCallId,
|
||||||
|
id: `${DO_NOT_RENDER_ID_PREFIX}${uuidv4()}`,
|
||||||
|
name: "update_file",
|
||||||
|
content: ACCEPTED_CHANGE_CONTENT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "human",
|
||||||
|
content: humanMessageContent,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: {
|
||||||
|
configurable: {
|
||||||
|
permissions: {
|
||||||
|
full_write_access: shouldGrantFullWriteAccess,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
setIsAccepted(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof window === "undefined" || isAccepted) return;
|
||||||
|
const toolResponse = getToolResponse(props.toolCallId, thread);
|
||||||
|
if (toolResponse) {
|
||||||
|
if (toolResponse.content === ACCEPTED_CHANGE_CONTENT) {
|
||||||
|
setIsAccepted(true);
|
||||||
|
} else if (toolResponse.content === REJECTED_CHANGE_CONTENT) {
|
||||||
|
setIsRejected(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (isAccepted || isRejected) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col gap-4 w-full max-w-4xl p-4 border-[1px] rounded-xl",
|
||||||
|
isAccepted ? "border-green-300" : "border-red-300",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col items-start justify-start gap-2">
|
||||||
|
<p className="text-lg font-medium">
|
||||||
|
{isAccepted ? "Accepted" : "Rejected"} Change
|
||||||
|
</p>
|
||||||
|
<p className="text-sm font-mono">{props.planItem}</p>
|
||||||
|
</div>
|
||||||
|
<ReactMarkdown
|
||||||
|
children={props.change}
|
||||||
|
components={{
|
||||||
|
code(props) {
|
||||||
|
const { children, className, node: _node } = props;
|
||||||
|
const match = /language-(\w+)/.exec(className || "");
|
||||||
|
return match ? (
|
||||||
|
<SyntaxHighlighter
|
||||||
|
children={String(children).replace(/\n$/, "")}
|
||||||
|
language={match[1]}
|
||||||
|
style={coldarkDark}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<code className={className}>{children}</code>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-4 w-full max-w-4xl p-4 border-[1px] rounded-xl border-slate-200">
|
||||||
|
<div className="flex flex-col items-start justify-start gap-2">
|
||||||
|
<p className="text-lg font-medium">Proposed Change</p>
|
||||||
|
<p className="text-sm font-mono">{props.planItem}</p>
|
||||||
|
</div>
|
||||||
|
<ReactMarkdown
|
||||||
|
children={props.change}
|
||||||
|
components={{
|
||||||
|
code(props) {
|
||||||
|
const { children, className, node: _node } = props;
|
||||||
|
const match = /language-(\w+)/.exec(className || "");
|
||||||
|
return match ? (
|
||||||
|
<SyntaxHighlighter
|
||||||
|
children={String(children).replace(/\n$/, "")}
|
||||||
|
language={match[1]}
|
||||||
|
style={coldarkDark}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<code className={className}>{children}</code>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{!props.fullWriteAccess && (
|
||||||
|
<div className="flex gap-2 items-center w-full">
|
||||||
|
<Button
|
||||||
|
className="cursor-pointer w-full"
|
||||||
|
variant="destructive"
|
||||||
|
onClick={handleReject}
|
||||||
|
>
|
||||||
|
Reject
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="cursor-pointer w-full"
|
||||||
|
onClick={() => handleAccept()}
|
||||||
|
>
|
||||||
|
Accept
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="cursor-pointer w-full bg-blue-500 hover:bg-blue-500/90"
|
||||||
|
onClick={() => handleAccept(true)}
|
||||||
|
>
|
||||||
|
Accept, don't ask again
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -160,7 +160,7 @@ export default function PortfolioView() {
|
|||||||
const chartData = selectedHolding ? generateChartData(selectedHolding) : [];
|
const chartData = selectedHolding ? generateChartData(selectedHolding) : [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-4xl bg-white rounded-xl shadow-lg overflow-hidden border border-gray-200">
|
<div className="w-full max-w-3xl bg-white rounded-xl shadow-lg overflow-hidden border border-gray-200">
|
||||||
<div className="bg-gradient-to-r from-indigo-700 to-indigo-500 px-6 py-4">
|
<div className="bg-gradient-to-r from-indigo-700 to-indigo-500 px-6 py-4">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<h2 className="text-white font-bold text-xl tracking-tight flex items-center">
|
<h2 className="text-white font-bold text-xl tracking-tight flex items-center">
|
||||||
|
|||||||
@@ -138,8 +138,18 @@ export default function StockPrice(props: {
|
|||||||
};
|
};
|
||||||
}, [oneDayPrices, thirtyDayPrices, displayRange]);
|
}, [oneDayPrices, thirtyDayPrices, displayRange]);
|
||||||
|
|
||||||
|
const formatDateByDisplayRange = (value: string, isTooltip?: boolean) => {
|
||||||
|
if (displayRange === "1d") {
|
||||||
|
return format(value, "h:mm a");
|
||||||
|
}
|
||||||
|
if (isTooltip) {
|
||||||
|
return format(value, "LLL do h:mm a");
|
||||||
|
}
|
||||||
|
return format(value, "LLL do");
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-4xl rounded-xl shadow-md overflow-hidden border border-gray-200 flex flex-col gap-4 p-3">
|
<div className="w-full max-w-3xl rounded-xl shadow-md overflow-hidden border border-gray-200 flex flex-col gap-4 p-3">
|
||||||
<div className="flex items-center justify-start gap-4 mb-2 text-lg font-medium text-gray-700">
|
<div className="flex items-center justify-start gap-4 mb-2 text-lg font-medium text-gray-700">
|
||||||
<p>{ticker}</p>
|
<p>{ticker}</p>
|
||||||
<p>${currentPrice}</p>
|
<p>${currentPrice}</p>
|
||||||
@@ -180,7 +190,7 @@ export default function StockPrice(props: {
|
|||||||
tickLine={false}
|
tickLine={false}
|
||||||
axisLine={false}
|
axisLine={false}
|
||||||
tickMargin={8}
|
tickMargin={8}
|
||||||
tickFormatter={(value) => format(value, "h:mm a")}
|
tickFormatter={(v) => formatDateByDisplayRange(v)}
|
||||||
/>
|
/>
|
||||||
<YAxis
|
<YAxis
|
||||||
domain={[lowPrice - 2, highPrice + 2]}
|
domain={[lowPrice - 2, highPrice + 2]}
|
||||||
@@ -191,10 +201,11 @@ export default function StockPrice(props: {
|
|||||||
/>
|
/>
|
||||||
<ChartTooltip
|
<ChartTooltip
|
||||||
cursor={false}
|
cursor={false}
|
||||||
|
wrapperStyle={{ backgroundColor: "white" }}
|
||||||
content={
|
content={
|
||||||
<ChartTooltipContent
|
<ChartTooltipContent
|
||||||
hideLabel={false}
|
hideLabel={false}
|
||||||
labelFormatter={(value) => format(value, "h:mm a")}
|
labelFormatter={(v) => formatDateByDisplayRange(v, true)}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -320,7 +320,7 @@ export default function AccommodationsList({
|
|||||||
align: "start",
|
align: "start",
|
||||||
loop: true,
|
loop: true,
|
||||||
}}
|
}}
|
||||||
className="w-full sm:max-w-sm md:max-w-2xl lg:max-w-3xl"
|
className="w-full sm:max-w-sm md:max-w-3xl lg:max-w-3xl"
|
||||||
>
|
>
|
||||||
<CarouselContent>
|
<CarouselContent>
|
||||||
{accommodations.map((accommodation) => (
|
{accommodations.map((accommodation) => (
|
||||||
|
|||||||
@@ -1,403 +0,0 @@
|
|||||||
import "./index.css";
|
|
||||||
import { TripDetails } from "../../../trip-planner/types";
|
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
export default function BookAccommodation({
|
|
||||||
tripDetails,
|
|
||||||
accommodationName,
|
|
||||||
}: {
|
|
||||||
tripDetails: TripDetails;
|
|
||||||
accommodationName: string;
|
|
||||||
}) {
|
|
||||||
// Placeholder data - ideally would come from props
|
|
||||||
const [accommodation] = useState({
|
|
||||||
name: accommodationName,
|
|
||||||
type: "Hotel",
|
|
||||||
price: "$150/night",
|
|
||||||
rating: 4.8,
|
|
||||||
totalPrice:
|
|
||||||
"$" +
|
|
||||||
150 *
|
|
||||||
Math.ceil(
|
|
||||||
(new Date(tripDetails.endDate).getTime() -
|
|
||||||
new Date(tripDetails.startDate).getTime()) /
|
|
||||||
(1000 * 60 * 60 * 24),
|
|
||||||
),
|
|
||||||
image: "https://placehold.co/300x200?text=Accommodation",
|
|
||||||
roomTypes: ["Standard", "Deluxe", "Suite"],
|
|
||||||
checkInTime: "3:00 PM",
|
|
||||||
checkOutTime: "11:00 AM",
|
|
||||||
});
|
|
||||||
|
|
||||||
const [selectedRoom, setSelectedRoom] = useState("Standard");
|
|
||||||
const [bookingStep, setBookingStep] = useState<
|
|
||||||
"details" | "payment" | "confirmed"
|
|
||||||
>("details");
|
|
||||||
const [formData, setFormData] = useState({
|
|
||||||
name: "",
|
|
||||||
email: "",
|
|
||||||
phone: "",
|
|
||||||
specialRequests: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleInputChange = (
|
|
||||||
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
|
|
||||||
) => {
|
|
||||||
setFormData({ ...formData, [e.target.name]: e.target.value });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setBookingStep("payment");
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePayment = (e: React.FormEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setBookingStep("confirmed");
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="w-full max-w-md bg-white rounded-lg shadow-md overflow-hidden">
|
|
||||||
<div className="bg-blue-600 px-4 py-3">
|
|
||||||
<h3 className="text-white font-medium">Book {accommodation.name}</h3>
|
|
||||||
<p className="text-blue-100 text-xs">
|
|
||||||
{new Date(tripDetails.startDate).toLocaleDateString()} -{" "}
|
|
||||||
{new Date(tripDetails.endDate).toLocaleDateString()} ·{" "}
|
|
||||||
{tripDetails.numberOfGuests} guests
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="p-4">
|
|
||||||
{bookingStep === "details" && (
|
|
||||||
<>
|
|
||||||
<div className="flex items-center space-x-3 mb-4">
|
|
||||||
<div className="flex-shrink-0 w-16 h-16 bg-gray-200 rounded-md overflow-hidden">
|
|
||||||
<img
|
|
||||||
src={accommodation.image}
|
|
||||||
alt={accommodation.name}
|
|
||||||
className="w-full h-full object-cover"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h4 className="font-medium text-gray-900">
|
|
||||||
{accommodation.name}
|
|
||||||
</h4>
|
|
||||||
<div className="flex items-center mt-1">
|
|
||||||
<svg
|
|
||||||
className="w-4 h-4 text-yellow-400"
|
|
||||||
fill="currentColor"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
>
|
|
||||||
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"></path>
|
|
||||||
</svg>
|
|
||||||
<span className="text-xs text-gray-500 ml-1">
|
|
||||||
{accommodation.rating}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-between mt-1">
|
|
||||||
<span className="text-sm text-gray-500">
|
|
||||||
{accommodation.type}
|
|
||||||
</span>
|
|
||||||
<span className="text-sm font-semibold text-blue-600">
|
|
||||||
{accommodation.price}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="border-t border-b py-3 mb-4">
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-gray-600">Check-in</span>
|
|
||||||
<span className="font-medium">
|
|
||||||
{new Date(tripDetails.startDate).toLocaleDateString()} (
|
|
||||||
{accommodation.checkInTime})
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between text-sm mt-2">
|
|
||||||
<span className="text-gray-600">Check-out</span>
|
|
||||||
<span className="font-medium">
|
|
||||||
{new Date(tripDetails.endDate).toLocaleDateString()} (
|
|
||||||
{accommodation.checkOutTime})
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between text-sm mt-2">
|
|
||||||
<span className="text-gray-600">Guests</span>
|
|
||||||
<span className="font-medium">
|
|
||||||
{tripDetails.numberOfGuests}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mb-4">
|
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
||||||
Room Type
|
|
||||||
</label>
|
|
||||||
<div className="grid grid-cols-3 gap-2">
|
|
||||||
{accommodation.roomTypes.map((room) => (
|
|
||||||
<button
|
|
||||||
key={room}
|
|
||||||
type="button"
|
|
||||||
onClick={() => setSelectedRoom(room)}
|
|
||||||
className={`text-sm py-2 px-3 rounded-md border transition-colors ${
|
|
||||||
selectedRoom === room
|
|
||||||
? "border-blue-500 bg-blue-50 text-blue-700"
|
|
||||||
: "border-gray-300 text-gray-700 hover:border-gray-400"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{room}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="space-y-3">
|
|
||||||
<div>
|
|
||||||
<label
|
|
||||||
htmlFor="name"
|
|
||||||
className="block text-sm font-medium text-gray-700 mb-1"
|
|
||||||
>
|
|
||||||
Full Name
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="name"
|
|
||||||
name="name"
|
|
||||||
value={formData.name}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
required
|
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 text-sm"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label
|
|
||||||
htmlFor="email"
|
|
||||||
className="block text-sm font-medium text-gray-700 mb-1"
|
|
||||||
>
|
|
||||||
Email
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="email"
|
|
||||||
id="email"
|
|
||||||
name="email"
|
|
||||||
value={formData.email}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
required
|
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 text-sm"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label
|
|
||||||
htmlFor="phone"
|
|
||||||
className="block text-sm font-medium text-gray-700 mb-1"
|
|
||||||
>
|
|
||||||
Phone
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="tel"
|
|
||||||
id="phone"
|
|
||||||
name="phone"
|
|
||||||
value={formData.phone}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
required
|
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 text-sm"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label
|
|
||||||
htmlFor="specialRequests"
|
|
||||||
className="block text-sm font-medium text-gray-700 mb-1"
|
|
||||||
>
|
|
||||||
Special Requests
|
|
||||||
</label>
|
|
||||||
<textarea
|
|
||||||
id="specialRequests"
|
|
||||||
name="specialRequests"
|
|
||||||
value={formData.specialRequests}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
rows={2}
|
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 text-sm"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="border-t pt-3 mt-4">
|
|
||||||
<div className="flex justify-between items-center mb-3">
|
|
||||||
<span className="text-gray-600 text-sm">Total Price:</span>
|
|
||||||
<span className="font-semibold text-lg">
|
|
||||||
{accommodation.totalPrice}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
className="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-md transition-colors"
|
|
||||||
>
|
|
||||||
Continue to Payment
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{bookingStep === "payment" && (
|
|
||||||
<form onSubmit={handlePayment} className="space-y-3">
|
|
||||||
<h4 className="font-medium text-lg text-gray-900 mb-3">
|
|
||||||
Payment Details
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label
|
|
||||||
htmlFor="cardName"
|
|
||||||
className="block text-sm font-medium text-gray-700 mb-1"
|
|
||||||
>
|
|
||||||
Name on Card
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="cardName"
|
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 text-sm"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label
|
|
||||||
htmlFor="cardNumber"
|
|
||||||
className="block text-sm font-medium text-gray-700 mb-1"
|
|
||||||
>
|
|
||||||
Card Number
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="cardNumber"
|
|
||||||
placeholder="XXXX XXXX XXXX XXXX"
|
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 text-sm"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-3">
|
|
||||||
<div>
|
|
||||||
<label
|
|
||||||
htmlFor="expiry"
|
|
||||||
className="block text-sm font-medium text-gray-700 mb-1"
|
|
||||||
>
|
|
||||||
Expiry Date
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="expiry"
|
|
||||||
placeholder="MM/YY"
|
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 text-sm"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label
|
|
||||||
htmlFor="cvc"
|
|
||||||
className="block text-sm font-medium text-gray-700 mb-1"
|
|
||||||
>
|
|
||||||
CVC
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="cvc"
|
|
||||||
placeholder="XXX"
|
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 text-sm"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="border-t pt-3 mt-4">
|
|
||||||
<div className="flex justify-between items-center mb-3">
|
|
||||||
<span className="text-gray-600 text-sm">Total Amount:</span>
|
|
||||||
<span className="font-semibold text-lg">
|
|
||||||
{accommodation.totalPrice}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
className="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-md transition-colors"
|
|
||||||
>
|
|
||||||
Complete Booking
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setBookingStep("details")}
|
|
||||||
className="w-full mt-2 bg-white border border-gray-300 text-gray-700 font-medium py-2 px-4 rounded-md hover:bg-gray-50 transition-colors"
|
|
||||||
>
|
|
||||||
Back
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{bookingStep === "confirmed" && (
|
|
||||||
<div className="text-center py-6">
|
|
||||||
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100 mb-3">
|
|
||||||
<svg
|
|
||||||
className="h-6 w-6 text-green-600"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth={2}
|
|
||||||
d="M5 13l4 4L19 7"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<h3 className="text-lg font-medium text-gray-900">
|
|
||||||
Booking Confirmed!
|
|
||||||
</h3>
|
|
||||||
<div className="mt-2">
|
|
||||||
<p className="text-sm text-gray-500">
|
|
||||||
Your booking at {accommodation.name} has been confirmed. You'll
|
|
||||||
receive a confirmation email shortly at {formData.email}.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="mt-4 p-3 bg-gray-50 rounded-lg text-left">
|
|
||||||
<h4 className="font-medium text-sm text-gray-700">
|
|
||||||
Booking Summary
|
|
||||||
</h4>
|
|
||||||
<ul className="mt-2 space-y-1 text-xs text-gray-600">
|
|
||||||
<li className="flex justify-between">
|
|
||||||
<span>Check-in:</span>
|
|
||||||
<span className="font-medium">
|
|
||||||
{new Date(tripDetails.startDate).toLocaleDateString()}
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li className="flex justify-between">
|
|
||||||
<span>Check-out:</span>
|
|
||||||
<span className="font-medium">
|
|
||||||
{new Date(tripDetails.endDate).toLocaleDateString()}
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li className="flex justify-between">
|
|
||||||
<span>Room type:</span>
|
|
||||||
<span className="font-medium">{selectedRoom}</span>
|
|
||||||
</li>
|
|
||||||
<li className="flex justify-between">
|
|
||||||
<span>Guests:</span>
|
|
||||||
<span className="font-medium">
|
|
||||||
{tripDetails.numberOfGuests}
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li className="flex justify-between pt-1 mt-1 border-t">
|
|
||||||
<span>Total paid:</span>
|
|
||||||
<span className="font-medium">
|
|
||||||
{accommodation.totalPrice}
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
@import "tailwindcss";
|
|
||||||
@@ -1,350 +0,0 @@
|
|||||||
import "./index.css";
|
|
||||||
import { TripDetails } from "../../../trip-planner/types";
|
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
export default function BookRestaurant({
|
|
||||||
tripDetails,
|
|
||||||
restaurantName,
|
|
||||||
}: {
|
|
||||||
tripDetails: TripDetails;
|
|
||||||
restaurantName: string;
|
|
||||||
}) {
|
|
||||||
// Placeholder data - ideally would come from props
|
|
||||||
const [restaurant] = useState({
|
|
||||||
name: restaurantName,
|
|
||||||
cuisine: "Contemporary",
|
|
||||||
priceRange: "$$",
|
|
||||||
rating: 4.7,
|
|
||||||
image: "https://placehold.co/300x200?text=Restaurant",
|
|
||||||
openingHours: "5:00 PM - 10:00 PM",
|
|
||||||
address: "123 Main St, " + tripDetails.location,
|
|
||||||
availableTimes: ["6:00 PM", "7:00 PM", "8:00 PM", "9:00 PM"],
|
|
||||||
});
|
|
||||||
|
|
||||||
const [reservationStep, setReservationStep] = useState<
|
|
||||||
"selection" | "details" | "confirmed"
|
|
||||||
>("selection");
|
|
||||||
const [selectedDate, setSelectedDate] = useState<Date>(
|
|
||||||
new Date(tripDetails.startDate),
|
|
||||||
);
|
|
||||||
const [selectedTime, setSelectedTime] = useState<string | null>(null);
|
|
||||||
const [guests, setGuests] = useState(Math.min(tripDetails.numberOfGuests, 8));
|
|
||||||
const [formData, setFormData] = useState({
|
|
||||||
name: "",
|
|
||||||
email: "",
|
|
||||||
phone: "",
|
|
||||||
specialRequests: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleDateChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const date = new Date(e.target.value);
|
|
||||||
setSelectedDate(date);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleGuestsChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
||||||
setGuests(Number(e.target.value));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleInputChange = (
|
|
||||||
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
|
|
||||||
) => {
|
|
||||||
setFormData({ ...formData, [e.target.name]: e.target.value });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTimeSelection = (time: string) => {
|
|
||||||
setSelectedTime(time);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleContinue = () => {
|
|
||||||
if (selectedTime) {
|
|
||||||
setReservationStep("details");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setReservationStep("confirmed");
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatDate = (date: Date) => {
|
|
||||||
return date.toISOString().split("T")[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="w-full max-w-md bg-white rounded-lg shadow-md overflow-hidden">
|
|
||||||
<div className="bg-orange-600 px-4 py-3">
|
|
||||||
<h3 className="text-white font-medium">Reserve at {restaurant.name}</h3>
|
|
||||||
<p className="text-orange-100 text-xs">
|
|
||||||
{restaurant.cuisine} • {restaurant.priceRange} • {restaurant.rating}★
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="p-4">
|
|
||||||
{reservationStep === "selection" && (
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="flex items-center space-x-3 mb-4">
|
|
||||||
<div className="flex-shrink-0 w-16 h-16 bg-gray-200 rounded-md overflow-hidden">
|
|
||||||
<img
|
|
||||||
src={restaurant.image}
|
|
||||||
alt={restaurant.name}
|
|
||||||
className="w-full h-full object-cover"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h4 className="font-medium text-gray-900">{restaurant.name}</h4>
|
|
||||||
<p className="text-sm text-gray-500">{restaurant.address}</p>
|
|
||||||
<p className="text-sm text-gray-500">
|
|
||||||
{restaurant.openingHours}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label
|
|
||||||
htmlFor="date"
|
|
||||||
className="block text-sm font-medium text-gray-700 mb-1"
|
|
||||||
>
|
|
||||||
Date
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="date"
|
|
||||||
id="date"
|
|
||||||
min={formatDate(new Date(tripDetails.startDate))}
|
|
||||||
max={formatDate(new Date(tripDetails.endDate))}
|
|
||||||
value={formatDate(selectedDate)}
|
|
||||||
onChange={handleDateChange}
|
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-orange-500 focus:border-orange-500 text-sm"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label
|
|
||||||
htmlFor="guests"
|
|
||||||
className="block text-sm font-medium text-gray-700 mb-1"
|
|
||||||
>
|
|
||||||
Guests
|
|
||||||
</label>
|
|
||||||
<select
|
|
||||||
id="guests"
|
|
||||||
value={guests}
|
|
||||||
onChange={handleGuestsChange}
|
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-orange-500 focus:border-orange-500 text-sm"
|
|
||||||
>
|
|
||||||
{Array.from({ length: 8 }, (_, i) => i + 1).map((num) => (
|
|
||||||
<option key={num} value={num}>
|
|
||||||
{num} {num === 1 ? "person" : "people"}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
||||||
Available Times
|
|
||||||
</label>
|
|
||||||
<div className="grid grid-cols-3 gap-2">
|
|
||||||
{restaurant.availableTimes.map((time) => (
|
|
||||||
<button
|
|
||||||
key={time}
|
|
||||||
type="button"
|
|
||||||
onClick={() => handleTimeSelection(time)}
|
|
||||||
className={`text-sm py-2 px-3 rounded-md border transition-colors ${
|
|
||||||
selectedTime === time
|
|
||||||
? "border-orange-500 bg-orange-50 text-orange-700"
|
|
||||||
: "border-gray-300 text-gray-700 hover:border-gray-400"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{time}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={handleContinue}
|
|
||||||
disabled={!selectedTime}
|
|
||||||
className={`w-full py-2 rounded-md text-white font-medium ${
|
|
||||||
selectedTime
|
|
||||||
? "bg-orange-600 hover:bg-orange-700"
|
|
||||||
: "bg-gray-400 cursor-not-allowed"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Continue
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{reservationStep === "details" && (
|
|
||||||
<form onSubmit={handleSubmit} className="space-y-3">
|
|
||||||
<div className="border-b pb-3 mb-1">
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-gray-600">Date & Time</span>
|
|
||||||
<span className="font-medium">
|
|
||||||
{selectedDate.toLocaleDateString()} at {selectedTime}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between text-sm mt-1">
|
|
||||||
<span className="text-gray-600">Party Size</span>
|
|
||||||
<span className="font-medium">
|
|
||||||
{guests} {guests === 1 ? "person" : "people"}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setReservationStep("selection")}
|
|
||||||
className="text-orange-600 text-xs hover:underline mt-2"
|
|
||||||
>
|
|
||||||
Change
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label
|
|
||||||
htmlFor="name"
|
|
||||||
className="block text-sm font-medium text-gray-700 mb-1"
|
|
||||||
>
|
|
||||||
Full Name
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="name"
|
|
||||||
name="name"
|
|
||||||
value={formData.name}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
required
|
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-orange-500 focus:border-orange-500 text-sm"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label
|
|
||||||
htmlFor="email"
|
|
||||||
className="block text-sm font-medium text-gray-700 mb-1"
|
|
||||||
>
|
|
||||||
Email
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="email"
|
|
||||||
id="email"
|
|
||||||
name="email"
|
|
||||||
value={formData.email}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
required
|
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-orange-500 focus:border-orange-500 text-sm"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label
|
|
||||||
htmlFor="phone"
|
|
||||||
className="block text-sm font-medium text-gray-700 mb-1"
|
|
||||||
>
|
|
||||||
Phone
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="tel"
|
|
||||||
id="phone"
|
|
||||||
name="phone"
|
|
||||||
value={formData.phone}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
required
|
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-orange-500 focus:border-orange-500 text-sm"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label
|
|
||||||
htmlFor="specialRequests"
|
|
||||||
className="block text-sm font-medium text-gray-700 mb-1"
|
|
||||||
>
|
|
||||||
Special Requests
|
|
||||||
</label>
|
|
||||||
<textarea
|
|
||||||
id="specialRequests"
|
|
||||||
name="specialRequests"
|
|
||||||
value={formData.specialRequests}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
rows={2}
|
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-orange-500 focus:border-orange-500 text-sm"
|
|
||||||
placeholder="Allergies, special occasions, seating preferences..."
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="pt-2">
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
className="w-full bg-orange-600 hover:bg-orange-700 text-white font-medium py-2 px-4 rounded-md transition-colors"
|
|
||||||
>
|
|
||||||
Confirm Reservation
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{reservationStep === "confirmed" && (
|
|
||||||
<div className="text-center py-6">
|
|
||||||
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100 mb-3">
|
|
||||||
<svg
|
|
||||||
className="h-6 w-6 text-green-600"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth={2}
|
|
||||||
d="M5 13l4 4L19 7"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<h3 className="text-lg font-medium text-gray-900">
|
|
||||||
Reservation Confirmed!
|
|
||||||
</h3>
|
|
||||||
<div className="mt-2">
|
|
||||||
<p className="text-sm text-gray-500">
|
|
||||||
Your table at {restaurant.name} has been reserved. You'll
|
|
||||||
receive a confirmation email shortly at {formData.email}.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="mt-4 p-3 bg-gray-50 rounded-lg text-left">
|
|
||||||
<h4 className="font-medium text-sm text-gray-700">
|
|
||||||
Reservation Details
|
|
||||||
</h4>
|
|
||||||
<ul className="mt-2 space-y-1 text-xs text-gray-600">
|
|
||||||
<li className="flex justify-between">
|
|
||||||
<span>Restaurant:</span>
|
|
||||||
<span className="font-medium">{restaurant.name}</span>
|
|
||||||
</li>
|
|
||||||
<li className="flex justify-between">
|
|
||||||
<span>Date:</span>
|
|
||||||
<span className="font-medium">
|
|
||||||
{selectedDate.toLocaleDateString()}
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li className="flex justify-between">
|
|
||||||
<span>Time:</span>
|
|
||||||
<span className="font-medium">{selectedTime}</span>
|
|
||||||
</li>
|
|
||||||
<li className="flex justify-between">
|
|
||||||
<span>Party Size:</span>
|
|
||||||
<span className="font-medium">
|
|
||||||
{guests} {guests === 1 ? "person" : "people"}
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li className="flex justify-between">
|
|
||||||
<span>Reservation Name:</span>
|
|
||||||
<span className="font-medium">{formData.name}</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<p className="mt-3 text-xs text-gray-500">
|
|
||||||
Need to cancel or modify? Please call the restaurant directly at
|
|
||||||
(123) 456-7890.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>LangGraph Chat</title>
|
<title>Chat LangGraph</title>
|
||||||
<link href="/src/styles.css" rel="stylesheet" />
|
<link href="/src/styles.css" rel="stylesheet" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
{
|
{
|
||||||
|
"node_version": "20",
|
||||||
"graphs": {
|
"graphs": {
|
||||||
"agent": "./agent/agent.tsx:graph"
|
"agent": "./agent/agent.ts:graph"
|
||||||
},
|
},
|
||||||
"ui": {
|
"ui": {
|
||||||
"agent": "./agent/uis/index.tsx"
|
"agent": "./agent/uis/index.tsx"
|
||||||
},
|
},
|
||||||
"_INTERNAL_docker_tag": "20",
|
"env": ".env",
|
||||||
"env": ".env"
|
"dependencies": ["."]
|
||||||
}
|
}
|
||||||
|
|||||||
12
package.json
12
package.json
@@ -16,12 +16,13 @@
|
|||||||
"@assistant-ui/react-markdown": "^0.8.0",
|
"@assistant-ui/react-markdown": "^0.8.0",
|
||||||
"@assistant-ui/react-syntax-highlighter": "^0.7.2",
|
"@assistant-ui/react-syntax-highlighter": "^0.7.2",
|
||||||
"@faker-js/faker": "^9.5.1",
|
"@faker-js/faker": "^9.5.1",
|
||||||
|
"@langchain/anthropic": "^0.3.15",
|
||||||
"@langchain/core": "^0.3.41",
|
"@langchain/core": "^0.3.41",
|
||||||
"@langchain/google-genai": "^0.1.10",
|
"@langchain/google-genai": "^0.1.10",
|
||||||
"@langchain/langgraph": "^0.2.49",
|
"@langchain/langgraph": "^0.2.49",
|
||||||
"@langchain/langgraph-api": "*",
|
"@langchain/langgraph-api": "^0.0.14",
|
||||||
"@langchain/langgraph-cli": "*",
|
"@langchain/langgraph-cli": "^0.0.14",
|
||||||
"@langchain/langgraph-sdk": "*",
|
"@langchain/langgraph-sdk": "^0.0.52",
|
||||||
"@langchain/openai": "^0.4.4",
|
"@langchain/openai": "^0.4.4",
|
||||||
"@radix-ui/react-avatar": "^1.1.3",
|
"@radix-ui/react-avatar": "^1.1.3",
|
||||||
"@radix-ui/react-dialog": "^1.1.6",
|
"@radix-ui/react-dialog": "^1.1.6",
|
||||||
@@ -58,11 +59,6 @@
|
|||||||
"uuid": "^11.0.5",
|
"uuid": "^11.0.5",
|
||||||
"zod": "^3.24.2"
|
"zod": "^3.24.2"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
|
||||||
"@langchain/langgraph-api": "0.0.14-experimental.1",
|
|
||||||
"@langchain/langgraph-cli": "0.0.14-experimental.1",
|
|
||||||
"@langchain/langgraph-sdk": "0.0.47-experimental.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.19.0",
|
"@eslint/js": "^9.19.0",
|
||||||
"@types/node": "^22.13.5",
|
"@types/node": "^22.13.5",
|
||||||
|
|||||||
423
pnpm-lock.yaml
generated
423
pnpm-lock.yaml
generated
@@ -4,11 +4,6 @@ settings:
|
|||||||
autoInstallPeers: true
|
autoInstallPeers: true
|
||||||
excludeLinksFromLockfile: false
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
overrides:
|
|
||||||
"@langchain/langgraph-api": 0.0.14-experimental.1
|
|
||||||
"@langchain/langgraph-cli": 0.0.14-experimental.1
|
|
||||||
"@langchain/langgraph-sdk": 0.0.47-experimental.0
|
|
||||||
|
|
||||||
importers:
|
importers:
|
||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -24,6 +19,9 @@ importers:
|
|||||||
"@faker-js/faker":
|
"@faker-js/faker":
|
||||||
specifier: ^9.5.1
|
specifier: ^9.5.1
|
||||||
version: 9.5.1
|
version: 9.5.1
|
||||||
|
"@langchain/anthropic":
|
||||||
|
specifier: ^0.3.15
|
||||||
|
version: 0.3.15(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))
|
||||||
"@langchain/core":
|
"@langchain/core":
|
||||||
specifier: ^0.3.41
|
specifier: ^0.3.41
|
||||||
version: 0.3.41(openai@4.85.4(zod@3.24.2))
|
version: 0.3.41(openai@4.85.4(zod@3.24.2))
|
||||||
@@ -34,14 +32,14 @@ importers:
|
|||||||
specifier: ^0.2.49
|
specifier: ^0.2.49
|
||||||
version: 0.2.49(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0)
|
version: 0.2.49(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0)
|
||||||
"@langchain/langgraph-api":
|
"@langchain/langgraph-api":
|
||||||
specifier: 0.0.14-experimental.1
|
specifier: ^0.0.14
|
||||||
version: 0.0.14-experimental.1(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(@langchain/langgraph-checkpoint@0.0.15(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2))))(@langchain/langgraph@0.2.49(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0))(openai@4.85.4(zod@3.24.2))(typescript@5.7.3)
|
version: 0.0.14(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(@langchain/langgraph-checkpoint@0.0.15(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2))))(@langchain/langgraph@0.2.49(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0))(openai@4.85.4(zod@3.24.2))(typescript@5.7.3)
|
||||||
"@langchain/langgraph-cli":
|
"@langchain/langgraph-cli":
|
||||||
specifier: 0.0.14-experimental.1
|
specifier: ^0.0.14
|
||||||
version: 0.0.14-experimental.1(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(@langchain/langgraph-checkpoint@0.0.15(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2))))(@langchain/langgraph@0.2.49(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0))(openai@4.85.4(zod@3.24.2))(typescript@5.7.3)
|
version: 0.0.14(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(@langchain/langgraph-checkpoint@0.0.15(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2))))(@langchain/langgraph@0.2.49(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0))(openai@4.85.4(zod@3.24.2))(typescript@5.7.3)
|
||||||
"@langchain/langgraph-sdk":
|
"@langchain/langgraph-sdk":
|
||||||
specifier: 0.0.47-experimental.0
|
specifier: ^0.0.52
|
||||||
version: 0.0.47-experimental.0(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0)
|
version: 0.0.52(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0)
|
||||||
"@langchain/openai":
|
"@langchain/openai":
|
||||||
specifier: ^0.4.4
|
specifier: ^0.4.4
|
||||||
version: 0.4.4(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))
|
version: 0.4.4(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))
|
||||||
@@ -222,6 +220,12 @@ packages:
|
|||||||
}
|
}
|
||||||
engines: { node: ">=6.0.0" }
|
engines: { node: ">=6.0.0" }
|
||||||
|
|
||||||
|
"@anthropic-ai/sdk@0.37.0":
|
||||||
|
resolution:
|
||||||
|
{
|
||||||
|
integrity: sha512-tHjX2YbkUBwEgg0JZU3EFSSAQPoK4qQR/NFYa8Vtzd5UAyXzZksCw2In69Rml4R/TyHPBfRYaLK35XiOe33pjw==,
|
||||||
|
}
|
||||||
|
|
||||||
"@assistant-ui/react-markdown@0.8.0":
|
"@assistant-ui/react-markdown@0.8.0":
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -437,15 +441,6 @@ packages:
|
|||||||
integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==,
|
integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==,
|
||||||
}
|
}
|
||||||
|
|
||||||
"@esbuild/aix-ppc64@0.23.1":
|
|
||||||
resolution:
|
|
||||||
{
|
|
||||||
integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==,
|
|
||||||
}
|
|
||||||
engines: { node: ">=18" }
|
|
||||||
cpu: [ppc64]
|
|
||||||
os: [aix]
|
|
||||||
|
|
||||||
"@esbuild/aix-ppc64@0.25.0":
|
"@esbuild/aix-ppc64@0.25.0":
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -455,15 +450,6 @@ packages:
|
|||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [aix]
|
os: [aix]
|
||||||
|
|
||||||
"@esbuild/android-arm64@0.23.1":
|
|
||||||
resolution:
|
|
||||||
{
|
|
||||||
integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==,
|
|
||||||
}
|
|
||||||
engines: { node: ">=18" }
|
|
||||||
cpu: [arm64]
|
|
||||||
os: [android]
|
|
||||||
|
|
||||||
"@esbuild/android-arm64@0.25.0":
|
"@esbuild/android-arm64@0.25.0":
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -473,15 +459,6 @@ packages:
|
|||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [android]
|
os: [android]
|
||||||
|
|
||||||
"@esbuild/android-arm@0.23.1":
|
|
||||||
resolution:
|
|
||||||
{
|
|
||||||
integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==,
|
|
||||||
}
|
|
||||||
engines: { node: ">=18" }
|
|
||||||
cpu: [arm]
|
|
||||||
os: [android]
|
|
||||||
|
|
||||||
"@esbuild/android-arm@0.25.0":
|
"@esbuild/android-arm@0.25.0":
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -491,15 +468,6 @@ packages:
|
|||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [android]
|
os: [android]
|
||||||
|
|
||||||
"@esbuild/android-x64@0.23.1":
|
|
||||||
resolution:
|
|
||||||
{
|
|
||||||
integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==,
|
|
||||||
}
|
|
||||||
engines: { node: ">=18" }
|
|
||||||
cpu: [x64]
|
|
||||||
os: [android]
|
|
||||||
|
|
||||||
"@esbuild/android-x64@0.25.0":
|
"@esbuild/android-x64@0.25.0":
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -509,15 +477,6 @@ packages:
|
|||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [android]
|
os: [android]
|
||||||
|
|
||||||
"@esbuild/darwin-arm64@0.23.1":
|
|
||||||
resolution:
|
|
||||||
{
|
|
||||||
integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==,
|
|
||||||
}
|
|
||||||
engines: { node: ">=18" }
|
|
||||||
cpu: [arm64]
|
|
||||||
os: [darwin]
|
|
||||||
|
|
||||||
"@esbuild/darwin-arm64@0.25.0":
|
"@esbuild/darwin-arm64@0.25.0":
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -527,15 +486,6 @@ packages:
|
|||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
"@esbuild/darwin-x64@0.23.1":
|
|
||||||
resolution:
|
|
||||||
{
|
|
||||||
integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==,
|
|
||||||
}
|
|
||||||
engines: { node: ">=18" }
|
|
||||||
cpu: [x64]
|
|
||||||
os: [darwin]
|
|
||||||
|
|
||||||
"@esbuild/darwin-x64@0.25.0":
|
"@esbuild/darwin-x64@0.25.0":
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -545,15 +495,6 @@ packages:
|
|||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
"@esbuild/freebsd-arm64@0.23.1":
|
|
||||||
resolution:
|
|
||||||
{
|
|
||||||
integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==,
|
|
||||||
}
|
|
||||||
engines: { node: ">=18" }
|
|
||||||
cpu: [arm64]
|
|
||||||
os: [freebsd]
|
|
||||||
|
|
||||||
"@esbuild/freebsd-arm64@0.25.0":
|
"@esbuild/freebsd-arm64@0.25.0":
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -563,15 +504,6 @@ packages:
|
|||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [freebsd]
|
os: [freebsd]
|
||||||
|
|
||||||
"@esbuild/freebsd-x64@0.23.1":
|
|
||||||
resolution:
|
|
||||||
{
|
|
||||||
integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==,
|
|
||||||
}
|
|
||||||
engines: { node: ">=18" }
|
|
||||||
cpu: [x64]
|
|
||||||
os: [freebsd]
|
|
||||||
|
|
||||||
"@esbuild/freebsd-x64@0.25.0":
|
"@esbuild/freebsd-x64@0.25.0":
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -581,15 +513,6 @@ packages:
|
|||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [freebsd]
|
os: [freebsd]
|
||||||
|
|
||||||
"@esbuild/linux-arm64@0.23.1":
|
|
||||||
resolution:
|
|
||||||
{
|
|
||||||
integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==,
|
|
||||||
}
|
|
||||||
engines: { node: ">=18" }
|
|
||||||
cpu: [arm64]
|
|
||||||
os: [linux]
|
|
||||||
|
|
||||||
"@esbuild/linux-arm64@0.25.0":
|
"@esbuild/linux-arm64@0.25.0":
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -599,15 +522,6 @@ packages:
|
|||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
"@esbuild/linux-arm@0.23.1":
|
|
||||||
resolution:
|
|
||||||
{
|
|
||||||
integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==,
|
|
||||||
}
|
|
||||||
engines: { node: ">=18" }
|
|
||||||
cpu: [arm]
|
|
||||||
os: [linux]
|
|
||||||
|
|
||||||
"@esbuild/linux-arm@0.25.0":
|
"@esbuild/linux-arm@0.25.0":
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -617,15 +531,6 @@ packages:
|
|||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
"@esbuild/linux-ia32@0.23.1":
|
|
||||||
resolution:
|
|
||||||
{
|
|
||||||
integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==,
|
|
||||||
}
|
|
||||||
engines: { node: ">=18" }
|
|
||||||
cpu: [ia32]
|
|
||||||
os: [linux]
|
|
||||||
|
|
||||||
"@esbuild/linux-ia32@0.25.0":
|
"@esbuild/linux-ia32@0.25.0":
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -635,15 +540,6 @@ packages:
|
|||||||
cpu: [ia32]
|
cpu: [ia32]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
"@esbuild/linux-loong64@0.23.1":
|
|
||||||
resolution:
|
|
||||||
{
|
|
||||||
integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==,
|
|
||||||
}
|
|
||||||
engines: { node: ">=18" }
|
|
||||||
cpu: [loong64]
|
|
||||||
os: [linux]
|
|
||||||
|
|
||||||
"@esbuild/linux-loong64@0.25.0":
|
"@esbuild/linux-loong64@0.25.0":
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -653,15 +549,6 @@ packages:
|
|||||||
cpu: [loong64]
|
cpu: [loong64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
"@esbuild/linux-mips64el@0.23.1":
|
|
||||||
resolution:
|
|
||||||
{
|
|
||||||
integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==,
|
|
||||||
}
|
|
||||||
engines: { node: ">=18" }
|
|
||||||
cpu: [mips64el]
|
|
||||||
os: [linux]
|
|
||||||
|
|
||||||
"@esbuild/linux-mips64el@0.25.0":
|
"@esbuild/linux-mips64el@0.25.0":
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -671,15 +558,6 @@ packages:
|
|||||||
cpu: [mips64el]
|
cpu: [mips64el]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
"@esbuild/linux-ppc64@0.23.1":
|
|
||||||
resolution:
|
|
||||||
{
|
|
||||||
integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==,
|
|
||||||
}
|
|
||||||
engines: { node: ">=18" }
|
|
||||||
cpu: [ppc64]
|
|
||||||
os: [linux]
|
|
||||||
|
|
||||||
"@esbuild/linux-ppc64@0.25.0":
|
"@esbuild/linux-ppc64@0.25.0":
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -689,15 +567,6 @@ packages:
|
|||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
"@esbuild/linux-riscv64@0.23.1":
|
|
||||||
resolution:
|
|
||||||
{
|
|
||||||
integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==,
|
|
||||||
}
|
|
||||||
engines: { node: ">=18" }
|
|
||||||
cpu: [riscv64]
|
|
||||||
os: [linux]
|
|
||||||
|
|
||||||
"@esbuild/linux-riscv64@0.25.0":
|
"@esbuild/linux-riscv64@0.25.0":
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -707,15 +576,6 @@ packages:
|
|||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
"@esbuild/linux-s390x@0.23.1":
|
|
||||||
resolution:
|
|
||||||
{
|
|
||||||
integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==,
|
|
||||||
}
|
|
||||||
engines: { node: ">=18" }
|
|
||||||
cpu: [s390x]
|
|
||||||
os: [linux]
|
|
||||||
|
|
||||||
"@esbuild/linux-s390x@0.25.0":
|
"@esbuild/linux-s390x@0.25.0":
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -725,15 +585,6 @@ packages:
|
|||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
"@esbuild/linux-x64@0.23.1":
|
|
||||||
resolution:
|
|
||||||
{
|
|
||||||
integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==,
|
|
||||||
}
|
|
||||||
engines: { node: ">=18" }
|
|
||||||
cpu: [x64]
|
|
||||||
os: [linux]
|
|
||||||
|
|
||||||
"@esbuild/linux-x64@0.25.0":
|
"@esbuild/linux-x64@0.25.0":
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -752,15 +603,6 @@ packages:
|
|||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [netbsd]
|
os: [netbsd]
|
||||||
|
|
||||||
"@esbuild/netbsd-x64@0.23.1":
|
|
||||||
resolution:
|
|
||||||
{
|
|
||||||
integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==,
|
|
||||||
}
|
|
||||||
engines: { node: ">=18" }
|
|
||||||
cpu: [x64]
|
|
||||||
os: [netbsd]
|
|
||||||
|
|
||||||
"@esbuild/netbsd-x64@0.25.0":
|
"@esbuild/netbsd-x64@0.25.0":
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -770,15 +612,6 @@ packages:
|
|||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [netbsd]
|
os: [netbsd]
|
||||||
|
|
||||||
"@esbuild/openbsd-arm64@0.23.1":
|
|
||||||
resolution:
|
|
||||||
{
|
|
||||||
integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==,
|
|
||||||
}
|
|
||||||
engines: { node: ">=18" }
|
|
||||||
cpu: [arm64]
|
|
||||||
os: [openbsd]
|
|
||||||
|
|
||||||
"@esbuild/openbsd-arm64@0.25.0":
|
"@esbuild/openbsd-arm64@0.25.0":
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -788,15 +621,6 @@ packages:
|
|||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [openbsd]
|
os: [openbsd]
|
||||||
|
|
||||||
"@esbuild/openbsd-x64@0.23.1":
|
|
||||||
resolution:
|
|
||||||
{
|
|
||||||
integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==,
|
|
||||||
}
|
|
||||||
engines: { node: ">=18" }
|
|
||||||
cpu: [x64]
|
|
||||||
os: [openbsd]
|
|
||||||
|
|
||||||
"@esbuild/openbsd-x64@0.25.0":
|
"@esbuild/openbsd-x64@0.25.0":
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -806,15 +630,6 @@ packages:
|
|||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [openbsd]
|
os: [openbsd]
|
||||||
|
|
||||||
"@esbuild/sunos-x64@0.23.1":
|
|
||||||
resolution:
|
|
||||||
{
|
|
||||||
integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==,
|
|
||||||
}
|
|
||||||
engines: { node: ">=18" }
|
|
||||||
cpu: [x64]
|
|
||||||
os: [sunos]
|
|
||||||
|
|
||||||
"@esbuild/sunos-x64@0.25.0":
|
"@esbuild/sunos-x64@0.25.0":
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -824,15 +639,6 @@ packages:
|
|||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [sunos]
|
os: [sunos]
|
||||||
|
|
||||||
"@esbuild/win32-arm64@0.23.1":
|
|
||||||
resolution:
|
|
||||||
{
|
|
||||||
integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==,
|
|
||||||
}
|
|
||||||
engines: { node: ">=18" }
|
|
||||||
cpu: [arm64]
|
|
||||||
os: [win32]
|
|
||||||
|
|
||||||
"@esbuild/win32-arm64@0.25.0":
|
"@esbuild/win32-arm64@0.25.0":
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -842,15 +648,6 @@ packages:
|
|||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
"@esbuild/win32-ia32@0.23.1":
|
|
||||||
resolution:
|
|
||||||
{
|
|
||||||
integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==,
|
|
||||||
}
|
|
||||||
engines: { node: ">=18" }
|
|
||||||
cpu: [ia32]
|
|
||||||
os: [win32]
|
|
||||||
|
|
||||||
"@esbuild/win32-ia32@0.25.0":
|
"@esbuild/win32-ia32@0.25.0":
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -860,15 +657,6 @@ packages:
|
|||||||
cpu: [ia32]
|
cpu: [ia32]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
"@esbuild/win32-x64@0.23.1":
|
|
||||||
resolution:
|
|
||||||
{
|
|
||||||
integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==,
|
|
||||||
}
|
|
||||||
engines: { node: ">=18" }
|
|
||||||
cpu: [x64]
|
|
||||||
os: [win32]
|
|
||||||
|
|
||||||
"@esbuild/win32-x64@0.25.0":
|
"@esbuild/win32-x64@0.25.0":
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -1077,6 +865,15 @@ packages:
|
|||||||
integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==,
|
integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"@langchain/anthropic@0.3.15":
|
||||||
|
resolution:
|
||||||
|
{
|
||||||
|
integrity: sha512-Ar2viYcZ64idgV7EtCBCb36tIkNtPAhQRxSaMTWPHGspFgMfvwRoleVri9e90sCpjpS9xhlHsIQ0LlUS/Atsrw==,
|
||||||
|
}
|
||||||
|
engines: { node: ">=18" }
|
||||||
|
peerDependencies:
|
||||||
|
"@langchain/core": ">=0.2.21 <0.4.0"
|
||||||
|
|
||||||
"@langchain/core@0.3.41":
|
"@langchain/core@0.3.41":
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -1093,10 +890,10 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
"@langchain/core": ">=0.3.17 <0.4.0"
|
"@langchain/core": ">=0.3.17 <0.4.0"
|
||||||
|
|
||||||
"@langchain/langgraph-api@0.0.14-experimental.1":
|
"@langchain/langgraph-api@0.0.14":
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
integrity: sha512-gSQzZZk9tIrxXMQjudQbYHXPeK7l3Y/YbzCtnH6hWHvETQOZApUn0G18O5hWT9iYaAzZfSS8ExG7y6YM0MsFTQ==,
|
integrity: sha512-/lh6ug9kXBhL5zrX56MA4xxNt99kzLQqNuYqQRd2PWflVNATMRJNMfWhLjh91Hbn0yf3CWQoIX/6mPQiwCfrKg==,
|
||||||
}
|
}
|
||||||
engines: { node: ^18.19.0 || >=20.16.0 }
|
engines: { node: ^18.19.0 || >=20.16.0 }
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1114,18 +911,18 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
"@langchain/core": ">=0.2.31 <0.4.0"
|
"@langchain/core": ">=0.2.31 <0.4.0"
|
||||||
|
|
||||||
"@langchain/langgraph-cli@0.0.14-experimental.1":
|
"@langchain/langgraph-cli@0.0.14":
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
integrity: sha512-S8Y7WrBPsNZR7wUyWj3De0sEdTTf+ipJf1lCrJho+moL9TVXUXUE+oFoMb1G/uHvt8Q/FCSE9BfadEg4JUb5MQ==,
|
integrity: sha512-wB6Q1VjAspGUXfbZnNuq56lXQNHHedqN09nfpGxNQnfnCf8VW/8veSkhCaNV5gdvRV9mDAWhJ0i78gxLxPhbRw==,
|
||||||
}
|
}
|
||||||
engines: { node: ^18.19.0 || >=20.16.0 }
|
engines: { node: ^18.19.0 || >=20.16.0 }
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
"@langchain/langgraph-sdk@0.0.47-experimental.0":
|
"@langchain/langgraph-sdk@0.0.52":
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
integrity: sha512-di60Pi2knQbe/sjOB3gNbNQNuTIhj0Yjls0SfEYeWDHirSN9heumPB/oxvwaxLBA8JKhuHg2h5lKUxAIT4b+aA==,
|
integrity: sha512-nPHm9trQJnRxUDWVl0LCZ0FrQu22RtnamTkrlNibTxpcpI8E3d6KxGxzwYqLgs+hQVyJXjCb6pTNSgahaPaR5g==,
|
||||||
}
|
}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
"@langchain/core": ">=0.2.31 <0.4.0"
|
"@langchain/core": ">=0.2.31 <0.4.0"
|
||||||
@@ -2871,14 +2668,6 @@ packages:
|
|||||||
integrity: sha512-62CPYzyfcRE7OowGmWGKs9sz43QhCa/dZ5h6ruZhDg65B5Zsn++4EA4NKIwEMbAio9JV8+FJZNXzejNX/RjSkg==,
|
integrity: sha512-62CPYzyfcRE7OowGmWGKs9sz43QhCa/dZ5h6ruZhDg65B5Zsn++4EA4NKIwEMbAio9JV8+FJZNXzejNX/RjSkg==,
|
||||||
}
|
}
|
||||||
|
|
||||||
esbuild@0.23.1:
|
|
||||||
resolution:
|
|
||||||
{
|
|
||||||
integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==,
|
|
||||||
}
|
|
||||||
engines: { node: ">=18" }
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
esbuild@0.25.0:
|
esbuild@0.25.0:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -3073,6 +2862,13 @@ packages:
|
|||||||
integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==,
|
integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fast-xml-parser@4.5.3:
|
||||||
|
resolution:
|
||||||
|
{
|
||||||
|
integrity: sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==,
|
||||||
|
}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
fastq@1.19.1:
|
fastq@1.19.1:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -5169,6 +4965,12 @@ packages:
|
|||||||
}
|
}
|
||||||
engines: { node: ">=8" }
|
engines: { node: ">=8" }
|
||||||
|
|
||||||
|
strnum@1.1.2:
|
||||||
|
resolution:
|
||||||
|
{
|
||||||
|
integrity: sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==,
|
||||||
|
}
|
||||||
|
|
||||||
style-to-object@1.0.8:
|
style-to-object@1.0.8:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -5774,6 +5576,18 @@ snapshots:
|
|||||||
"@jridgewell/gen-mapping": 0.3.8
|
"@jridgewell/gen-mapping": 0.3.8
|
||||||
"@jridgewell/trace-mapping": 0.3.25
|
"@jridgewell/trace-mapping": 0.3.25
|
||||||
|
|
||||||
|
"@anthropic-ai/sdk@0.37.0":
|
||||||
|
dependencies:
|
||||||
|
"@types/node": 18.19.76
|
||||||
|
"@types/node-fetch": 2.6.12
|
||||||
|
abort-controller: 3.0.0
|
||||||
|
agentkeepalive: 4.6.0
|
||||||
|
form-data-encoder: 1.7.2
|
||||||
|
formdata-node: 4.4.1
|
||||||
|
node-fetch: 2.7.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- encoding
|
||||||
|
|
||||||
"@assistant-ui/react-markdown@0.8.0(@assistant-ui/react@0.8.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)":
|
"@assistant-ui/react-markdown@0.8.0(@assistant-ui/react@0.8.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)":
|
||||||
dependencies:
|
dependencies:
|
||||||
"@assistant-ui/react": 0.8.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
"@assistant-ui/react": 0.8.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||||
@@ -5956,150 +5770,78 @@ snapshots:
|
|||||||
enabled: 2.0.0
|
enabled: 2.0.0
|
||||||
kuler: 2.0.0
|
kuler: 2.0.0
|
||||||
|
|
||||||
"@esbuild/aix-ppc64@0.23.1":
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
"@esbuild/aix-ppc64@0.25.0":
|
"@esbuild/aix-ppc64@0.25.0":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
"@esbuild/android-arm64@0.23.1":
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
"@esbuild/android-arm64@0.25.0":
|
"@esbuild/android-arm64@0.25.0":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
"@esbuild/android-arm@0.23.1":
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
"@esbuild/android-arm@0.25.0":
|
"@esbuild/android-arm@0.25.0":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
"@esbuild/android-x64@0.23.1":
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
"@esbuild/android-x64@0.25.0":
|
"@esbuild/android-x64@0.25.0":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
"@esbuild/darwin-arm64@0.23.1":
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
"@esbuild/darwin-arm64@0.25.0":
|
"@esbuild/darwin-arm64@0.25.0":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
"@esbuild/darwin-x64@0.23.1":
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
"@esbuild/darwin-x64@0.25.0":
|
"@esbuild/darwin-x64@0.25.0":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
"@esbuild/freebsd-arm64@0.23.1":
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
"@esbuild/freebsd-arm64@0.25.0":
|
"@esbuild/freebsd-arm64@0.25.0":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
"@esbuild/freebsd-x64@0.23.1":
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
"@esbuild/freebsd-x64@0.25.0":
|
"@esbuild/freebsd-x64@0.25.0":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
"@esbuild/linux-arm64@0.23.1":
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
"@esbuild/linux-arm64@0.25.0":
|
"@esbuild/linux-arm64@0.25.0":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
"@esbuild/linux-arm@0.23.1":
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
"@esbuild/linux-arm@0.25.0":
|
"@esbuild/linux-arm@0.25.0":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
"@esbuild/linux-ia32@0.23.1":
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
"@esbuild/linux-ia32@0.25.0":
|
"@esbuild/linux-ia32@0.25.0":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
"@esbuild/linux-loong64@0.23.1":
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
"@esbuild/linux-loong64@0.25.0":
|
"@esbuild/linux-loong64@0.25.0":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
"@esbuild/linux-mips64el@0.23.1":
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
"@esbuild/linux-mips64el@0.25.0":
|
"@esbuild/linux-mips64el@0.25.0":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
"@esbuild/linux-ppc64@0.23.1":
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
"@esbuild/linux-ppc64@0.25.0":
|
"@esbuild/linux-ppc64@0.25.0":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
"@esbuild/linux-riscv64@0.23.1":
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
"@esbuild/linux-riscv64@0.25.0":
|
"@esbuild/linux-riscv64@0.25.0":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
"@esbuild/linux-s390x@0.23.1":
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
"@esbuild/linux-s390x@0.25.0":
|
"@esbuild/linux-s390x@0.25.0":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
"@esbuild/linux-x64@0.23.1":
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
"@esbuild/linux-x64@0.25.0":
|
"@esbuild/linux-x64@0.25.0":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
"@esbuild/netbsd-arm64@0.25.0":
|
"@esbuild/netbsd-arm64@0.25.0":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
"@esbuild/netbsd-x64@0.23.1":
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
"@esbuild/netbsd-x64@0.25.0":
|
"@esbuild/netbsd-x64@0.25.0":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
"@esbuild/openbsd-arm64@0.23.1":
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
"@esbuild/openbsd-arm64@0.25.0":
|
"@esbuild/openbsd-arm64@0.25.0":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
"@esbuild/openbsd-x64@0.23.1":
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
"@esbuild/openbsd-x64@0.25.0":
|
"@esbuild/openbsd-x64@0.25.0":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
"@esbuild/sunos-x64@0.23.1":
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
"@esbuild/sunos-x64@0.25.0":
|
"@esbuild/sunos-x64@0.25.0":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
"@esbuild/win32-arm64@0.23.1":
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
"@esbuild/win32-arm64@0.25.0":
|
"@esbuild/win32-arm64@0.25.0":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
"@esbuild/win32-ia32@0.23.1":
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
"@esbuild/win32-ia32@0.25.0":
|
"@esbuild/win32-ia32@0.25.0":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
"@esbuild/win32-x64@0.23.1":
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
"@esbuild/win32-x64@0.25.0":
|
"@esbuild/win32-x64@0.25.0":
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -6218,6 +5960,16 @@ snapshots:
|
|||||||
"@jridgewell/resolve-uri": 3.1.2
|
"@jridgewell/resolve-uri": 3.1.2
|
||||||
"@jridgewell/sourcemap-codec": 1.5.0
|
"@jridgewell/sourcemap-codec": 1.5.0
|
||||||
|
|
||||||
|
"@langchain/anthropic@0.3.15(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))":
|
||||||
|
dependencies:
|
||||||
|
"@anthropic-ai/sdk": 0.37.0
|
||||||
|
"@langchain/core": 0.3.41(openai@4.85.4(zod@3.24.2))
|
||||||
|
fast-xml-parser: 4.5.3
|
||||||
|
zod: 3.24.2
|
||||||
|
zod-to-json-schema: 3.24.3(zod@3.24.2)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- encoding
|
||||||
|
|
||||||
"@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2))":
|
"@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2))":
|
||||||
dependencies:
|
dependencies:
|
||||||
"@cfworker/json-schema": 4.1.1
|
"@cfworker/json-schema": 4.1.1
|
||||||
@@ -6243,7 +5995,7 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- zod
|
- zod
|
||||||
|
|
||||||
"@langchain/langgraph-api@0.0.14-experimental.1(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(@langchain/langgraph-checkpoint@0.0.15(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2))))(@langchain/langgraph@0.2.49(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0))(openai@4.85.4(zod@3.24.2))(typescript@5.7.3)":
|
"@langchain/langgraph-api@0.0.14(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(@langchain/langgraph-checkpoint@0.0.15(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2))))(@langchain/langgraph@0.2.49(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0))(openai@4.85.4(zod@3.24.2))(typescript@5.7.3)":
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/code-frame": 7.26.2
|
"@babel/code-frame": 7.26.2
|
||||||
"@hono/node-server": 1.13.8(hono@4.7.2)
|
"@hono/node-server": 1.13.8(hono@4.7.2)
|
||||||
@@ -6255,7 +6007,7 @@ snapshots:
|
|||||||
"@typescript/vfs": 1.6.1(typescript@5.7.3)
|
"@typescript/vfs": 1.6.1(typescript@5.7.3)
|
||||||
dedent: 1.5.3
|
dedent: 1.5.3
|
||||||
dotenv: 16.4.7
|
dotenv: 16.4.7
|
||||||
esbuild: 0.23.1
|
esbuild: 0.25.0
|
||||||
esbuild-plugin-tailwindcss: 2.0.1
|
esbuild-plugin-tailwindcss: 2.0.1
|
||||||
exit-hook: 4.0.0
|
exit-hook: 4.0.0
|
||||||
hono: 4.7.2
|
hono: 4.7.2
|
||||||
@@ -6279,11 +6031,11 @@ snapshots:
|
|||||||
"@langchain/core": 0.3.41(openai@4.85.4(zod@3.24.2))
|
"@langchain/core": 0.3.41(openai@4.85.4(zod@3.24.2))
|
||||||
uuid: 10.0.0
|
uuid: 10.0.0
|
||||||
|
|
||||||
"@langchain/langgraph-cli@0.0.14-experimental.1(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(@langchain/langgraph-checkpoint@0.0.15(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2))))(@langchain/langgraph@0.2.49(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0))(openai@4.85.4(zod@3.24.2))(typescript@5.7.3)":
|
"@langchain/langgraph-cli@0.0.14(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(@langchain/langgraph-checkpoint@0.0.15(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2))))(@langchain/langgraph@0.2.49(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0))(openai@4.85.4(zod@3.24.2))(typescript@5.7.3)":
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/code-frame": 7.26.2
|
"@babel/code-frame": 7.26.2
|
||||||
"@commander-js/extra-typings": 13.1.0(commander@13.1.0)
|
"@commander-js/extra-typings": 13.1.0(commander@13.1.0)
|
||||||
"@langchain/langgraph-api": 0.0.14-experimental.1(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(@langchain/langgraph-checkpoint@0.0.15(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2))))(@langchain/langgraph@0.2.49(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0))(openai@4.85.4(zod@3.24.2))(typescript@5.7.3)
|
"@langchain/langgraph-api": 0.0.14(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(@langchain/langgraph-checkpoint@0.0.15(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2))))(@langchain/langgraph@0.2.49(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0))(openai@4.85.4(zod@3.24.2))(typescript@5.7.3)
|
||||||
chokidar: 4.0.3
|
chokidar: 4.0.3
|
||||||
commander: 13.1.0
|
commander: 13.1.0
|
||||||
dedent: 1.5.3
|
dedent: 1.5.3
|
||||||
@@ -6308,7 +6060,7 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- typescript
|
- typescript
|
||||||
|
|
||||||
"@langchain/langgraph-sdk@0.0.47-experimental.0(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0)":
|
"@langchain/langgraph-sdk@0.0.52(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0)":
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/json-schema": 7.0.15
|
"@types/json-schema": 7.0.15
|
||||||
p-queue: 6.6.2
|
p-queue: 6.6.2
|
||||||
@@ -6322,7 +6074,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@langchain/core": 0.3.41(openai@4.85.4(zod@3.24.2))
|
"@langchain/core": 0.3.41(openai@4.85.4(zod@3.24.2))
|
||||||
"@langchain/langgraph-checkpoint": 0.0.15(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))
|
"@langchain/langgraph-checkpoint": 0.0.15(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))
|
||||||
"@langchain/langgraph-sdk": 0.0.47-experimental.0(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0)
|
"@langchain/langgraph-sdk": 0.0.52(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0)
|
||||||
uuid: 10.0.0
|
uuid: 10.0.0
|
||||||
zod: 3.24.2
|
zod: 3.24.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -7296,33 +7048,6 @@ snapshots:
|
|||||||
postcss: 8.5.3
|
postcss: 8.5.3
|
||||||
postcss-modules: 6.0.1(postcss@8.5.3)
|
postcss-modules: 6.0.1(postcss@8.5.3)
|
||||||
|
|
||||||
esbuild@0.23.1:
|
|
||||||
optionalDependencies:
|
|
||||||
"@esbuild/aix-ppc64": 0.23.1
|
|
||||||
"@esbuild/android-arm": 0.23.1
|
|
||||||
"@esbuild/android-arm64": 0.23.1
|
|
||||||
"@esbuild/android-x64": 0.23.1
|
|
||||||
"@esbuild/darwin-arm64": 0.23.1
|
|
||||||
"@esbuild/darwin-x64": 0.23.1
|
|
||||||
"@esbuild/freebsd-arm64": 0.23.1
|
|
||||||
"@esbuild/freebsd-x64": 0.23.1
|
|
||||||
"@esbuild/linux-arm": 0.23.1
|
|
||||||
"@esbuild/linux-arm64": 0.23.1
|
|
||||||
"@esbuild/linux-ia32": 0.23.1
|
|
||||||
"@esbuild/linux-loong64": 0.23.1
|
|
||||||
"@esbuild/linux-mips64el": 0.23.1
|
|
||||||
"@esbuild/linux-ppc64": 0.23.1
|
|
||||||
"@esbuild/linux-riscv64": 0.23.1
|
|
||||||
"@esbuild/linux-s390x": 0.23.1
|
|
||||||
"@esbuild/linux-x64": 0.23.1
|
|
||||||
"@esbuild/netbsd-x64": 0.23.1
|
|
||||||
"@esbuild/openbsd-arm64": 0.23.1
|
|
||||||
"@esbuild/openbsd-x64": 0.23.1
|
|
||||||
"@esbuild/sunos-x64": 0.23.1
|
|
||||||
"@esbuild/win32-arm64": 0.23.1
|
|
||||||
"@esbuild/win32-ia32": 0.23.1
|
|
||||||
"@esbuild/win32-x64": 0.23.1
|
|
||||||
|
|
||||||
esbuild@0.25.0:
|
esbuild@0.25.0:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
"@esbuild/aix-ppc64": 0.25.0
|
"@esbuild/aix-ppc64": 0.25.0
|
||||||
@@ -7484,6 +7209,10 @@ snapshots:
|
|||||||
|
|
||||||
fast-levenshtein@2.0.6: {}
|
fast-levenshtein@2.0.6: {}
|
||||||
|
|
||||||
|
fast-xml-parser@4.5.3:
|
||||||
|
dependencies:
|
||||||
|
strnum: 1.1.2
|
||||||
|
|
||||||
fastq@1.19.1:
|
fastq@1.19.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
reusify: 1.1.0
|
reusify: 1.1.0
|
||||||
@@ -8939,6 +8668,8 @@ snapshots:
|
|||||||
|
|
||||||
strip-json-comments@3.1.1: {}
|
strip-json-comments@3.1.1: {}
|
||||||
|
|
||||||
|
strnum@1.1.2: {}
|
||||||
|
|
||||||
style-to-object@1.0.8:
|
style-to-object@1.0.8:
|
||||||
dependencies:
|
dependencies:
|
||||||
inline-style-parser: 0.2.4
|
inline-style-parser: 0.2.4
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { useThreads } from "@/providers/Thread";
|
import { useThreads } from "@/providers/Thread";
|
||||||
import { Thread } from "@langchain/langgraph-sdk";
|
import { Thread } from "@langchain/langgraph-sdk";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
import { getContentString } from "../utils";
|
import { getContentString } from "../utils";
|
||||||
import { useQueryParam, StringParam, BooleanParam } from "use-query-params";
|
import { useQueryParam, StringParam, BooleanParam } from "use-query-params";
|
||||||
import {
|
import {
|
||||||
@@ -11,6 +12,8 @@ import {
|
|||||||
SheetTitle,
|
SheetTitle,
|
||||||
} from "@/components/ui/sheet";
|
} from "@/components/ui/sheet";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
import { PanelRightOpen } from "lucide-react";
|
||||||
|
import { useMediaQuery } from "@/hooks/useMediaQuery";
|
||||||
|
|
||||||
function ThreadList({
|
function ThreadList({
|
||||||
threads,
|
threads,
|
||||||
@@ -22,7 +25,7 @@ function ThreadList({
|
|||||||
const [threadId, setThreadId] = useQueryParam("threadId", StringParam);
|
const [threadId, setThreadId] = useQueryParam("threadId", StringParam);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col gap-2 items-start justify-start overflow-y-scroll [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-gray-300 [&::-webkit-scrollbar-track]:bg-transparent">
|
<div className="h-full flex flex-col w-full gap-2 items-start justify-start overflow-y-scroll [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-gray-300 [&::-webkit-scrollbar-track]:bg-transparent">
|
||||||
{threads.map((t) => {
|
{threads.map((t) => {
|
||||||
let itemText = t.thread_id;
|
let itemText = t.thread_id;
|
||||||
if (
|
if (
|
||||||
@@ -36,10 +39,10 @@ function ThreadList({
|
|||||||
itemText = getContentString(firstMessage.content);
|
itemText = getContentString(firstMessage.content);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div key={t.thread_id} className="w-full">
|
<div key={t.thread_id} className="w-full px-1">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
className="truncate text-left items-start justify-start w-[264px]"
|
className="text-left items-start justify-start font-normal w-[280px]"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onThreadClick?.(t.thread_id);
|
onThreadClick?.(t.thread_id);
|
||||||
@@ -47,7 +50,7 @@ function ThreadList({
|
|||||||
setThreadId(t.thread_id);
|
setThreadId(t.thread_id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{itemText}
|
<p className="truncate text-ellipsis">{itemText}</p>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -58,15 +61,16 @@ function ThreadList({
|
|||||||
|
|
||||||
function ThreadHistoryLoading() {
|
function ThreadHistoryLoading() {
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col gap-2 items-start justify-start overflow-y-scroll [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-gray-300 [&::-webkit-scrollbar-track]:bg-transparent">
|
<div className="h-full flex flex-col w-full gap-2 items-start justify-start overflow-y-scroll [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-gray-300 [&::-webkit-scrollbar-track]:bg-transparent">
|
||||||
{Array.from({ length: 30 }).map((_, i) => (
|
{Array.from({ length: 30 }).map((_, i) => (
|
||||||
<Skeleton key={`skeleton-${i}`} className="w-[264px] h-10" />
|
<Skeleton key={`skeleton-${i}`} className="w-[280px] h-10" />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ThreadHistory() {
|
export default function ThreadHistory() {
|
||||||
|
const isLargeScreen = useMediaQuery("(min-width: 1024px)");
|
||||||
const [chatHistoryOpen, setChatHistoryOpen] = useQueryParam(
|
const [chatHistoryOpen, setChatHistoryOpen] = useQueryParam(
|
||||||
"chatHistoryOpen",
|
"chatHistoryOpen",
|
||||||
BooleanParam,
|
BooleanParam,
|
||||||
@@ -86,25 +90,44 @@ export default function ThreadHistory() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="hidden lg:flex flex-col border-r-[1px] border-slate-300 items-start justify-start gap-6 h-screen w-[300px] shrink-0 px-2 py-4 shadow-inner-right">
|
<div className="hidden lg:flex flex-col border-r-[1px] border-slate-300 items-start justify-start gap-6 h-screen w-[300px] shrink-0 shadow-inner-right">
|
||||||
<h1 className="text-2xl font-medium pl-4">Thread History</h1>
|
<div className="flex items-center justify-between w-full pt-1.5 px-4">
|
||||||
|
<Button
|
||||||
|
className="hover:bg-gray-100"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => setChatHistoryOpen((p) => !p)}
|
||||||
|
>
|
||||||
|
<PanelRightOpen className="size-5" />
|
||||||
|
</Button>
|
||||||
|
<h1 className="text-xl font-semibold tracking-tight">
|
||||||
|
Thread History
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
{threadsLoading ? (
|
{threadsLoading ? (
|
||||||
<ThreadHistoryLoading />
|
<ThreadHistoryLoading />
|
||||||
) : (
|
) : (
|
||||||
<ThreadList threads={threads} />
|
<ThreadList threads={threads} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Sheet open={!!chatHistoryOpen} onOpenChange={setChatHistoryOpen}>
|
<div className="lg:hidden">
|
||||||
<SheetContent side="left" className="lg:hidden flex">
|
<Sheet
|
||||||
<SheetHeader>
|
open={!!chatHistoryOpen && !isLargeScreen}
|
||||||
<SheetTitle>Thread History</SheetTitle>
|
onOpenChange={(open) => {
|
||||||
</SheetHeader>
|
if (isLargeScreen) return;
|
||||||
<ThreadList
|
setChatHistoryOpen(open);
|
||||||
threads={threads}
|
}}
|
||||||
onThreadClick={() => setChatHistoryOpen((o) => !o)}
|
>
|
||||||
/>
|
<SheetContent side="left" className="lg:hidden flex">
|
||||||
</SheetContent>
|
<SheetHeader>
|
||||||
</Sheet>
|
<SheetTitle>Thread History</SheetTitle>
|
||||||
|
</SheetHeader>
|
||||||
|
<ThreadList
|
||||||
|
threads={threads}
|
||||||
|
onThreadClick={() => setChatHistoryOpen((o) => !o)}
|
||||||
|
/>
|
||||||
|
</SheetContent>
|
||||||
|
</Sheet>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import { ReactNode, useEffect, useRef } from "react";
|
import { ReactNode, useEffect, useRef } from "react";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { useStreamContext } from "@/providers/Stream";
|
import { useStreamContext } from "@/providers/Stream";
|
||||||
import { useState, FormEvent } from "react";
|
import { useState, FormEvent } from "react";
|
||||||
import { Input } from "../ui/input";
|
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import { Checkpoint, Message } from "@langchain/langgraph-sdk";
|
import { Checkpoint, Message } from "@langchain/langgraph-sdk";
|
||||||
import { AssistantMessage, AssistantMessageLoading } from "./messages/ai";
|
import { AssistantMessage, AssistantMessageLoading } from "./messages/ai";
|
||||||
@@ -24,6 +24,7 @@ import { BooleanParam, StringParam, useQueryParam } from "use-query-params";
|
|||||||
import { StickToBottom, useStickToBottomContext } from "use-stick-to-bottom";
|
import { StickToBottom, useStickToBottomContext } from "use-stick-to-bottom";
|
||||||
import ThreadHistory from "./history";
|
import ThreadHistory from "./history";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { useMediaQuery } from "@/hooks/useMediaQuery";
|
||||||
|
|
||||||
function StickyToBottomContent(props: {
|
function StickyToBottomContent(props: {
|
||||||
content: ReactNode;
|
content: ReactNode;
|
||||||
@@ -65,12 +66,13 @@ function ScrollToBottom(props: { className?: string }) {
|
|||||||
|
|
||||||
export function Thread() {
|
export function Thread() {
|
||||||
const [threadId, setThreadId] = useQueryParam("threadId", StringParam);
|
const [threadId, setThreadId] = useQueryParam("threadId", StringParam);
|
||||||
const [_, setChatHistoryOpen] = useQueryParam(
|
const [chatHistoryOpen, setChatHistoryOpen] = useQueryParam(
|
||||||
"chatHistoryOpen",
|
"chatHistoryOpen",
|
||||||
BooleanParam,
|
BooleanParam,
|
||||||
);
|
);
|
||||||
const [input, setInput] = useState("");
|
const [input, setInput] = useState("");
|
||||||
const [firstTokenReceived, setFirstTokenReceived] = useState(false);
|
const [firstTokenReceived, setFirstTokenReceived] = useState(false);
|
||||||
|
const isLargeScreen = useMediaQuery("(min-width: 1024px)");
|
||||||
|
|
||||||
const stream = useStreamContext();
|
const stream = useStreamContext();
|
||||||
const messages = stream.messages;
|
const messages = stream.messages;
|
||||||
@@ -166,32 +168,91 @@ export function Thread() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full h-screen overflow-hidden">
|
<div className="flex w-full h-screen overflow-hidden">
|
||||||
<ThreadHistory />
|
<div className="relative lg:flex hidden">
|
||||||
<div
|
<motion.div
|
||||||
|
className="absolute h-full border-r bg-white overflow-hidden z-20"
|
||||||
|
style={{ width: 300 }}
|
||||||
|
animate={
|
||||||
|
isLargeScreen
|
||||||
|
? { x: chatHistoryOpen ? 0 : -300 }
|
||||||
|
: { x: chatHistoryOpen ? 0 : -300 }
|
||||||
|
}
|
||||||
|
initial={{ x: -300 }}
|
||||||
|
transition={
|
||||||
|
isLargeScreen
|
||||||
|
? { type: "spring", stiffness: 300, damping: 30 }
|
||||||
|
: { duration: 0 }
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="relative h-full" style={{ width: 300 }}>
|
||||||
|
<ThreadHistory />
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
<motion.div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex-1 flex flex-col min-w-0 overflow-hidden",
|
"flex-1 flex flex-col min-w-0 overflow-hidden relative",
|
||||||
!chatStarted && "grid-rows-[1fr]",
|
!chatStarted && "grid-rows-[1fr]",
|
||||||
)}
|
)}
|
||||||
|
layout={isLargeScreen}
|
||||||
|
animate={{
|
||||||
|
marginLeft: chatHistoryOpen ? (isLargeScreen ? 300 : 0) : 0,
|
||||||
|
width: chatHistoryOpen
|
||||||
|
? isLargeScreen
|
||||||
|
? "calc(100% - 300px)"
|
||||||
|
: "100%"
|
||||||
|
: "100%",
|
||||||
|
}}
|
||||||
|
transition={
|
||||||
|
isLargeScreen
|
||||||
|
? { type: "spring", stiffness: 300, damping: 30 }
|
||||||
|
: { duration: 0 }
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{chatStarted && (
|
{!chatStarted && (
|
||||||
<div className="flex items-center justify-between gap-3 p-2 pl-4 z-10 relative">
|
<div className="absolute top-0 left-0 w-full flex items-center justify-between gap-3 p-2 pl-4 z-10">
|
||||||
<div className="flex gap-2 items-center justify-start">
|
{(!chatHistoryOpen || !isLargeScreen) && (
|
||||||
<button
|
|
||||||
className="flex gap-2 items-center cursor-pointer"
|
|
||||||
onClick={() => setThreadId(null)}
|
|
||||||
>
|
|
||||||
<LangGraphLogoSVG width={32} height={32} />
|
|
||||||
<span className="text-xl font-semibold tracking-tight">
|
|
||||||
LangGraph Chat
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<Button
|
<Button
|
||||||
className="flex lg:hidden"
|
className="hover:bg-gray-100"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => setChatHistoryOpen((p) => !p)}
|
onClick={() => setChatHistoryOpen((p) => !p)}
|
||||||
>
|
>
|
||||||
<PanelRightOpen />
|
<PanelRightOpen className="size-5" />
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{chatStarted && (
|
||||||
|
<div className="flex items-center justify-between gap-3 p-2 pl-4 z-10 relative">
|
||||||
|
<div className="flex items-center justify-start gap-2 relative">
|
||||||
|
<div className="absolute left-0 z-10">
|
||||||
|
{(!chatHistoryOpen || !isLargeScreen) && (
|
||||||
|
<Button
|
||||||
|
className="hover:bg-gray-100"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => setChatHistoryOpen((p) => !p)}
|
||||||
|
>
|
||||||
|
<PanelRightOpen className="size-5" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<motion.button
|
||||||
|
className="flex gap-2 items-center cursor-pointer"
|
||||||
|
onClick={() => setThreadId(null)}
|
||||||
|
animate={{
|
||||||
|
marginLeft: !chatHistoryOpen ? 48 : 0,
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
type: "spring",
|
||||||
|
stiffness: 300,
|
||||||
|
damping: 30,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LangGraphLogoSVG width={32} height={32} />
|
||||||
|
<span className="text-xl font-semibold tracking-tight">
|
||||||
|
Chat LangGraph
|
||||||
|
</span>
|
||||||
|
</motion.button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<TooltipIconButton
|
<TooltipIconButton
|
||||||
@@ -215,7 +276,7 @@ export function Thread() {
|
|||||||
!chatStarted && "flex flex-col items-stretch mt-[25vh]",
|
!chatStarted && "flex flex-col items-stretch mt-[25vh]",
|
||||||
chatStarted && "grid grid-rows-[1fr_auto]",
|
chatStarted && "grid grid-rows-[1fr_auto]",
|
||||||
)}
|
)}
|
||||||
contentClassName="pt-8 pb-16 px-4 max-w-4xl mx-auto flex flex-col gap-4 w-full"
|
contentClassName="pt-8 pb-16 max-w-3xl mx-auto flex flex-col gap-4 w-full"
|
||||||
content={
|
content={
|
||||||
<>
|
<>
|
||||||
{messages
|
{messages
|
||||||
@@ -242,29 +303,36 @@ export function Thread() {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
footer={
|
footer={
|
||||||
<div className="sticky flex flex-col items-center gap-8 bottom-8 px-4">
|
<div className="sticky flex flex-col items-center gap-8 bottom-0 px-4 bg-white">
|
||||||
{!chatStarted && (
|
{!chatStarted && (
|
||||||
<div className="flex gap-3 items-center">
|
<div className="flex gap-3 items-center">
|
||||||
<LangGraphLogoSVG className="flex-shrink-0 h-8" />
|
<LangGraphLogoSVG className="flex-shrink-0 h-8" />
|
||||||
<h1 className="text-2xl font-semibold tracking-tight">
|
<h1 className="text-2xl font-semibold tracking-tight">
|
||||||
LangGraph Chat
|
Chat LangGraph
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ScrollToBottom className="absolute bottom-full left-1/2 -translate-x-1/2 mb-4 animate-in fade-in-0 zoom-in-95" />
|
<ScrollToBottom className="absolute bottom-full left-1/2 -translate-x-1/2 mb-4 animate-in fade-in-0 zoom-in-95" />
|
||||||
|
|
||||||
<div className="bg-background rounded-2xl border shadow-md mx-auto w-full max-w-4xl">
|
<div className="bg-muted rounded-2xl border shadow-xs mx-auto mb-8 w-full max-w-3xl relative z-10">
|
||||||
<form
|
<form
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
className="grid grid-rows-[1fr_auto] gap-2 max-w-4xl mx-auto"
|
className="grid grid-rows-[1fr_auto] gap-2 max-w-3xl mx-auto"
|
||||||
>
|
>
|
||||||
<Input
|
<textarea
|
||||||
type="text"
|
|
||||||
value={input}
|
value={input}
|
||||||
onChange={(e) => setInput(e.target.value)}
|
onChange={(e) => setInput(e.target.value)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter" && !e.shiftKey && !e.metaKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
const el = e.target as HTMLElement | undefined;
|
||||||
|
const form = el?.closest("form");
|
||||||
|
form?.requestSubmit();
|
||||||
|
}
|
||||||
|
}}
|
||||||
placeholder="Type your message..."
|
placeholder="Type your message..."
|
||||||
className="px-4 py-6 border-none bg-transparent shadow-none ring-0 outline-none focus:outline-none focus:ring-0"
|
className="p-3.5 pb-0 border-none bg-transparent field-sizing-content shadow-none ring-0 outline-none focus:outline-none focus:ring-0 resize-none"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex items-center justify-end p-2 pt-0">
|
<div className="flex items-center justify-end p-2 pt-0">
|
||||||
@@ -276,6 +344,7 @@ export function Thread() {
|
|||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
|
className="transition-all shadow-md"
|
||||||
disabled={isLoading || !input.trim()}
|
disabled={isLoading || !input.trim()}
|
||||||
>
|
>
|
||||||
Send
|
Send
|
||||||
@@ -288,7 +357,7 @@ export function Thread() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</StickToBottom>
|
</StickToBottom>
|
||||||
</div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
|
import { parsePartialJson } from "@langchain/core/output_parsers";
|
||||||
import { useStreamContext } from "@/providers/Stream";
|
import { useStreamContext } from "@/providers/Stream";
|
||||||
import { Checkpoint, Message } from "@langchain/langgraph-sdk";
|
import { AIMessage, Checkpoint, Message } from "@langchain/langgraph-sdk";
|
||||||
import { getContentString } from "../utils";
|
import { getContentString } from "../utils";
|
||||||
import { BranchSwitcher, CommandBar } from "./shared";
|
import { BranchSwitcher, CommandBar } from "./shared";
|
||||||
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
|
||||||
import { MarkdownText } from "../markdown-text";
|
import { MarkdownText } from "../markdown-text";
|
||||||
import { LoadExternalComponent } from "@langchain/langgraph-sdk/react-ui";
|
import { LoadExternalComponent } from "@langchain/langgraph-sdk/react-ui";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { ToolCalls, ToolResult } from "./tool-calls";
|
import { ToolCalls, ToolResult } from "./tool-calls";
|
||||||
|
import { MessageContentComplex } from "@langchain/core/messages";
|
||||||
|
import { Fragment } from "react/jsx-runtime";
|
||||||
|
|
||||||
function CustomComponent({
|
function CustomComponent({
|
||||||
message,
|
message,
|
||||||
@@ -17,32 +19,53 @@ function CustomComponent({
|
|||||||
}) {
|
}) {
|
||||||
const meta = thread.getMessagesMetadata(message);
|
const meta = thread.getMessagesMetadata(message);
|
||||||
const seenState = meta?.firstSeenState;
|
const seenState = meta?.firstSeenState;
|
||||||
const customComponent = seenState?.values.ui
|
const customComponents = seenState?.values.ui
|
||||||
?.slice()
|
?.slice()
|
||||||
.reverse()
|
.filter(({ additional_kwargs }) =>
|
||||||
.find(
|
!additional_kwargs.message_id
|
||||||
({ additional_kwargs }) =>
|
? additional_kwargs.run_id === seenState.metadata?.run_id
|
||||||
additional_kwargs.run_id === seenState.metadata?.run_id,
|
: additional_kwargs.message_id === message.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!customComponent) {
|
if (!customComponents?.length) return null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={message.id}>
|
<Fragment key={message.id}>
|
||||||
{customComponent && (
|
{customComponents.map((customComponent) => (
|
||||||
<LoadExternalComponent
|
<LoadExternalComponent
|
||||||
assistantId="agent"
|
key={customComponent.id}
|
||||||
stream={thread}
|
stream={thread}
|
||||||
message={customComponent}
|
message={customComponent}
|
||||||
meta={{ ui: customComponent }}
|
meta={{ ui: customComponent }}
|
||||||
/>
|
/>
|
||||||
)}
|
))}
|
||||||
</div>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseAnthropicStreamedToolCalls(
|
||||||
|
content: MessageContentComplex[],
|
||||||
|
): AIMessage["tool_calls"] {
|
||||||
|
const toolCallContents = content.filter((c) => c.type === "tool_use" && c.id);
|
||||||
|
|
||||||
|
return toolCallContents.map((tc) => {
|
||||||
|
const toolCall = tc as Record<string, any>;
|
||||||
|
let json: Record<string, any> = {};
|
||||||
|
if (toolCall?.input) {
|
||||||
|
try {
|
||||||
|
json = parsePartialJson(toolCall.input) ?? {};
|
||||||
|
} catch {
|
||||||
|
// Pass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
name: toolCall.name ?? "",
|
||||||
|
id: toolCall.id ?? "",
|
||||||
|
args: json,
|
||||||
|
type: "tool_call",
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function AssistantMessage({
|
export function AssistantMessage({
|
||||||
message,
|
message,
|
||||||
isLoading,
|
isLoading,
|
||||||
@@ -57,29 +80,41 @@ export function AssistantMessage({
|
|||||||
const thread = useStreamContext();
|
const thread = useStreamContext();
|
||||||
const meta = thread.getMessagesMetadata(message);
|
const meta = thread.getMessagesMetadata(message);
|
||||||
const parentCheckpoint = meta?.firstSeenState?.parent_checkpoint;
|
const parentCheckpoint = meta?.firstSeenState?.parent_checkpoint;
|
||||||
|
const anthropicStreamedToolCalls = Array.isArray(message.content)
|
||||||
|
? parseAnthropicStreamedToolCalls(message.content)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const hasToolCalls =
|
const hasToolCalls =
|
||||||
"tool_calls" in message &&
|
"tool_calls" in message &&
|
||||||
message.tool_calls &&
|
message.tool_calls &&
|
||||||
message.tool_calls.length > 0;
|
message.tool_calls.length > 0;
|
||||||
|
const toolCallsHaveContents =
|
||||||
|
hasToolCalls &&
|
||||||
|
message.tool_calls?.some(
|
||||||
|
(tc) => tc.args && Object.keys(tc.args).length > 0,
|
||||||
|
);
|
||||||
|
const hasAnthropicToolCalls = !!anthropicStreamedToolCalls?.length;
|
||||||
const isToolResult = message.type === "tool";
|
const isToolResult = message.type === "tool";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-start mr-auto gap-2 group">
|
<div className="flex items-start mr-auto gap-2 group">
|
||||||
<Avatar>
|
|
||||||
<AvatarFallback>A</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
{isToolResult ? (
|
{isToolResult ? (
|
||||||
<ToolResult message={message} />
|
<ToolResult message={message} />
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
{hasToolCalls && <ToolCalls toolCalls={message.tool_calls} />}
|
|
||||||
<CustomComponent message={message} thread={thread} />
|
|
||||||
{contentString.length > 0 && (
|
{contentString.length > 0 && (
|
||||||
<div className="rounded-2xl bg-muted px-4 py-2">
|
<div className="py-1">
|
||||||
<MarkdownText>{contentString}</MarkdownText>
|
<MarkdownText>{contentString}</MarkdownText>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{(hasToolCalls && toolCallsHaveContents && (
|
||||||
|
<ToolCalls toolCalls={message.tool_calls} />
|
||||||
|
)) ||
|
||||||
|
(hasAnthropicToolCalls && (
|
||||||
|
<ToolCalls toolCalls={anthropicStreamedToolCalls} />
|
||||||
|
)) ||
|
||||||
|
(hasToolCalls && <ToolCalls toolCalls={message.tool_calls} />)}
|
||||||
|
<CustomComponent message={message} thread={thread} />
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex gap-2 items-center mr-auto transition-opacity",
|
"flex gap-2 items-center mr-auto transition-opacity",
|
||||||
@@ -108,9 +143,6 @@ export function AssistantMessage({
|
|||||||
export function AssistantMessageLoading() {
|
export function AssistantMessageLoading() {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-start mr-auto gap-2">
|
<div className="flex items-start mr-auto gap-2">
|
||||||
<Avatar>
|
|
||||||
<AvatarFallback>A</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
<div className="flex items-center gap-1 rounded-2xl bg-muted px-4 py-2 h-8">
|
<div className="flex items-center gap-1 rounded-2xl bg-muted px-4 py-2 h-8">
|
||||||
<div className="w-1.5 h-1.5 rounded-full bg-foreground/50 animate-[pulse_1.5s_ease-in-out_infinite]"></div>
|
<div className="w-1.5 h-1.5 rounded-full bg-foreground/50 animate-[pulse_1.5s_ease-in-out_infinite]"></div>
|
||||||
<div className="w-1.5 h-1.5 rounded-full bg-foreground/50 animate-[pulse_1.5s_ease-in-out_0.5s_infinite]"></div>
|
<div className="w-1.5 h-1.5 rounded-full bg-foreground/50 animate-[pulse_1.5s_ease-in-out_0.5s_infinite]"></div>
|
||||||
|
|||||||
@@ -84,7 +84,9 @@ export function HumanMessage({
|
|||||||
onSubmit={handleSubmitEdit}
|
onSubmit={handleSubmitEdit}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-right py-1">{contentString}</p>
|
<p className="text-right px-4 py-2 rounded-3xl bg-muted">
|
||||||
|
{contentString}
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export function ToolCalls({
|
|||||||
if (!toolCalls || toolCalls.length === 0) return null;
|
if (!toolCalls || toolCalls.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4 w-full max-w-4xl">
|
||||||
{toolCalls.map((tc, idx) => {
|
{toolCalls.map((tc, idx) => {
|
||||||
const args = tc.args as Record<string, any>;
|
const args = tc.args as Record<string, any>;
|
||||||
const hasArgs = Object.keys(args).length > 0;
|
const hasArgs = Object.keys(args).length > 0;
|
||||||
|
|||||||
16
src/hooks/useMediaQuery.tsx
Normal file
16
src/hooks/useMediaQuery.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
export function useMediaQuery(query: string) {
|
||||||
|
const [matches, setMatches] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const media = window.matchMedia(query);
|
||||||
|
setMatches(media.matches);
|
||||||
|
|
||||||
|
const listener = (e: MediaQueryListEvent) => setMatches(e.matches);
|
||||||
|
media.addEventListener("change", listener);
|
||||||
|
return () => media.removeEventListener("change", listener);
|
||||||
|
}, [query]);
|
||||||
|
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
@@ -118,9 +118,11 @@
|
|||||||
* {
|
* {
|
||||||
@apply border-border outline-ring/50;
|
@apply border-border outline-ring/50;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--chart-1: 12 76% 61%;
|
--chart-1: 12 76% 61%;
|
||||||
--chart-2: 173 58% 39%;
|
--chart-2: 173 58% 39%;
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import React, { createContext, useContext, ReactNode, useState } from "react";
|
import React, { createContext, useContext, ReactNode, useState } from "react";
|
||||||
import { useStream } from "@langchain/langgraph-sdk/react";
|
import { useStream } from "@langchain/langgraph-sdk/react";
|
||||||
import { type Message } from "@langchain/langgraph-sdk";
|
import { type Message } from "@langchain/langgraph-sdk";
|
||||||
import type {
|
import {
|
||||||
UIMessage,
|
uiMessageReducer,
|
||||||
RemoveUIMessage,
|
type UIMessage,
|
||||||
|
type RemoveUIMessage,
|
||||||
} from "@langchain/langgraph-sdk/react-ui";
|
} from "@langchain/langgraph-sdk/react-ui";
|
||||||
import { useQueryParam, StringParam } from "use-query-params";
|
import { useQueryParam, StringParam } from "use-query-params";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
@@ -24,7 +25,7 @@ const useTypedStream = useStream<
|
|||||||
messages?: Message[] | Message | string;
|
messages?: Message[] | Message | string;
|
||||||
ui?: (UIMessage | RemoveUIMessage)[] | UIMessage | RemoveUIMessage;
|
ui?: (UIMessage | RemoveUIMessage)[] | UIMessage | RemoveUIMessage;
|
||||||
};
|
};
|
||||||
CustomUpdateType: UIMessage | RemoveUIMessage;
|
CustomEventType: UIMessage | RemoveUIMessage;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
@@ -53,6 +54,12 @@ const StreamSession = ({
|
|||||||
apiKey: apiKey ?? undefined,
|
apiKey: apiKey ?? undefined,
|
||||||
assistantId,
|
assistantId,
|
||||||
threadId: threadId ?? null,
|
threadId: threadId ?? null,
|
||||||
|
onCustomEvent: (event, options) => {
|
||||||
|
options.mutate((prev) => {
|
||||||
|
const ui = uiMessageReducer(prev.ui ?? [], event);
|
||||||
|
return { ...prev, ui };
|
||||||
|
});
|
||||||
|
},
|
||||||
onThreadId: (id) => {
|
onThreadId: (id) => {
|
||||||
setThreadId(id);
|
setThreadId(id);
|
||||||
// Refetch threads list when thread ID changes.
|
// Refetch threads list when thread ID changes.
|
||||||
@@ -89,16 +96,16 @@ export const StreamProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
if (!apiUrl || !assistantId) {
|
if (!apiUrl || !assistantId) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center min-h-screen w-full p-4">
|
<div className="flex items-center justify-center min-h-screen w-full p-4">
|
||||||
<div className="animate-in fade-in-0 zoom-in-95 flex flex-col border bg-background shadow-lg rounded-lg max-w-2xl">
|
<div className="animate-in fade-in-0 zoom-in-95 flex flex-col border bg-background shadow-lg rounded-lg max-w-3xl">
|
||||||
<div className="flex flex-col gap-2 mt-14 p-6 border-b">
|
<div className="flex flex-col gap-2 mt-14 p-6 border-b">
|
||||||
<div className="flex items-start flex-col gap-2">
|
<div className="flex items-start flex-col gap-2">
|
||||||
<LangGraphLogoSVG className="h-7" />
|
<LangGraphLogoSVG className="h-7" />
|
||||||
<h1 className="text-xl font-semibold tracking-tight">
|
<h1 className="text-xl font-semibold tracking-tight">
|
||||||
LangGraph Chat
|
Chat LangGraph
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
Welcome to LangGraph Chat! Before you get started, you need to
|
Welcome to Chat LangGraph! Before you get started, you need to
|
||||||
enter the URL of the deployment and the assistant / graph ID.
|
enter the URL of the deployment and the assistant / graph ID.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
darkMode: ["class"],
|
darkMode: ["class"],
|
||||||
content: ["./index.html", "./src/**/*.{ts,tsx,js,jsx}"],
|
content: [
|
||||||
|
"./index.html",
|
||||||
|
"./src/**/*.{ts,tsx,js,jsx}",
|
||||||
|
"./agent/**/*.{ts,tsx,js,jsx}",
|
||||||
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
borderRadius: {
|
borderRadius: {
|
||||||
|
|||||||
Reference in New Issue
Block a user