From 4bb24a126b2cc5c03bbdd75f7496749ac640c4d1 Mon Sep 17 00:00:00 2001 From: Ian Macartney Date: Fri, 9 May 2025 03:36:42 -0700 Subject: [PATCH 01/86] expose functions for playground --- src/client/playground.ts | 175 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 src/client/playground.ts diff --git a/src/client/playground.ts b/src/client/playground.ts new file mode 100644 index 00000000..efa4926d --- /dev/null +++ b/src/client/playground.ts @@ -0,0 +1,175 @@ +import { + paginationOptsValidator, + queryGeneric, + mutationGeneric, + actionGeneric, +} from "convex/server"; +import { vThreadDoc, type Agent } from "./index"; +import type { RunQueryCtx, UseApi } from "./types"; +import type { ToolSet } from "ai"; +import { v } from "convex/values"; +import { Mounts } from "../component/_generated/api"; +import { + paginationResultValidator, + vContextOptions, + vMessage, +} from "../validators"; +import { assert } from "convex-helpers"; + +// Playground API definition +export function definePlaygroundAPI( + component: UseApi, + { agents }: { agents: Agent[] } +) { + // Map agent name to instance + const agentMap: Record> = Object.fromEntries( + agents.map((agent) => [agent.options.name, agent]) + ); + + async function validateApiKey(ctx: RunQueryCtx, apiKey: string) { + await ctx.runQuery(component.apiKeys.validate, { apiKey }); + } + + // List all agents + const listAgents = queryGeneric({ + args: { + apiKey: v.string(), + }, + handler: async (ctx, args) => { + await validateApiKey(ctx, args.apiKey); + return Object.keys(agentMap); + }, + returns: v.array(v.string()), + }); + + const listUsers = queryGeneric({ + args: { + apiKey: v.string(), + paginationOpts: paginationOptsValidator, + }, + handler: async (ctx, args) => { + await validateApiKey(ctx, args.apiKey); + return ctx.runQuery(component.users.listUsersWithThreads, { + paginationOpts: args.paginationOpts, + }); + }, + returns: paginationResultValidator(v.string()), + }); + + // List threads for a user (query) + const listThreads = queryGeneric({ + args: { + apiKey: v.string(), + userId: v.string(), + paginationOpts: paginationOptsValidator, + }, + handler: async (ctx, args) => { + await validateApiKey(ctx, args.apiKey); + return ctx.runQuery(component.threads.getThreadsByUserId, { + userId: args.userId, + paginationOpts: args.paginationOpts, + order: "desc", + }); + }, + returns: paginationResultValidator(vThreadDoc), + }); + + // List messages for a thread (query) + const listMessages = queryGeneric({ + args: { + apiKey: v.string(), + threadId: v.string(), + paginationOpts: paginationOptsValidator, + }, + handler: async (ctx, args) => { + await validateApiKey(ctx, args.apiKey); + return ctx.runQuery(component.messages.getThreadMessages, { + threadId: args.threadId, + paginationOpts: args.paginationOpts, + order: "desc", + statuses: ["success", "failed", "pending"], + }); + }, + }); + + // Create a thread (mutation) + const createThread = mutationGeneric({ + args: { + apiKey: v.string(), + agentName: v.string(), + userId: v.string(), + title: v.optional(v.string()), + summary: v.optional(v.string()), + }, + handler: async (ctx, args) => { + await validateApiKey(ctx, args.apiKey); + const agent = agentMap[args.agentName]; + if (!agent) throw new Error(`Unknown agent: ${args.agentName}`); + return agent.createThread(ctx, { + userId: args.userId, + title: args.title, + summary: args.summary, + }); + }, + }); + + // Send a message (action) + const sendMessage = actionGeneric({ + args: { + apiKey: v.string(), + agentName: v.string(), + userId: v.string(), + threadId: v.string(), + prompt: v.optional(v.string()), + messages: v.optional(v.array(vMessage)), + // Add more args as needed + }, + handler: async (ctx, args) => { + await validateApiKey(ctx, args.apiKey); + const { threadId, userId, messages, prompt, agentName } = args; + const agent = agentMap[agentName]; + if (!agent) throw new Error(`Unknown agent: ${agentName}`); + const { thread } = await agent.continueThread(ctx, { threadId, userId }); + // Prefer messages if provided, else prompt + assert(messages || prompt, "Must provide either messages or prompt"); + assert(!messages || !prompt, "Provide messages or prompt, not both"); + const result = await thread.generateText({ prompt, messages }); + return result; + }, + }); + + // Fetch prompt context (action) + const fetchPromptContext = actionGeneric({ + args: { + apiKey: v.string(), + agentName: v.string(), + userId: v.optional(v.string()), + threadId: v.optional(v.string()), + messages: v.array(vMessage), + contextOptions: vContextOptions, + beforeMessageId: v.optional(v.string()), + }, + handler: async (ctx, args) => { + await validateApiKey(ctx, args.apiKey); + const agent = agentMap[args.agentName]; + if (!agent) throw new Error(`Unknown agent: ${args.agentName}`); + return agent.fetchContextMessages(ctx, { + userId: args.userId, + threadId: args.threadId, + messages: args.messages, + contextOptions: args.contextOptions, + beforeMessageId: args.beforeMessageId, + }); + }, + }); + + return { + listUsers, + listThreads, + listMessages, + listAgents, + createThread, + sendMessage, + fetchPromptContext, + }; +} From cc4cf8abeaf1b4153f44727c534c2e487c51c63b Mon Sep 17 00:00:00 2001 From: Ian Macartney Date: Fri, 9 May 2025 03:55:58 -0700 Subject: [PATCH 02/86] allow mapping user names --- src/client/playground.ts | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/client/playground.ts b/src/client/playground.ts index efa4926d..1bc09c52 100644 --- a/src/client/playground.ts +++ b/src/client/playground.ts @@ -3,6 +3,8 @@ import { queryGeneric, mutationGeneric, actionGeneric, + GenericDataModel, + GenericQueryCtx, } from "convex/server"; import { vThreadDoc, type Agent } from "./index"; import type { RunQueryCtx, UseApi } from "./types"; @@ -17,9 +19,18 @@ import { import { assert } from "convex-helpers"; // Playground API definition -export function definePlaygroundAPI( +export function definePlaygroundAPI( component: UseApi, - { agents }: { agents: Agent[] } + { + agents, + userNameLookup, + }: { + agents: Agent[]; + userNameLookup?: ( + ctx: GenericQueryCtx, + userId: string + ) => string | Promise; + } ) { // Map agent name to instance const agentMap: Record> = Object.fromEntries( @@ -49,9 +60,17 @@ export function definePlaygroundAPI( }, handler: async (ctx, args) => { await validateApiKey(ctx, args.apiKey); - return ctx.runQuery(component.users.listUsersWithThreads, { + const users = await ctx.runQuery(component.users.listUsersWithThreads, { paginationOpts: args.paginationOpts, }); + return { + ...users, + page: userNameLookup + ? await Promise.all( + users.page.map((userId) => userNameLookup(ctx, userId)) + ) + : users.page, + }; }, returns: paginationResultValidator(v.string()), }); From a4ac8b562d888f2c990f92102f903407d7749351 Mon Sep 17 00:00:00 2001 From: Ian Macartney Date: Fri, 9 May 2025 03:56:28 -0700 Subject: [PATCH 03/86] add playground to package route --- package.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/package.json b/package.json index 2101bfa0..19ed0312 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,13 @@ "default": "./dist/commonjs/client/index.js" } }, + "./playground": { + "import": { + "@convex-dev/component-source": "./src/client/playground.ts", + "types": "./dist/esm/client/playground.d.ts", + "default": "./dist/esm/client/playground.js" + } + }, "./validators": { "import": { "@convex-dev/component-source": "./src/validators.ts", From 149c75795140246e8091926d3c2ee0123650a839 Mon Sep 17 00:00:00 2001 From: Ian Macartney Date: Fri, 9 May 2025 03:56:43 -0700 Subject: [PATCH 04/86] expose playground in example --- example/convex/_generated/api.d.ts | 2 ++ example/convex/example.ts | 4 ++-- example/convex/playground.ts | 23 +++++++++++++++++++++++ src/client/playground.ts | 6 ++++-- 4 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 example/convex/playground.ts diff --git a/example/convex/_generated/api.d.ts b/example/convex/_generated/api.d.ts index 2ecfd486..f84ac63f 100644 --- a/example/convex/_generated/api.d.ts +++ b/example/convex/_generated/api.d.ts @@ -12,6 +12,7 @@ import type * as example from "../example.js"; import type * as http from "../http.js"; import type * as ideaAgents from "../ideaAgents.js"; import type * as ideas from "../ideas.js"; +import type * as playground from "../playground.js"; import type * as weather from "../weather.js"; import type { @@ -33,6 +34,7 @@ declare const fullApi: ApiFromModules<{ http: typeof http; ideaAgents: typeof ideaAgents; ideas: typeof ideas; + playground: typeof playground; weather: typeof weather; }>; declare const fullApiWithMounts: typeof fullApi; diff --git a/example/convex/example.ts b/example/convex/example.ts index 9308bc37..cfcfd162 100644 --- a/example/convex/example.ts +++ b/example/convex/example.ts @@ -15,7 +15,7 @@ const usageHandler: UsageHandler = async (_ctx, args) => { }; // Define an agent similarly to the AI SDK -const weatherAgent = new Agent(components.agent, { +export const weatherAgent = new Agent(components.agent, { name: "Weather Agent", chat: openai.chat("gpt-4o-mini"), textEmbedding: openai.embedding("text-embedding-3-small"), @@ -29,7 +29,7 @@ const weatherAgent = new Agent(components.agent, { usageHandler, }); -const fashionAgent = new Agent(components.agent, { +export const fashionAgent = new Agent(components.agent, { name: "Fashion Agent", chat: openai.chat("gpt-4o-mini"), textEmbedding: openai.embedding("text-embedding-3-small"), diff --git a/example/convex/playground.ts b/example/convex/playground.ts new file mode 100644 index 00000000..65d4d239 --- /dev/null +++ b/example/convex/playground.ts @@ -0,0 +1,23 @@ +import { definePlaygroundAPI } from "@convex-dev/agent/playground"; +import { components } from "./_generated/api"; +import { weatherAgent, fashionAgent } from "./example"; + +/** + * Here we expose the API so the frontend can access it. + * Authorization is handled by passing up an apiKey that can be generated + * on the dashboard or via CLI via: + * ``` + * npx convex run --component agent apiKeys:issue + * ``` + */ +export const { + listAgents, + listUsers, + listThreads, + listMessages, + createThread, + generateText, + fetchPromptContext, +} = definePlaygroundAPI(components.agent, { + agents: [weatherAgent, fashionAgent], +}); diff --git a/src/client/playground.ts b/src/client/playground.ts index 1bc09c52..8d0e183d 100644 --- a/src/client/playground.ts +++ b/src/client/playground.ts @@ -18,6 +18,8 @@ import { } from "../validators"; import { assert } from "convex-helpers"; +export type PlaygroundAPI = ReturnType; + // Playground API definition export function definePlaygroundAPI( component: UseApi, @@ -133,7 +135,7 @@ export function definePlaygroundAPI( }); // Send a message (action) - const sendMessage = actionGeneric({ + const generateText = actionGeneric({ args: { apiKey: v.string(), agentName: v.string(), @@ -188,7 +190,7 @@ export function definePlaygroundAPI( listMessages, listAgents, createThread, - sendMessage, + generateText, fetchPromptContext, }; } From 7c5f78180d9a3409b88afb5ca421b302fd561dc9 Mon Sep 17 00:00:00 2001 From: Ian Macartney Date: Fri, 9 May 2025 04:29:57 -0700 Subject: [PATCH 05/86] update convex rules.mdc --- .cursor/rules/convex_rules.mdc | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/.cursor/rules/convex_rules.mdc b/.cursor/rules/convex_rules.mdc index 1a1662e4..4693e568 100644 --- a/.cursor/rules/convex_rules.mdc +++ b/.cursor/rules/convex_rules.mdc @@ -1,6 +1,7 @@ --- description: Guidelines and best practices for building Convex projects, including database schema design, queries, mutations, and real-world examples -globs: **/*.{ts,tsx,js,jsx} +globs: **/*.ts,**/*.tsx,**/*.js,**/*.jsx +alwaysApply: false --- # Convex guidelines @@ -85,6 +86,19 @@ globs: **/*.{ts,tsx,js,jsx} }, }); ``` +- Here are the valid Convex types along with their respective validators: + Convex Type | TS/JS type | Example Usage | Validator for argument validation and schemas | Notes | +| ----------- | ------------| -----------------------| -----------------------------------------------| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Id | string | `doc._id` | `v.id(tableName)` | | +| Null | null | `null` | `v.null()` | JavaScript's `undefined` is not a valid Convex value. Functions the return `undefined` or do not return will return `null` when called from a client. Use `null` instead. | +| Int64 | bigint | `3n` | `v.int64()` | Int64s only support BigInts between -2^63 and 2^63-1. Convex supports `bigint`s in most modern browsers. | +| Float64 | number | `3.1` | `v.number()` | Convex supports all IEEE-754 double-precision floating point numbers (such as NaNs). Inf and NaN are JSON serialized as strings. | +| Boolean | boolean | `true` | `v.boolean()` | +| String | string | `"abc"` | `v.string()` | Strings are stored as UTF-8 and must be valid Unicode sequences. Strings must be smaller than the 1MB total size limit when encoded as UTF-8. | +| Bytes | ArrayBuffer | `new ArrayBuffer(8)` | `v.bytes()` | Convex supports first class bytestrings, passed in as `ArrayBuffer`s. Bytestrings must be smaller than the 1MB total size limit for Convex types. | +| Array | Array] | `[1, 3.2, "abc"]` | `v.array(values)` | Arrays can have at most 8192 values. | +| Object | Object | `{a: "abc"}` | `v.object({property: value})` | Convex only supports "plain old JavaScript objects" (objects that do not have a custom prototype). Objects can have at most 1024 entries. Field names must be nonempty and not start with "$" or "_". | +| Record | Record | `{"a": "1", "b": "2"}` | `v.record(keys, values)` | Records are objects at runtime, but can have dynamic keys. Keys must be only ASCII characters, nonempty, and not start with "$" or "_". | ### Function registration - Use `internalQuery`, `internalMutation`, and `internalAction` to register internal functions. These functions are private and aren't part of an app's API. They can only be called by other Convex functions. These functions are always imported from `./_generated/server`. @@ -152,6 +166,9 @@ globs: **/*.{ts,tsx,js,jsx} }, }); ``` + Note: `paginationOpts` is an object with the following properties: + - `numItems`: the maximum number of documents to return (the validator is `v.number()`) + - `cursor`: the cursor to use to fetch the next page of documents (the validator is `v.union(v.string(), v.null())`) - A query that ends in `.paginate()` returns an object that has the following properties: - page (contains an array of documents that you fetches) - isDone (a boolean that represents whether or not this is the last page of documents) @@ -165,7 +182,7 @@ globs: **/*.{ts,tsx,js,jsx} ## Schema guidelines - Always define your schema in `convex/schema.ts`. - Always import the schema definition functions from `convex/server`: -- System fields are automatically added to all documents and are prefixed with an underscore. +- System fields are automatically added to all documents and are prefixed with an underscore. The two system fields that are automatically added to all documents are `_creationTime` which has the validator `v.number()` and `_id` which has the validator `v.id(tableName)`. - Always include all index fields in the index name. For example, if an index is defined as `["field1", "field2"]`, the index name should be "by_field1_and_field2". - Index fields must be queried in the same order they are defined. If you want to be able to query by "field1" then "field2" and by "field2" then "field1", you must create separate indexes. @@ -301,13 +318,13 @@ const messages = await ctx.db # Examples: -## Example: thread-app +## Example: chat-app ### Task ``` -Create a real-time thread application backend with AI responses. The app should: +Create a real-time chat application backend with AI responses. The app should: - Allow creating users with names -- Support multiple thread channels +- Support multiple chat channels - Enable users to send messages to channels - Automatically generate AI responses to user messages - Show recent message history @@ -325,7 +342,7 @@ and limit history display to the 10 most recent messages per channel. ### Analysis 1. Task Requirements Summary: -- Build a real-time thread backend with AI integration +- Build a real-time chat backend with AI integration - Support user creation - Enable channel-based conversations - Store and retrieve messages with proper ordering @@ -414,8 +431,8 @@ Internal Functions: #### package.json ```typescript { - "name": "thread-app", - "description": "This example shows how to build a thread app without authentication.", + "name": "chat-app", + "description": "This example shows how to build a chat app without authentication.", "version": "1.0.0", "dependencies": { "convex": "^1.17.4", From 945522fc69a65fd6f74e09c33442695d3d2ebf5d Mon Sep 17 00:00:00 2001 From: Ian Macartney Date: Fri, 9 May 2025 06:09:38 -0700 Subject: [PATCH 06/86] add playground-chef --- playground-chef/.gitignore | 27 ++ playground-chef/README.md | 28 ++ playground-chef/components.json | 21 + playground-chef/eslint.config.js | 77 ++++ playground-chef/index.html | 16 + playground-chef/package.json | 45 ++ playground-chef/postcss.config.cjs | 6 + playground-chef/src/App.tsx | 424 +++++++++++++++++++ playground-chef/src/SignInForm.tsx | 72 ++++ playground-chef/src/SignOutButton.tsx | 21 + playground-chef/src/components/ui/button.tsx | 56 +++ playground-chef/src/components/ui/select.tsx | 123 ++++++ playground-chef/src/index.css | 49 +++ playground-chef/src/lib/utils.ts | 6 + playground-chef/src/main.tsx | 16 + playground-chef/src/vite-env.d.ts | 1 + playground-chef/tailwind.config.js | 9 + playground-chef/tsconfig.app.json | 29 ++ playground-chef/tsconfig.json | 13 + playground-chef/tsconfig.node.json | 22 + playground-chef/vite.config.ts | 42 ++ 21 files changed, 1103 insertions(+) create mode 100644 playground-chef/.gitignore create mode 100644 playground-chef/README.md create mode 100644 playground-chef/components.json create mode 100644 playground-chef/eslint.config.js create mode 100644 playground-chef/index.html create mode 100644 playground-chef/package.json create mode 100644 playground-chef/postcss.config.cjs create mode 100644 playground-chef/src/App.tsx create mode 100644 playground-chef/src/SignInForm.tsx create mode 100644 playground-chef/src/SignOutButton.tsx create mode 100644 playground-chef/src/components/ui/button.tsx create mode 100644 playground-chef/src/components/ui/select.tsx create mode 100644 playground-chef/src/index.css create mode 100644 playground-chef/src/lib/utils.ts create mode 100644 playground-chef/src/main.tsx create mode 100644 playground-chef/src/vite-env.d.ts create mode 100644 playground-chef/tailwind.config.js create mode 100644 playground-chef/tsconfig.app.json create mode 100644 playground-chef/tsconfig.json create mode 100644 playground-chef/tsconfig.node.json create mode 100644 playground-chef/vite.config.ts diff --git a/playground-chef/.gitignore b/playground-chef/.gitignore new file mode 100644 index 00000000..fe60c1c6 --- /dev/null +++ b/playground-chef/.gitignore @@ -0,0 +1,27 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# Ignored for the template, you probably want to remove it: +package-lock.json \ No newline at end of file diff --git a/playground-chef/README.md b/playground-chef/README.md new file mode 100644 index 00000000..8b3ed29e --- /dev/null +++ b/playground-chef/README.md @@ -0,0 +1,28 @@ +# Chat Admin Interface Implementation + +This is a project built with [Chef](https://chef.convex.dev) using [Convex](https://convex.dev) as its backend. + +This project is connected to the Convex deployment named [`pastel-curlew-479`](https://dashboard.convex.dev/d/pastel-curlew-479). + +## Project structure + +The frontend code is in the `app` directory and is built with [Vite](https://vitejs.dev/). + +The backend code is in the `convex` directory. + +`npm run dev` will start the frontend and backend servers. + +## App authentication + +Chef apps use [Convex Auth](https://auth.convex.dev/) with Anonymous auth for easy sign in. You may wish to change this before deploying your app. + +## Developing and deploying your app + +Check out the [Convex docs](https://docs.convex.dev/) for more information on how to develop with Convex. +* If you're new to Convex, the [Overview](https://docs.convex.dev/understanding/) is a good place to start +* Check out the [Hosting and Deployment](https://docs.convex.dev/production/) docs for how to deploy your app +* Read the [Best Practices](https://docs.convex.dev/understanding/best-practices/) guide for tips on how to improve you app further + +## HTTP API + +User-defined http routes are defined in the `convex/router.ts` file. We split these routes into a separate file from `convex/http.ts` to allow us to prevent the LLM from modifying the authentication routes. diff --git a/playground-chef/components.json b/playground-chef/components.json new file mode 100644 index 00000000..6d3998d1 --- /dev/null +++ b/playground-chef/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/index.css", + "baseColor": "slate", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} diff --git a/playground-chef/eslint.config.js b/playground-chef/eslint.config.js new file mode 100644 index 00000000..dc2e2195 --- /dev/null +++ b/playground-chef/eslint.config.js @@ -0,0 +1,77 @@ +import js from "@eslint/js"; +import globals from "globals"; +import reactHooks from "eslint-plugin-react-hooks"; +import reactRefresh from "eslint-plugin-react-refresh"; +import tseslint from "typescript-eslint"; + +export default tseslint.config( + { + ignores: [ + "dist", + "eslint.config.js", + "convex/_generated", + "postcss.config.js", + "tailwind.config.js", + "vite.config.ts", + ], + }, + { + extends: [ + js.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + ], + files: ["**/*.{ts,tsx}"], + languageOptions: { + ecmaVersion: 2020, + globals: { + ...globals.browser, + ...globals.node, + }, + parserOptions: { + project: [ + "./tsconfig.node.json", + "./tsconfig.app.json", + "./convex/tsconfig.json", + ], + }, + }, + plugins: { + "react-hooks": reactHooks, + "react-refresh": reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + "react-refresh/only-export-components": [ + "warn", + { allowConstantExport: true }, + ], + // All of these overrides ease getting into + // TypeScript, and can be removed for stricter + // linting down the line. + + // Only warn on unused variables, and ignore variables starting with `_` + "@typescript-eslint/no-unused-vars": [ + "warn", + { varsIgnorePattern: "^_", argsIgnorePattern: "^_" }, + ], + + // Allow escaping the compiler + "@typescript-eslint/ban-ts-comment": "error", + + // Allow explicit `any`s + "@typescript-eslint/no-explicit-any": "off", + + // START: Allow implicit `any`s + "@typescript-eslint/no-unsafe-argument": "off", + "@typescript-eslint/no-unsafe-assignment": "off", + "@typescript-eslint/no-unsafe-call": "off", + "@typescript-eslint/no-unsafe-member-access": "off", + "@typescript-eslint/no-unsafe-return": "off", + // END: Allow implicit `any`s + + // Allow async functions without await + // for consistency (esp. Convex `handler`s) + "@typescript-eslint/require-await": "off", + }, + }, +); diff --git a/playground-chef/index.html b/playground-chef/index.html new file mode 100644 index 00000000..f587b5ca --- /dev/null +++ b/playground-chef/index.html @@ -0,0 +1,16 @@ + + + + + + + + Chef + + + + +
+ + + diff --git a/playground-chef/package.json b/playground-chef/package.json new file mode 100644 index 00000000..d2dc267f --- /dev/null +++ b/playground-chef/package.json @@ -0,0 +1,45 @@ +{ + "name": "flex-template", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --open", + "lint": "tsc -p . -noEmit --pretty false && vite build" + }, + "dependencies": { + "@monaco-editor/react": "^4.7.0", + "@radix-ui/react-dropdown-menu": "^2.1.14", + "@radix-ui/react-icons": "^1.3.2", + "@radix-ui/react-select": "^2.2.4", + "@radix-ui/react-slot": "^1.2.2", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "dayjs": "^1.11.13", + "lucide-react": "^0.508.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "sonner": "^2.0.3", + "tailwind-merge": "^3.1.0" + }, + "devDependencies": { + "@eslint/js": "^9.21.0", + "@types/node": "^22.13.10", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "~10", + "dotenv": "^16.4.7", + "eslint": "^9.21.0", + "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-refresh": "^0.4.19", + "globals": "^15.15.0", + "npm-run-all": "^4.1.5", + "postcss": "~8", + "prettier": "^3.5.3", + "tailwindcss": "~3", + "typescript": "~5.7.2", + "typescript-eslint": "^8.24.1", + "vite": "^6.2.0" + } +} diff --git a/playground-chef/postcss.config.cjs b/playground-chef/postcss.config.cjs new file mode 100644 index 00000000..12a703d9 --- /dev/null +++ b/playground-chef/postcss.config.cjs @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/playground-chef/src/App.tsx b/playground-chef/src/App.tsx new file mode 100644 index 00000000..d04c65af --- /dev/null +++ b/playground-chef/src/App.tsx @@ -0,0 +1,424 @@ +import { useState } from "react"; +import dayjs from "dayjs"; +import relativeTime from "dayjs/plugin/relativeTime"; +import Editor from "@monaco-editor/react"; +import { Button } from "./components/ui/button"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "./components/ui/select"; + +dayjs.extend(relativeTime); + +interface User { + id: string; + name: string; +} + +interface Agent { + id: string; + name: string; +} + +interface Tool { + id: string; + name: string; + description: string; +} + +interface Thread { + id: string; + title: string; + subtitle: string; + latestMessage: string; + createdAt: string; + lastMessageAt: string; +} + +interface Message { + id: string; + content: string; + type: "text" | "tool_call" | "tool_response"; + sender: { + type: "user" | "bot" | "system"; + name: string; + }; + timestamp: string; + duration: number; + toolCall?: { + tool: string; + args: Record; + }; + toolResponse?: { + tool: string; + result: Record; + }; +} + +interface ContextMessage { + id: string; + content: string; + vectorSearchRank: number | null; + textSearchRank: number | null; +} + +// Example data +const USERS: User[] = [ + { id: "1", name: "Alice Johnson" }, + { id: "2", name: "Bob Smith" }, +]; + +const AGENTS: Agent[] = [ + { id: "1", name: "Sales Assistant" }, + { id: "2", name: "Support Agent" }, +]; + +const TOOLS: Tool[] = [ + { id: "getPricing", name: "Get Pricing", description: "Fetch product pricing" }, + { id: "getFeatures", name: "Get Features", description: "List product features" }, +]; + +const THREADS: Thread[] = [ + { + id: "1", + title: "Product Inquiry", + subtitle: "Discussion about pricing", + latestMessage: "Thank you for the information", + createdAt: "2024-03-18T10:00:00", + lastMessageAt: "2024-03-18T11:30:00", + }, +]; + +const DEFAULT_CONTEXT_OPTIONS = { + maxMessages: 10, + includeSystemMessages: true, + temperature: 0.7, +}; + +const DEFAULT_STORAGE_OPTIONS = { + ttl: 3600, + cacheKey: "default", +}; + +const MESSAGES: Message[] = [ + { + id: "1", + content: "Hi, I need help with pricing", + type: "text", + sender: { type: "user", name: "Alice Johnson" }, + timestamp: "2024-03-18T10:00:00", + duration: 0, + }, + { + id: "2", + content: "Let me check the pricing for you", + type: "tool_call", + sender: { type: "bot", name: "Sales Assistant" }, + timestamp: "2024-03-18T10:01:00", + duration: 0.5, + toolCall: { + tool: "getPricing", + args: { product: "enterprise" }, + }, + }, + { + id: "3", + content: "Here's the pricing information", + type: "tool_response", + sender: { type: "system", name: "Tool Response" }, + timestamp: "2024-03-18T10:01:01", + duration: 0, + toolResponse: { + tool: "getPricing", + result: { price: 999, currency: "USD" }, + }, + }, +]; + +const CONTEXT_MESSAGES: ContextMessage[] = [ + { + id: "1", + content: "Previous pricing discussion", + vectorSearchRank: 1, + textSearchRank: 2, + }, + { + id: "2", + content: "Product features overview", + vectorSearchRank: 2, + textSearchRank: null, + }, + { + id: "3", + content: "Customer preferences", + vectorSearchRank: null, + textSearchRank: 1, + }, +]; + +export default function App() { + const [selectedUser, setSelectedUser] = useState(); + const [selectedThread, setSelectedThread] = useState(); + const [selectedAgent, setSelectedAgent] = useState(); + const [selectedTool, setSelectedTool] = useState(); + const [selectedMessage, setSelectedMessage] = useState(MESSAGES[0]); + const [newMessage, setNewMessage] = useState(""); + const [contextOptions, setContextOptions] = useState( + JSON.stringify(DEFAULT_CONTEXT_OPTIONS, null, 2) + ); + const [storageOptions, setStorageOptions] = useState( + JSON.stringify(DEFAULT_STORAGE_OPTIONS, null, 2) + ); + + return ( +
+ {/* Left Panel */} +
+ + +
+ {THREADS.map((thread) => ( +
setSelectedThread(thread.id)} + > +
{thread.title}
+
{thread.subtitle}
+
+ {thread.latestMessage} +
+
+ Created {dayjs(thread.createdAt).fromNow()} + Last message {dayjs(thread.lastMessageAt).fromNow()} +
+
+ ))} +
+ + +
+ + {/* Middle Panel */} +
+
+ {MESSAGES.map((message) => ( +
setSelectedMessage(message)} + > +
+
+ + {message.sender.type === "user" + ? "👤" + : message.sender.type === "bot" + ? "🤖" + : "⚙️"}{" "} + {message.sender.name} + + {message.type === "tool_call" && ( + + 🧰 Tool Call + + )} + {message.type === "tool_response" && ( + + 📦 Tool Response + + )} +
+ + {message.duration}s + +
+ +
{message.content}
+ + {message.toolCall && ( +
+
+
Tool: {message.toolCall.tool}
+
+ {JSON.stringify(message.toolCall.args, null, 2)} +
+
+
+ )} + + {message.toolResponse && ( +
+
+
+ Response from: {message.toolResponse.tool} +
+
+ {JSON.stringify(message.toolResponse.result, null, 2)} +
+
+
+ )} +
+ ))} +
+
+ + {/* Right Panel */} +
+ {/* Selected Message JSON */} +
+

Selected Message

+
+ +
+
+ + {/* New Message Section */} +
+

New Message

+ +
+ + + + +
+ Context Options +
+ setContextOptions(value || "")} + options={{ + minimap: { enabled: false }, + fontSize: 12, + }} + /> +
+
+ +
+ Storage Options +
+ setStorageOptions(value || "")} + options={{ + minimap: { enabled: false }, + fontSize: 12, + }} + /> +
+
+ +