Compare commits
10 Commits
9e9e1076d5
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 79adc5fffb | |||
| c29b3fbc84 | |||
| 0c1f85996c | |||
| be6f48659f | |||
| 8b8c92f1d6 | |||
| e292cebb1d | |||
| cb523bbe35 | |||
| a42b6a63bb | |||
| d35379afae | |||
| 4b52a65314 |
@@ -0,0 +1,2 @@
|
|||||||
|
.env*
|
||||||
|
.dev.vars*
|
||||||
@@ -33,6 +33,7 @@
|
|||||||
"globals": "^16.0.0",
|
"globals": "^16.0.0",
|
||||||
"typescript": "~5.7.2",
|
"typescript": "~5.7.2",
|
||||||
"typescript-eslint": "^8.26.1",
|
"typescript-eslint": "^8.26.1",
|
||||||
|
"unocss": "66.1.0-beta.13",
|
||||||
"vite": "^6.3.1",
|
"vite": "^6.3.1",
|
||||||
"wrangler": "^4.14.1"
|
"wrangler": "^4.14.1"
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+668
-41
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
|||||||
|
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
||||||
|
import { personalAssistantAgent } from "./agents/personalAssistant.js";
|
||||||
|
import { chefAgent } from "./agents/chef.js";
|
||||||
|
import { Agent } from "./types.js";
|
||||||
|
|
||||||
|
// This declaration is primarily for providing type hints in your code
|
||||||
|
interface Env {
|
||||||
|
OPENROUTER_API_KEY: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
const env: Env;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create OpenRouter instance
|
||||||
|
export const openrouter = createOpenRouter({
|
||||||
|
apiKey: import.meta.env.VITE_OPENROUTER_API_KEY || env.OPENROUTER_API_KEY,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Define the agents by ID
|
||||||
|
export const agentsById: Record<string, Agent> = {
|
||||||
|
"personal-assistant": personalAssistantAgent,
|
||||||
|
chef: chefAgent,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to get an agent by ID
|
||||||
|
export function getAgentById(agentId: string): Agent | undefined {
|
||||||
|
return agentsById[agentId];
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ import { Agent } from "../types.js";
|
|||||||
export const chefAgent: Agent = {
|
export const chefAgent: Agent = {
|
||||||
id: "chef",
|
id: "chef",
|
||||||
name: "Chef",
|
name: "Chef",
|
||||||
modelName: "mistral/ministral-8b",
|
modelName: "mistralai/mistral-nemo",
|
||||||
systemMessage:
|
systemMessage:
|
||||||
"You are a master chef who is famous for his creative and original recipes, but also knows many standard, tried-and-true recipes.",
|
"You are a master chef who is famous for his creative and original recipes, but also knows many standard, tried-and-true recipes.",
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ import { singleSpace } from "../util.js";
|
|||||||
export const personalAssistantAgent: Agent = {
|
export const personalAssistantAgent: Agent = {
|
||||||
id: "personal-assistant",
|
id: "personal-assistant",
|
||||||
name: "Personal Assistant",
|
name: "Personal Assistant",
|
||||||
|
// modelName: "qwen/qwen3-32b:free",
|
||||||
// modelName: "mistral/ministral-8b",
|
// modelName: "mistral/ministral-8b",
|
||||||
modelName: "google/gemini-2.5-flash-preview",
|
// modelName: "google/gemini-2.5-flash-preview",
|
||||||
|
modelName: "mistralai/mistral-nemo",
|
||||||
systemMessage:
|
systemMessage:
|
||||||
singleSpace(`You are a personal assistant Agent who is helpful, friendly, and
|
singleSpace(`You are a personal assistant Agent who is helpful, friendly, and
|
||||||
responsible. You are my liason to other Agents who have specialized
|
responsible. You are my liason to other Agents who have specialized
|
||||||
@@ -77,22 +79,34 @@ export const personalAssistantAgent: Agent = {
|
|||||||
// }
|
// }
|
||||||
// },
|
// },
|
||||||
}),
|
}),
|
||||||
|
echo: tool({
|
||||||
say: tool({
|
description: "Echoes the message.",
|
||||||
description: "Say something.",
|
|
||||||
parameters: jsonSchema<{ message: string }>({
|
parameters: jsonSchema<{ message: string }>({
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
message: {
|
message: {
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "The message to say.",
|
description: "The message to echo.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
execute: async ({ message }: { message: string }) => {
|
|
||||||
console.log(message);
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
// say: tool({
|
||||||
|
// description: "Say something.",
|
||||||
|
// parameters: jsonSchema<{ message: string }>({
|
||||||
|
// type: "object",
|
||||||
|
// properties: {
|
||||||
|
// message: {
|
||||||
|
// type: "string",
|
||||||
|
// description: "The message to say.",
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// }),
|
||||||
|
// execute: async ({ message }: { message: string }) => {
|
||||||
|
// return message;
|
||||||
|
// },
|
||||||
|
// }),
|
||||||
exit: tool({
|
exit: tool({
|
||||||
description: "Exits the conversation.",
|
description: "Exits the conversation.",
|
||||||
parameters: jsonSchema<{ exitCode: number }>({
|
parameters: jsonSchema<{ exitCode: number }>({
|
||||||
@@ -123,7 +137,7 @@ export const personalAssistantAgent: Agent = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
execute: async ({ min, max }: { min: number; max: number }) => {
|
execute: async ({ min = 0, max = 1 }: { min?: number; max?: number }) => {
|
||||||
return Math.random() * (max - min) + min;
|
return Math.random() * (max - min) + min;
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
+107
@@ -0,0 +1,107 @@
|
|||||||
|
import {
|
||||||
|
streamText,
|
||||||
|
generateText,
|
||||||
|
generateObject,
|
||||||
|
type Message,
|
||||||
|
jsonSchema,
|
||||||
|
} from "ai";
|
||||||
|
import { getAgentById, openrouter } from "./agentRegistry.js";
|
||||||
|
|
||||||
|
// Define the tools with explicit type
|
||||||
|
export interface DelegateParams {
|
||||||
|
agentId?: string;
|
||||||
|
prompt?: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EchoParams {
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ToolFunctions {
|
||||||
|
[key: string]: (params: any) => Promise<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tools: ToolFunctions = {
|
||||||
|
delegate: async function ({
|
||||||
|
agentId,
|
||||||
|
prompt,
|
||||||
|
}: DelegateParams): Promise<string> {
|
||||||
|
// Validate required parameters
|
||||||
|
if (!agentId || !prompt) {
|
||||||
|
return "Error: Missing required parameters. Both 'agentId' and 'prompt' are required.";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the target agent
|
||||||
|
const agent = getAgentById(agentId);
|
||||||
|
if (!agent) {
|
||||||
|
return `Error: No such agent: ${agentId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const conversation: { messages: Array<Omit<Message, "id">> } = {
|
||||||
|
messages: [
|
||||||
|
{ role: "system", content: agent.systemMessage },
|
||||||
|
{ role: "user", content: prompt },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
let isConversationDone = false;
|
||||||
|
while (!isConversationDone) {
|
||||||
|
// Generate a response from the agent using the prompt
|
||||||
|
const result = await generateText({
|
||||||
|
model: openrouter(agent.modelName),
|
||||||
|
messages: conversation.messages,
|
||||||
|
tools: agent.tools,
|
||||||
|
});
|
||||||
|
conversation.messages.push({ role: "assistant", content: result.text });
|
||||||
|
const sentimentIsConversationDone = await generateObject<{
|
||||||
|
isConversationDone: boolean;
|
||||||
|
}>({
|
||||||
|
model: openrouter("mistralai/mistral-nemo"),
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: "system",
|
||||||
|
content:
|
||||||
|
"You are a tool to determine whether a conversation is done or should continue with another reply.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: "user",
|
||||||
|
content: conversation.messages
|
||||||
|
.map((message) => `${message.role}: ${message.content}`)
|
||||||
|
.join("\n"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
schema: jsonSchema({
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
isConversationDone: {
|
||||||
|
type: "boolean",
|
||||||
|
description:
|
||||||
|
"Whether the conversation is done or should continue and requires a reply.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
isConversationDone =
|
||||||
|
sentimentIsConversationDone.object.isConversationDone;
|
||||||
|
}
|
||||||
|
|
||||||
|
// const summaryText = await generateSummary();
|
||||||
|
// // Return the agent's response
|
||||||
|
// return summaryText;
|
||||||
|
return conversation.messages
|
||||||
|
.map((message) => `${message.role}: ${message.content}`)
|
||||||
|
.join("\n");
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const errorMessage =
|
||||||
|
error instanceof Error ? error.message : "Unknown error";
|
||||||
|
console.error("Error delegating to agent:", error);
|
||||||
|
return `Error delegating to agent ${agentId}: ${errorMessage}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
echo: async function ({ message }: EchoParams): Promise<string> {
|
||||||
|
return message;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default tools;
|
||||||
+112
@@ -1,3 +1,115 @@
|
|||||||
|
import { formatDataStreamPart, Message } from "ai";
|
||||||
|
import tools from "./tools.js";
|
||||||
|
import { DelegateParams } from "./tools.js";
|
||||||
|
|
||||||
export function singleSpace(str: string) {
|
export function singleSpace(str: string) {
|
||||||
return str.replace(/\s+/g, " ");
|
return str.replace(/\s+/g, " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function processPendingToolCalls(
|
||||||
|
messages: Message[],
|
||||||
|
dataStreamWriter: any
|
||||||
|
) {
|
||||||
|
const lastMessage = messages[messages.length - 1];
|
||||||
|
if (!lastMessage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!lastMessage.parts) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Processing pending tool calls in message:", lastMessage.id);
|
||||||
|
|
||||||
|
/** Execute all the pending tool calls: */
|
||||||
|
lastMessage.parts = await Promise.all(
|
||||||
|
lastMessage.parts?.map(async (part: any) => {
|
||||||
|
// Check if this part has a tool invocation
|
||||||
|
if (part.type === "tool-invocation" && part.toolInvocation) {
|
||||||
|
const toolInvocation = part.toolInvocation as any;
|
||||||
|
console.log("Found tool invocation:", toolInvocation);
|
||||||
|
|
||||||
|
if (toolInvocation.state === "result") {
|
||||||
|
// Check if user approved the tool call
|
||||||
|
if (toolInvocation.result === "yes") {
|
||||||
|
try {
|
||||||
|
// Get the tool function
|
||||||
|
const toolName = toolInvocation.toolName || toolInvocation.name;
|
||||||
|
console.log(`Executing tool: ${toolName}`);
|
||||||
|
|
||||||
|
const toolFunction = tools[toolName as keyof typeof tools];
|
||||||
|
|
||||||
|
if (toolFunction) {
|
||||||
|
// Extract parameters from the tool invocation
|
||||||
|
let parameters = {};
|
||||||
|
try {
|
||||||
|
if (toolInvocation.parameters) {
|
||||||
|
parameters = JSON.parse(toolInvocation.parameters);
|
||||||
|
} else if (toolInvocation.args) {
|
||||||
|
parameters = toolInvocation.args;
|
||||||
|
}
|
||||||
|
console.log(`Tool parameters:`, parameters);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error parsing tool parameters:", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the tool function with the parameters
|
||||||
|
console.log(
|
||||||
|
`Calling tool function with parameters:`,
|
||||||
|
parameters
|
||||||
|
);
|
||||||
|
const result = await toolFunction(parameters as DelegateParams);
|
||||||
|
console.log(`Tool result:`, result);
|
||||||
|
|
||||||
|
// forward updated tool result to the client:
|
||||||
|
dataStreamWriter.write(
|
||||||
|
formatDataStreamPart("tool_result", {
|
||||||
|
toolCallId: toolInvocation.toolCallId,
|
||||||
|
result,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// update the message part:
|
||||||
|
return {
|
||||||
|
...part,
|
||||||
|
toolInvocation: { ...toolInvocation, result },
|
||||||
|
};
|
||||||
|
|
||||||
|
// // Set the result
|
||||||
|
// toolInvocation.result = result;
|
||||||
|
|
||||||
|
// // Add a new message with the tool result
|
||||||
|
// if (result) {
|
||||||
|
// messages.push({
|
||||||
|
// id: `tool-result-${Date.now()}`,
|
||||||
|
// role: "assistant",
|
||||||
|
// content: `Tool Result: ${result}`,
|
||||||
|
// parts: [
|
||||||
|
// {
|
||||||
|
// type: "text",
|
||||||
|
// text: `Tool Result: ${result}`,
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
} else {
|
||||||
|
const errorMsg = `Error: Tool '${toolName}' not found`;
|
||||||
|
console.error(errorMsg);
|
||||||
|
toolInvocation.result = errorMsg;
|
||||||
|
}
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const errorMessage =
|
||||||
|
error instanceof Error ? error.message : "Unknown error";
|
||||||
|
console.error("Error executing tool:", error);
|
||||||
|
toolInvocation.result = `Error executing tool: ${errorMessage}`;
|
||||||
|
}
|
||||||
|
} else if (toolInvocation.result === "no") {
|
||||||
|
toolInvocation.result = "Error: User denied tool call.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return part;
|
||||||
|
}) ?? []
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log("Finished processing tool calls. Updated messages:", messages);
|
||||||
|
}
|
||||||
|
|||||||
+43
-54
@@ -1,77 +1,66 @@
|
|||||||
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
import { streamText, Message, createDataStream } from "ai";
|
||||||
import { streamText } from "ai";
|
|
||||||
import { Hono } from "hono";
|
import { Hono } from "hono";
|
||||||
import { stream } from "hono/streaming";
|
import { stream } from "hono/streaming";
|
||||||
import { personalAssistantAgent } from "./agents/assistant.js";
|
import { processPendingToolCalls } from "./util.js";
|
||||||
import { chefAgent } from "./agents/chef.js";
|
import { agentsById, openrouter } from "./agentRegistry.js";
|
||||||
import { Agent } from "./types.js";
|
|
||||||
|
|
||||||
// This declaration is primarily for providing type hints in your code
|
|
||||||
// and it doesn't directly define the *values* of the environment variables.
|
|
||||||
|
|
||||||
interface Env {
|
|
||||||
OPENROUTER_API_KEY: string;
|
|
||||||
// Add other environment variables here
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
const env: Env;
|
|
||||||
}
|
|
||||||
|
|
||||||
const app = new Hono();
|
const app = new Hono();
|
||||||
|
|
||||||
const openrouter = createOpenRouter({
|
app.post("/api/chat/:agent_id", async (c) => {
|
||||||
apiKey: import.meta.env.VITE_OPENROUTER_API_KEY || env.OPENROUTER_API_KEY,
|
const input: { messages: Message[] } = await c.req.json();
|
||||||
});
|
const agentId = c.req.param("agent_id");
|
||||||
const systemMessage = {
|
const agent = agentsById[agentId];
|
||||||
role: "system",
|
|
||||||
content:
|
|
||||||
"You are a wise old man named Dorf that answers questions succintly.",
|
|
||||||
};
|
|
||||||
|
|
||||||
app.post("/api/chat", async (c) => {
|
|
||||||
const input = await c.req.json();
|
|
||||||
console.log(input);
|
|
||||||
const result = streamText({
|
|
||||||
model: openrouter("mistral/ministral-8b"),
|
|
||||||
messages: [systemMessage, ...input.messages],
|
|
||||||
tools: {},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Mark the response as a v1 data stream:
|
|
||||||
c.header("X-Vercel-AI-Data-Stream", "v1");
|
|
||||||
c.header("Content-Type", "text/plain; charset=utf-8");
|
|
||||||
|
|
||||||
return stream(c, (stream) => stream.pipe(result.toDataStream()));
|
|
||||||
});
|
|
||||||
|
|
||||||
const agentsByName: Record<string, Agent> = {
|
|
||||||
assistant: personalAssistantAgent,
|
|
||||||
chef: chefAgent,
|
|
||||||
};
|
|
||||||
|
|
||||||
app.post("/api/chat/:agent_name", async (c) => {
|
|
||||||
const input = await c.req.json();
|
|
||||||
const agentName = c.req.param("agent_name");
|
|
||||||
const agent = agentsByName[agentName];
|
|
||||||
if (!agent) {
|
if (!agent) {
|
||||||
return c.json({ error: `No such agent: ${agentName}` });
|
c.status(404);
|
||||||
|
return c.json({ error: `No such agent: ${agentId}` });
|
||||||
}
|
}
|
||||||
console.log(input);
|
|
||||||
|
const dataStream = createDataStream({
|
||||||
|
execute: async (dataStreamWriter) => {
|
||||||
|
// dataStreamWriter.writeData('initialized call');
|
||||||
|
|
||||||
|
// Process any pending tool calls in the messages
|
||||||
|
// This modifies the messages array in place
|
||||||
|
await processPendingToolCalls(input.messages, dataStreamWriter);
|
||||||
|
|
||||||
const result = streamText({
|
const result = streamText({
|
||||||
model: openrouter(agent.modelName),
|
model: openrouter(agent.modelName),
|
||||||
|
maxSteps: 5,
|
||||||
messages: [
|
messages: [
|
||||||
{ role: "system", content: agent.systemMessage },
|
{ role: "system", content: agent.systemMessage },
|
||||||
|
...Object.values(agentsById).map((agent) => ({
|
||||||
|
role: "system" as const,
|
||||||
|
content: `Agent ${JSON.stringify({
|
||||||
|
id: agent.id,
|
||||||
|
name: agent.name,
|
||||||
|
description: agent.description,
|
||||||
|
skills: agent.skills,
|
||||||
|
})}`,
|
||||||
|
})),
|
||||||
...input.messages,
|
...input.messages,
|
||||||
],
|
],
|
||||||
tools: agent.tools,
|
tools: agent.tools,
|
||||||
|
onError: (error) => {
|
||||||
|
console.error("Error in streamText:", error);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
result.mergeIntoDataStream(dataStreamWriter);
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
// Error messages are masked by default for security reasons.
|
||||||
|
// If you want to expose the error message to the client, you can do so here:
|
||||||
|
return error instanceof Error ? error.message : String(error);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mark the response as a v1 data stream:
|
// Mark the response as a v1 data stream:
|
||||||
c.header("X-Vercel-AI-Data-Stream", "v1");
|
c.header("X-Vercel-AI-Data-Stream", "v1");
|
||||||
c.header("Content-Type", "text/plain; charset=utf-8");
|
c.header("Content-Type", "text/plain; charset=utf-8");
|
||||||
|
|
||||||
return stream(c, (stream) => stream.pipe(result.toDataStream()));
|
return stream(c, (stream) =>
|
||||||
|
stream.pipe(dataStream.pipeThrough(new TextEncoderStream()))
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default app;
|
export default app;
|
||||||
|
|||||||
+1
-2
@@ -3,8 +3,7 @@ import { createRoot } from "react-dom/client";
|
|||||||
import { RouterProvider, createRouter } from "@tanstack/react-router";
|
import { RouterProvider, createRouter } from "@tanstack/react-router";
|
||||||
// Import the generated route tree
|
// Import the generated route tree
|
||||||
import { routeTree } from "./routeTree.gen";
|
import { routeTree } from "./routeTree.gen";
|
||||||
|
import "virtual:uno.css";
|
||||||
// import App from "./App.tsx";
|
|
||||||
|
|
||||||
// Create a new router instance
|
// Create a new router instance
|
||||||
const router = createRouter({ routeTree });
|
const router = createRouter({ routeTree });
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
:root {
|
|
||||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
|
||||||
line-height: 1.5;
|
|
||||||
font-weight: 400;
|
|
||||||
|
|
||||||
color-scheme: light dark;
|
|
||||||
color: rgba(255, 255, 255, 0.87);
|
|
||||||
background-color: #242424;
|
|
||||||
|
|
||||||
font-synthesis: none;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
font-weight: 500;
|
|
||||||
color: #646cff;
|
|
||||||
text-decoration: inherit;
|
|
||||||
}
|
|
||||||
a:hover {
|
|
||||||
color: #535bf2;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
display: flex;
|
|
||||||
place-items: center;
|
|
||||||
min-width: 320px;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 3.2em;
|
|
||||||
line-height: 1.1;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
padding: 0.6em 1.2em;
|
|
||||||
font-size: 1em;
|
|
||||||
font-weight: 500;
|
|
||||||
font-family: inherit;
|
|
||||||
background-color: #1a1a1a;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: border-color 0.25s;
|
|
||||||
}
|
|
||||||
button:hover {
|
|
||||||
border-color: #646cff;
|
|
||||||
}
|
|
||||||
button:focus,
|
|
||||||
button:focus-visible {
|
|
||||||
outline: 4px auto -webkit-focus-ring-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: light) {
|
|
||||||
:root {
|
|
||||||
color: #213547;
|
|
||||||
background-color: #ffffff;
|
|
||||||
}
|
|
||||||
a:hover {
|
|
||||||
color: #747bff;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import { createRootRoute, Link, Outlet } from "@tanstack/react-router";
|
import { createRootRoute, Link, Outlet } from "@tanstack/react-router";
|
||||||
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
|
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
|
||||||
import "./__root.css";
|
|
||||||
|
|
||||||
export const Route = createRootRoute({
|
export const Route = createRootRoute({
|
||||||
component: () => (
|
component: () => (
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ export const Route = createFileRoute("/chats/$agentName")({
|
|||||||
|
|
||||||
function Chat() {
|
function Chat() {
|
||||||
const { agentName } = Route.useParams();
|
const { agentName } = Route.useParams();
|
||||||
const { messages, input, handleInputChange, handleSubmit } = useChat(
|
const { messages, input, handleInputChange, handleSubmit, addToolResult } =
|
||||||
|
useChat(
|
||||||
/*{
|
/*{
|
||||||
api: "http://localhost:8787/api/chat",
|
api: "http://localhost:8787/api/chat",
|
||||||
}*/ { api: `/api/chat/${agentName}` }
|
}*/ { api: `/api/chat/${agentName}` }
|
||||||
@@ -17,34 +18,61 @@ function Chat() {
|
|||||||
<div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
|
<div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
|
||||||
{messages.map((message) => (
|
{messages.map((message) => (
|
||||||
<div key={message.id} className="whitespace-pre-wrap">
|
<div key={message.id} className="whitespace-pre-wrap">
|
||||||
|
<span className="py-1 px-2 bg-gray-200 rounded-4">
|
||||||
{message.role === "user" ? "User: " : "AI: "}
|
{message.role === "user" ? "User: " : "AI: "}
|
||||||
|
</span>
|
||||||
{message.parts.map((part, i) => {
|
{message.parts.map((part, i) => {
|
||||||
switch (part.type) {
|
switch (part.type) {
|
||||||
case "text":
|
case "text":
|
||||||
return <div key={`${message.id}-${i}`}>{part.text}</div>;
|
return <span key={`${message.id}-${i}`}>{part.text}</span>;
|
||||||
case "tool-invocation":
|
case "tool-invocation":
|
||||||
return (
|
return (
|
||||||
<pre key={`${message.id}-${i}`}>
|
<div key={`${message.id}-${i}`}>
|
||||||
{JSON.stringify(part.toolInvocation, null, 2)}
|
<pre>{JSON.stringify(part.toolInvocation, null, 2)}</pre>
|
||||||
</pre>
|
{part.toolInvocation.state === "call" ? (
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<span>Continue?</span>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
addToolResult({
|
||||||
|
toolCallId: part.toolInvocation.toolCallId,
|
||||||
|
result: "yes",
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Yes
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
addToolResult({
|
||||||
|
toolCallId: part.toolInvocation.toolCallId,
|
||||||
|
result: "no",
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
No
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
case "reasoning":
|
case "reasoning":
|
||||||
return (
|
return (
|
||||||
<div key={`${message.id}-${i}`}>
|
<span key={`${message.id}-${i}`}>
|
||||||
(Reasoning) {part.reasoning}
|
(Reasoning) {part.reasoning}
|
||||||
</div>
|
</span>
|
||||||
);
|
);
|
||||||
case "step-start":
|
case "step-start":
|
||||||
return (
|
return (
|
||||||
<div key={`${message.id}-${i}`}>
|
<span key={`${message.id}-${i}`}>
|
||||||
(Step Start) {JSON.stringify(part)}
|
(Step Start) {JSON.stringify(part)}
|
||||||
</div>
|
</span>
|
||||||
);
|
);
|
||||||
case "source":
|
case "source":
|
||||||
return (
|
return (
|
||||||
<div key={`${message.id}-${i}`}>
|
<span key={`${message.id}-${i}`}>
|
||||||
(Source) {JSON.stringify(part.source)}
|
(Source) {JSON.stringify(part.source)}
|
||||||
</div>
|
</span>
|
||||||
);
|
);
|
||||||
case "file":
|
case "file":
|
||||||
return (
|
return (
|
||||||
@@ -55,9 +83,9 @@ function Chat() {
|
|||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<div key={`${message.id}-${i}`}>
|
<span key={`${message.id}-${i}`}>
|
||||||
(?) {JSON.stringify(part)}
|
(?) {JSON.stringify(part)}
|
||||||
</div>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
#root {
|
|
||||||
max-width: 1280px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 2rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
height: 6em;
|
|
||||||
padding: 1.5em;
|
|
||||||
will-change: filter;
|
|
||||||
transition: filter 300ms;
|
|
||||||
}
|
|
||||||
.logo:hover {
|
|
||||||
filter: drop-shadow(0 0 2em #646cffaa);
|
|
||||||
}
|
|
||||||
.logo.react:hover {
|
|
||||||
filter: drop-shadow(0 0 2em #61dafbaa);
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes logo-spin {
|
|
||||||
from {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
|
||||||
a:nth-of-type(2) .logo {
|
|
||||||
animation: logo-spin infinite 20s linear;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
padding: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.read-the-docs {
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,6 @@ import { createFileRoute } from "@tanstack/react-router";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import reactLogo from "../assets/react.svg";
|
import reactLogo from "../assets/react.svg";
|
||||||
import viteLogo from "/vite.svg";
|
import viteLogo from "/vite.svg";
|
||||||
import "./index.css";
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/")({
|
export const Route = createFileRoute("/")({
|
||||||
component: App,
|
component: App,
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { defineConfig } from "unocss";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
// ...UnoCSS options
|
||||||
|
});
|
||||||
@@ -2,6 +2,7 @@ import { defineConfig } from "vite";
|
|||||||
import react from "@vitejs/plugin-react-swc";
|
import react from "@vitejs/plugin-react-swc";
|
||||||
import { cloudflare } from "@cloudflare/vite-plugin";
|
import { cloudflare } from "@cloudflare/vite-plugin";
|
||||||
import { TanStackRouterVite } from "@tanstack/router-plugin/vite";
|
import { TanStackRouterVite } from "@tanstack/router-plugin/vite";
|
||||||
|
import UnoCSS from "unocss/vite";
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
@@ -9,6 +10,7 @@ export default defineConfig({
|
|||||||
// must go before `react` plugin:
|
// must go before `react` plugin:
|
||||||
TanStackRouterVite({ target: "react", autoCodeSplitting: true }),
|
TanStackRouterVite({ target: "react", autoCodeSplitting: true }),
|
||||||
react(),
|
react(),
|
||||||
|
UnoCSS(),
|
||||||
cloudflare(),
|
cloudflare(),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user