├── apps ├── web │ ├── src │ │ ├── components │ │ │ ├── chat-interface │ │ │ │ ├── index.tsx │ │ │ │ ├── model-selector │ │ │ │ │ └── new-badge.tsx │ │ │ │ └── feedback.tsx │ │ │ ├── canvas │ │ │ │ ├── index.ts │ │ │ │ └── canavas-loading.tsx │ │ │ ├── artifacts │ │ │ │ ├── actions_toolbar │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── code │ │ │ │ │ │ └── PortToLanguage.tsx │ │ │ │ │ └── text │ │ │ │ │ │ ├── TranslateOptions.tsx │ │ │ │ │ │ ├── ReadingLevelOptions.tsx │ │ │ │ │ │ └── LengthOptions.tsx │ │ │ │ ├── CodeRenderer.module.css │ │ │ │ ├── ArtifactLoading.tsx │ │ │ │ ├── header │ │ │ │ │ ├── artifact-title.tsx │ │ │ │ │ ├── navigate-artifact-history.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── TextRenderer.module.css │ │ │ │ └── components │ │ │ │ │ └── CopyText.tsx │ │ │ ├── ui │ │ │ │ ├── header.tsx │ │ │ │ ├── skeleton.tsx │ │ │ │ ├── textarea.tsx │ │ │ │ ├── label.tsx │ │ │ │ ├── progress.tsx │ │ │ │ ├── inline-context-tooltip.tsx │ │ │ │ ├── assistant-ui │ │ │ │ │ ├── attachment-adapters │ │ │ │ │ │ ├── pdf.ts │ │ │ │ │ │ ├── video.ts │ │ │ │ │ │ └── audio.ts │ │ │ │ │ ├── syntax-highlighter.tsx │ │ │ │ │ ├── avatar.tsx │ │ │ │ │ ├── utils │ │ │ │ │ │ └── withDefaults.tsx │ │ │ │ │ └── tooltip-icon-button.tsx │ │ │ │ ├── input.tsx │ │ │ │ ├── toaster.tsx │ │ │ │ ├── checkbox.tsx │ │ │ │ ├── badge.tsx │ │ │ │ ├── tooltip.tsx │ │ │ │ ├── hover-card.tsx │ │ │ │ ├── popover.tsx │ │ │ │ ├── avatar.tsx │ │ │ │ ├── alert.tsx │ │ │ │ ├── password-input.tsx │ │ │ │ ├── resizable.tsx │ │ │ │ ├── button.tsx │ │ │ │ ├── card.tsx │ │ │ │ ├── accordion.tsx │ │ │ │ └── slider.tsx │ │ │ ├── NoSSRWrapper.tsx │ │ │ ├── assistant-select │ │ │ │ ├── edit-delete-dropdown.module.css │ │ │ │ ├── utils.tsx │ │ │ │ ├── color-picker.tsx │ │ │ │ ├── assistant-item.tsx │ │ │ │ └── context-documents │ │ │ │ │ └── uploaded-file.tsx │ │ │ ├── tool-hooks │ │ │ │ ├── AttachmentsToolUI.tsx │ │ │ │ └── LangSmithLinkToolUI.tsx │ │ │ ├── auth │ │ │ │ ├── login │ │ │ │ │ └── actions.ts │ │ │ │ └── signup │ │ │ │ │ ├── actions.ts │ │ │ │ │ └── success │ │ │ │ │ └── index.tsx │ │ │ ├── web-search-results │ │ │ │ └── loading-cards.tsx │ │ │ ├── icons │ │ │ │ ├── svg │ │ │ │ │ ├── TXTIcon.svg │ │ │ │ │ ├── MP4Icon.svg │ │ │ │ │ ├── PDFIcon.svg │ │ │ │ │ └── MP3Icon.svg │ │ │ │ └── magic_pencil.tsx │ │ │ ├── assistant-ui │ │ │ │ └── tooltip-icon-button.tsx │ │ │ └── reflections-dialog │ │ │ │ └── ConfirmClearDialog.tsx │ │ ├── app │ │ │ ├── favicon.ico │ │ │ ├── auth │ │ │ │ ├── signup │ │ │ │ │ ├── success │ │ │ │ │ │ └── page.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── login │ │ │ │ │ └── page.tsx │ │ │ │ ├── signout │ │ │ │ │ └── page.tsx │ │ │ │ ├── callback │ │ │ │ │ └── route.ts │ │ │ │ └── confirm │ │ │ │ │ └── route.ts │ │ │ ├── page.tsx │ │ │ ├── layout.tsx │ │ │ └── api │ │ │ │ ├── store │ │ │ │ ├── delete │ │ │ │ │ ├── route.ts │ │ │ │ │ └── id │ │ │ │ │ │ └── route.ts │ │ │ │ ├── get │ │ │ │ │ └── route.ts │ │ │ │ └── put │ │ │ │ │ └── route.ts │ │ │ │ ├── firecrawl │ │ │ │ └── scrape │ │ │ │ │ └── route.ts │ │ │ │ ├── runs │ │ │ │ ├── share │ │ │ │ │ └── route.ts │ │ │ │ └── feedback │ │ │ │ │ └── route.ts │ │ │ │ └── whisper │ │ │ │ └── audio │ │ │ │ └── route.ts │ │ ├── lib │ │ │ ├── store.ts │ │ │ ├── utils.ts │ │ │ ├── supabase │ │ │ │ ├── client.ts │ │ │ │ ├── verify_user_server.ts │ │ │ │ ├── server.ts │ │ │ │ └── middleware.ts │ │ │ ├── normalize_string.ts │ │ │ ├── cookies.ts │ │ │ └── get_language_template.ts │ │ ├── hooks │ │ │ ├── utils.ts │ │ │ ├── useRuns.tsx │ │ │ ├── useLocalStorage.tsx │ │ │ └── useFeedback.ts │ │ ├── workers │ │ │ └── graph-stream │ │ │ │ ├── streamWorker.types.ts │ │ │ │ ├── streamWorker.ts │ │ │ │ └── stream.worker.ts │ │ ├── middleware.ts │ │ ├── constants.ts │ │ ├── contexts │ │ │ └── UserContext.tsx │ │ └── types.ts │ ├── public │ │ ├── lc_logo.jpg │ │ ├── screenshot.png │ │ └── lg_studio_graph_diagram.png │ ├── turbo.json │ ├── postcss.config.mjs │ ├── next.config.mjs │ ├── ls.vitest.config.ts │ ├── components.json │ ├── .prettierrc │ ├── .gitignore │ ├── tsconfig.json │ ├── .eslintrc.json │ ├── LICENSE │ └── .env.example └── agents │ ├── turbo.json │ ├── src │ ├── summarizer │ │ └── state.ts │ ├── reflection │ │ ├── state.ts │ │ └── prompts.ts │ ├── thread-title │ │ ├── state.ts │ │ └── prompts.ts │ ├── web-search │ │ ├── state.ts │ │ ├── nodes │ │ │ ├── search.ts │ │ │ ├── classify-message.ts │ │ │ └── query-generator.ts │ │ └── index.ts │ └── open-canvas │ │ └── nodes │ │ ├── summarizer.ts │ │ ├── rewrite-artifact │ │ ├── schemas.ts │ │ └── update-meta.ts │ │ ├── generate-artifact │ │ ├── schemas.ts │ │ ├── utils.ts │ │ └── index.ts │ │ ├── generateTitle.ts │ │ ├── reflect.ts │ │ ├── generateFollowup.ts │ │ └── replyToGeneralInput.ts │ ├── .prettierrc │ ├── .gitignore │ ├── tsconfig.json │ ├── .eslintrc.cjs │ └── package.json ├── static └── screenshot.png ├── packages ├── evals │ ├── turbo.json │ ├── src │ │ ├── data │ │ │ ├── codegen.ts │ │ │ └── query_routing.ts │ │ ├── highlights.ts │ │ └── agent.int.test.ts │ ├── .prettierrc │ ├── tsconfig.json │ ├── package.json │ └── .eslintrc.cjs └── shared │ ├── turbo.json │ ├── .prettierrc │ ├── .gitignore │ ├── tsconfig.json │ ├── src │ ├── utils │ │ ├── urls.ts │ │ └── artifacts.ts │ ├── prompts │ │ └── quick-actions.ts │ └── constants.ts │ ├── package.json │ └── .eslintrc.cjs ├── .vscode └── settings.json ├── turbo.json ├── langgraph.json ├── tsconfig.json ├── .gitignore ├── .env.example ├── package.json └── LICENSE /apps/web/src/components/chat-interface/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./thread"; 2 | -------------------------------------------------------------------------------- /static/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sugarforever/blue-canvas/main/static/screenshot.png -------------------------------------------------------------------------------- /apps/web/src/components/canvas/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./canvas"; 2 | export * from "./canavas-loading"; 3 | -------------------------------------------------------------------------------- /apps/web/public/lc_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sugarforever/blue-canvas/main/apps/web/public/lc_logo.jpg -------------------------------------------------------------------------------- /apps/web/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sugarforever/blue-canvas/main/apps/web/src/app/favicon.ico -------------------------------------------------------------------------------- /apps/web/src/components/artifacts/actions_toolbar/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./text"; 2 | export * from "./code"; 3 | -------------------------------------------------------------------------------- /apps/web/public/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sugarforever/blue-canvas/main/apps/web/public/screenshot.png -------------------------------------------------------------------------------- /apps/web/public/lg_studio_graph_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sugarforever/blue-canvas/main/apps/web/public/lg_studio_graph_diagram.png -------------------------------------------------------------------------------- /apps/agents/turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["//"], 3 | "tasks": { 4 | "build": { 5 | "outputs": ["**/dist/**"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/evals/turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["//"], 3 | "tasks": { 4 | "build": { 5 | "outputs": ["**/dist/**"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/shared/turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["//"], 3 | "tasks": { 4 | "build": { 5 | "outputs": ["**/dist/**"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/web/turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["//"], 3 | "tasks": { 4 | "build": { 5 | "outputs": [".next/**", "!.next/cache/**"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/web/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /apps/web/src/lib/store.ts: -------------------------------------------------------------------------------- 1 | export const USER_RULES_STORE_KEY = "rules"; 2 | 3 | export const createNamespace = (assistantId: string) => { 4 | return ["assistant_id", assistantId, "userRules"]; 5 | }; 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "groq", 4 | "langchain", 5 | "langsmith", 6 | "opencanvas", 7 | "Signup", 8 | "sourounding", 9 | "Supabase" 10 | ] 11 | } -------------------------------------------------------------------------------- /apps/web/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /apps/web/src/hooks/utils.ts: -------------------------------------------------------------------------------- 1 | import { Client } from "@langchain/langgraph-sdk"; 2 | 3 | export const createClient = () => { 4 | const apiUrl = process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:3000/api"; 5 | return new Client({ 6 | apiUrl, 7 | }); 8 | }; 9 | -------------------------------------------------------------------------------- /apps/web/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | webpack: (config, { isServer }) => { 4 | if (!isServer) { 5 | config.output.globalObject = 'self'; 6 | } 7 | return config; 8 | }, 9 | }; 10 | 11 | export default nextConfig; 12 | -------------------------------------------------------------------------------- /apps/web/src/components/ui/header.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | 3 | export function TighterText({ 4 | className, 5 | children, 6 | }: { 7 | className?: string; 8 | children: React.ReactNode; 9 | }) { 10 | return

{children}

; 11 | } 12 | -------------------------------------------------------------------------------- /packages/evals/src/data/codegen.ts: -------------------------------------------------------------------------------- 1 | import { HumanMessage } from "@langchain/core/messages"; 2 | 3 | export const CODEGEN_DATA: Record = { 4 | inputs: { 5 | messages: [ 6 | new HumanMessage("Write me code for an LLM agent that does scraping"), 7 | ], 8 | next: "generateArtifact", 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /apps/web/src/app/auth/signup/success/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { UserProvider } from "@/contexts/UserContext"; 4 | import { SignupSuccess } from "@/components/auth/signup/success"; 5 | 6 | export default function Page() { 7 | return ( 8 | 9 | 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /apps/web/src/components/NoSSRWrapper.tsx: -------------------------------------------------------------------------------- 1 | import dynamic from "next/dynamic"; 2 | import React from "react"; 3 | 4 | const NoSSRWrapper: React.FC = (props) => ( 5 | {props.children} 6 | ); 7 | 8 | export default dynamic(() => Promise.resolve(NoSSRWrapper), { 9 | ssr: false, 10 | }); 11 | -------------------------------------------------------------------------------- /apps/web/src/components/assistant-select/edit-delete-dropdown.module.css: -------------------------------------------------------------------------------- 1 | .dropdownContent { 2 | width: 48px !important; 3 | min-width: 48px !important; 4 | padding: 4px !important; 5 | } 6 | 7 | .dropdownContent > [role="menuitem"] { 8 | width: 100% !important; 9 | padding: 4px !important; 10 | display: flex; 11 | justify-content: center; 12 | } 13 | -------------------------------------------------------------------------------- /apps/web/src/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ); 13 | } 14 | 15 | export { Skeleton }; 16 | -------------------------------------------------------------------------------- /apps/web/src/app/auth/login/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Login } from "@/components/auth/login/Login"; 4 | import { Suspense } from "react"; 5 | 6 | export default function Page() { 7 | return ( 8 |
9 | Loading...
}> 10 | 11 | 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /apps/web/src/app/auth/signup/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Signup } from "@/components/auth/signup/Signup"; 4 | import { Suspense } from "react"; 5 | 6 | export default function Page() { 7 | return ( 8 |
9 | Loading...}> 10 | 11 | 12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /apps/web/ls.vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitest/config"; 2 | import path from "path"; 3 | 4 | export default defineConfig({ 5 | test: { 6 | include: ["**/*.eval.?(c|m)[jt]s"], 7 | reporters: ["langsmith/vitest/reporter"], 8 | setupFiles: ["dotenv/config"], 9 | }, 10 | resolve: { 11 | alias: { 12 | "@": path.resolve(__dirname, "./src"), 13 | }, 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /apps/web/src/components/assistant-select/utils.tsx: -------------------------------------------------------------------------------- 1 | import * as Icons from "lucide-react"; 2 | import React from "react"; 3 | 4 | export const getIcon = (iconName?: string) => { 5 | if (iconName && Icons[iconName as keyof typeof Icons]) { 6 | return React.createElement( 7 | Icons[iconName as keyof typeof Icons] as React.ElementType 8 | ); 9 | } 10 | return React.createElement(Icons.User); 11 | }; 12 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "globalDependencies": ["**/.env"], 4 | "tasks": { 5 | "build": { 6 | "dependsOn": ["^build"], 7 | "outputs": ["dist/**", ".next/**", "!.next/cache/**"] 8 | }, 9 | "lint": { 10 | "dependsOn": ["^lint"] 11 | }, 12 | "lint:fix": { 13 | "dependsOn": ["^lint:fix"] 14 | }, 15 | "format": { 16 | "dependsOn": ["^format"] 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /langgraph.json: -------------------------------------------------------------------------------- 1 | { 2 | "node_version": "20", 3 | "dependencies": [ 4 | "." 5 | ], 6 | "graphs": { 7 | "agent": "./apps/agents/src/open-canvas/index.ts:graph", 8 | "reflection": "./apps/agents/src/reflection/index.ts:graph", 9 | "thread_title": "./apps/agents/src/thread-title/index.ts:graph", 10 | "summarizer": "./apps/agents/src/summarizer/index.ts:graph", 11 | "web_search": "./apps/agents/src/web-search/index.ts:graph" 12 | }, 13 | "env": ".env" 14 | } -------------------------------------------------------------------------------- /apps/web/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | } 20 | } -------------------------------------------------------------------------------- /apps/web/src/workers/graph-stream/streamWorker.types.ts: -------------------------------------------------------------------------------- 1 | import { ALL_MODEL_NAMES } from "@opencanvas/shared/models"; 2 | import { CustomModelConfig, GraphInput } from "@opencanvas/shared/types"; 3 | 4 | export interface StreamWorkerMessage { 5 | type: "chunk" | "done" | "error"; 6 | data?: string; 7 | error?: string; 8 | } 9 | 10 | export interface StreamConfig { 11 | threadId: string; 12 | assistantId: string; 13 | input: GraphInput; 14 | modelName: ALL_MODEL_NAMES; 15 | modelConfigs: Record; 16 | } 17 | -------------------------------------------------------------------------------- /apps/agents/src/summarizer/state.ts: -------------------------------------------------------------------------------- 1 | import { Annotation, MessagesAnnotation } from "@langchain/langgraph"; 2 | 3 | export const SummarizerGraphAnnotation = Annotation.Root({ 4 | /** 5 | * The chat history to reflect on. 6 | */ 7 | ...MessagesAnnotation.spec, 8 | /** 9 | * The original thread ID to use to update the message state. 10 | */ 11 | threadId: Annotation, 12 | }); 13 | 14 | export type SummarizeState = typeof SummarizerGraphAnnotation.State; 15 | 16 | export type SummarizeGraphReturnType = Partial; 17 | -------------------------------------------------------------------------------- /apps/web/src/lib/supabase/client.ts: -------------------------------------------------------------------------------- 1 | import { createBrowserClient } from "@supabase/ssr"; 2 | 3 | export function createSupabaseClient() { 4 | if (!process.env.NEXT_PUBLIC_SUPABASE_URL) { 5 | throw new Error("NEXT_PUBLIC_SUPABASE_URL is not defined"); 6 | } 7 | if (!process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY) { 8 | throw new Error("NEXT_PUBLIC_SUPABASE_ANON_KEY is not defined"); 9 | } 10 | 11 | return createBrowserClient( 12 | process.env.NEXT_PUBLIC_SUPABASE_URL, 13 | process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /apps/agents/src/reflection/state.ts: -------------------------------------------------------------------------------- 1 | import { ArtifactV3 } from "@opencanvas/shared/types"; 2 | import { Annotation, MessagesAnnotation } from "@langchain/langgraph"; 3 | 4 | export const ReflectionGraphAnnotation = Annotation.Root({ 5 | /** 6 | * The chat history to reflect on. 7 | */ 8 | ...MessagesAnnotation.spec, 9 | /** 10 | * The artifact to reflect on. 11 | */ 12 | artifact: Annotation, 13 | }); 14 | 15 | export type ReflectionGraphReturnType = Partial< 16 | typeof ReflectionGraphAnnotation.State 17 | >; 18 | -------------------------------------------------------------------------------- /apps/web/src/components/artifacts/CodeRenderer.module.css: -------------------------------------------------------------------------------- 1 | .codeMirrorCustom { 2 | height: 100vh !important; 3 | overflow: hidden; 4 | } 5 | 6 | .codeMirrorCustom :global(.cm-editor) { 7 | height: 100% !important; 8 | border: none !important; 9 | } 10 | 11 | .codeMirrorCustom :global(.cm-scroller) { 12 | overflow: auto; 13 | } 14 | 15 | .codeMirrorCustom :global(.cm-gutters) { 16 | height: 100% !important; 17 | border-right: none !important; 18 | } 19 | 20 | .codeMirrorCustom :global(.cm-focused) { 21 | outline: none !important; 22 | } 23 | -------------------------------------------------------------------------------- /apps/agents/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/prettierrc", 3 | "printWidth": 80, 4 | "tabWidth": 2, 5 | "useTabs": false, 6 | "semi": true, 7 | "singleQuote": false, 8 | "quoteProps": "as-needed", 9 | "jsxSingleQuote": false, 10 | "trailingComma": "es5", 11 | "bracketSpacing": true, 12 | "arrowParens": "always", 13 | "requirePragma": false, 14 | "insertPragma": false, 15 | "proseWrap": "preserve", 16 | "htmlWhitespaceSensitivity": "css", 17 | "vueIndentScriptAndStyle": false, 18 | "endOfLine": "lf" 19 | } 20 | -------------------------------------------------------------------------------- /apps/web/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/prettierrc", 3 | "printWidth": 80, 4 | "tabWidth": 2, 5 | "useTabs": false, 6 | "semi": true, 7 | "singleQuote": false, 8 | "quoteProps": "as-needed", 9 | "jsxSingleQuote": false, 10 | "trailingComma": "es5", 11 | "bracketSpacing": true, 12 | "arrowParens": "always", 13 | "requirePragma": false, 14 | "insertPragma": false, 15 | "proseWrap": "preserve", 16 | "htmlWhitespaceSensitivity": "css", 17 | "vueIndentScriptAndStyle": false, 18 | "endOfLine": "lf" 19 | } 20 | -------------------------------------------------------------------------------- /packages/evals/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/prettierrc", 3 | "printWidth": 80, 4 | "tabWidth": 2, 5 | "useTabs": false, 6 | "semi": true, 7 | "singleQuote": false, 8 | "quoteProps": "as-needed", 9 | "jsxSingleQuote": false, 10 | "trailingComma": "es5", 11 | "bracketSpacing": true, 12 | "arrowParens": "always", 13 | "requirePragma": false, 14 | "insertPragma": false, 15 | "proseWrap": "preserve", 16 | "htmlWhitespaceSensitivity": "css", 17 | "vueIndentScriptAndStyle": false, 18 | "endOfLine": "lf" 19 | } 20 | -------------------------------------------------------------------------------- /packages/shared/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/prettierrc", 3 | "printWidth": 80, 4 | "tabWidth": 2, 5 | "useTabs": false, 6 | "semi": true, 7 | "singleQuote": false, 8 | "quoteProps": "as-needed", 9 | "jsxSingleQuote": false, 10 | "trailingComma": "es5", 11 | "bracketSpacing": true, 12 | "arrowParens": "always", 13 | "requirePragma": false, 14 | "insertPragma": false, 15 | "proseWrap": "preserve", 16 | "htmlWhitespaceSensitivity": "css", 17 | "vueIndentScriptAndStyle": false, 18 | "endOfLine": "lf" 19 | } 20 | -------------------------------------------------------------------------------- /apps/web/src/lib/supabase/verify_user_server.ts: -------------------------------------------------------------------------------- 1 | import { Session, User } from "@supabase/supabase-js"; 2 | import { createClient } from "./server"; 3 | 4 | export async function verifyUserAuthenticated(): Promise< 5 | { user: User; session: Session } | undefined 6 | > { 7 | const supabase = createClient(); 8 | const { 9 | data: { user }, 10 | } = await supabase.auth.getUser(); 11 | const { 12 | data: { session }, 13 | } = await supabase.auth.getSession(); 14 | if (!user || !session) { 15 | return undefined; 16 | } 17 | return { user, session }; 18 | } 19 | -------------------------------------------------------------------------------- /apps/agents/src/thread-title/state.ts: -------------------------------------------------------------------------------- 1 | import { Annotation, MessagesAnnotation } from "@langchain/langgraph"; 2 | import { ArtifactV3 } from "@opencanvas/shared/types"; 3 | 4 | export const TitleGenerationAnnotation = Annotation.Root({ 5 | /** 6 | * The chat history to generate a title for 7 | */ 8 | ...MessagesAnnotation.spec, 9 | /** 10 | * The artifact that was generated/updated (if any) 11 | */ 12 | artifact: Annotation, 13 | }); 14 | 15 | export type TitleGenerationReturnType = Partial< 16 | typeof TitleGenerationAnnotation.State 17 | >; 18 | -------------------------------------------------------------------------------- /apps/web/src/hooks/useRuns.tsx: -------------------------------------------------------------------------------- 1 | export function useRuns() { 2 | /** 3 | * Generates a public shared run ID for the given run ID. 4 | */ 5 | const shareRun = async (runId: string): Promise => { 6 | const res = await fetch("/api/runs/share", { 7 | method: "POST", 8 | body: JSON.stringify({ runId }), 9 | headers: { 10 | "Content-Type": "application/json", 11 | }, 12 | }); 13 | 14 | if (!res.ok) { 15 | return; 16 | } 17 | 18 | const { sharedRunURL } = await res.json(); 19 | return sharedRunURL; 20 | }; 21 | 22 | return { 23 | shareRun, 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /apps/web/src/components/tool-hooks/AttachmentsToolUI.tsx: -------------------------------------------------------------------------------- 1 | import { ContextDocument } from "@opencanvas/shared/types"; 2 | import { HumanMessage } from "@langchain/core/messages"; 3 | import { UploadedFiles } from "../assistant-select/context-documents/uploaded-file"; 4 | 5 | export const ContextDocumentsUI = ({ 6 | message, 7 | className, 8 | }: { 9 | message: HumanMessage | undefined; 10 | className?: string; 11 | }) => { 12 | const documents = message?.additional_kwargs?.documents as ContextDocument[]; 13 | if (!documents?.length) { 14 | return null; 15 | } 16 | 17 | return ; 18 | }; 19 | -------------------------------------------------------------------------------- /apps/web/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | .yarn/cache 9 | 10 | # testing 11 | /coverage 12 | 13 | # next.js 14 | /.next/ 15 | /out/ 16 | 17 | # production 18 | /build 19 | 20 | # misc 21 | .DS_Store 22 | *.pem 23 | 24 | # debug 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | 29 | # local env files 30 | .env*.local 31 | .env 32 | 33 | # vercel 34 | .vercel 35 | 36 | # typescript 37 | *.tsbuildinfo 38 | next-env.d.ts 39 | 40 | credentials.json 41 | 42 | # LangGraph API 43 | .langgraph_api 44 | -------------------------------------------------------------------------------- /apps/agents/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | .yarn/cache 9 | 10 | # testing 11 | /coverage 12 | 13 | # next.js 14 | /.next/ 15 | /out/ 16 | 17 | # production 18 | /build 19 | 20 | # misc 21 | .DS_Store 22 | *.pem 23 | 24 | # debug 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | 29 | # local env files 30 | .env*.local 31 | .env 32 | 33 | # vercel 34 | .vercel 35 | 36 | # typescript 37 | *.tsbuildinfo 38 | next-env.d.ts 39 | 40 | credentials.json 41 | 42 | # LangGraph API 43 | .langgraph_api 44 | -------------------------------------------------------------------------------- /packages/shared/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | .yarn/cache 9 | 10 | # testing 11 | /coverage 12 | 13 | # next.js 14 | /.next/ 15 | /out/ 16 | 17 | # production 18 | /build 19 | 20 | # misc 21 | .DS_Store 22 | *.pem 23 | 24 | # debug 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | 29 | # local env files 30 | .env*.local 31 | .env 32 | 33 | # vercel 34 | .vercel 35 | 36 | # typescript 37 | *.tsbuildinfo 38 | next-env.d.ts 39 | 40 | credentials.json 41 | 42 | # LangGraph API 43 | .langgraph_api 44 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./src/*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /apps/web/src/middleware.ts: -------------------------------------------------------------------------------- 1 | import { type NextRequest } from "next/server"; 2 | import { updateSession } from "@/lib/supabase/middleware"; 3 | 4 | export async function middleware(request: NextRequest) { 5 | return await updateSession(request); 6 | } 7 | 8 | export const config = { 9 | matcher: [ 10 | /* 11 | * Match all request paths except for the ones starting with: 12 | * - _next/static (static files) 13 | * - _next/image (image optimization files) 14 | * - favicon.ico (favicon file) 15 | * Feel free to modify this pattern to include more paths. 16 | */ 17 | "/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)", 18 | ], 19 | }; 20 | -------------------------------------------------------------------------------- /apps/web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./src/*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | **/node_modules 6 | /.pnp 7 | .pnp.js 8 | .yarn/install-state.gz 9 | .yarn/cache 10 | 11 | # testing 12 | /coverage 13 | 14 | # next.js 15 | /.next/ 16 | /out/ 17 | 18 | # production 19 | /build 20 | /dist 21 | **/dist 22 | .turbo/ 23 | 24 | # misc 25 | .DS_Store 26 | *.pem 27 | 28 | # debug 29 | npm-debug.log* 30 | yarn-debug.log* 31 | yarn-error.log* 32 | 33 | # local env files 34 | .env*.local 35 | .env 36 | 37 | # vercel 38 | .vercel 39 | 40 | # typescript 41 | *.tsbuildinfo 42 | next-env.d.ts 43 | 44 | credentials.json 45 | 46 | # LangGraph API 47 | .langgraph_api 48 | -------------------------------------------------------------------------------- /apps/web/src/lib/normalize_string.ts: -------------------------------------------------------------------------------- 1 | const actualNewline = ` 2 | `; 3 | 4 | export const cleanContent = (content: string): string => { 5 | return content ? content.replace(/\\n/g, actualNewline) : ""; 6 | }; 7 | 8 | export const reverseCleanContent = (content: string): string => { 9 | return content ? content.replaceAll(actualNewline, "\n") : ""; 10 | }; 11 | 12 | export const newlineToCarriageReturn = (str: string) => 13 | // str.replace(actualNewline, "\r\n"); 14 | str.replace(actualNewline, [actualNewline, actualNewline].join("")); 15 | 16 | export const emptyLineCount = (content: string): number => { 17 | const liens = content.split("\n"); 18 | return liens.filter((line) => line.trim() == "").length; 19 | }; 20 | -------------------------------------------------------------------------------- /apps/web/src/components/auth/login/actions.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { revalidatePath } from "next/cache"; 4 | import { redirect } from "next/navigation"; 5 | 6 | import { createClient } from "@/lib/supabase/server"; 7 | import { LoginWithEmailInput } from "./Login"; 8 | 9 | export async function login(input: LoginWithEmailInput) { 10 | const supabase = createClient(); 11 | 12 | const data = { 13 | email: input.email, 14 | password: input.password, 15 | }; 16 | 17 | const { error } = await supabase.auth.signInWithPassword(data); 18 | 19 | if (error) { 20 | console.error(error); 21 | redirect("/auth/login?error=true"); 22 | } 23 | 24 | revalidatePath("/", "layout"); 25 | redirect("/"); 26 | } 27 | -------------------------------------------------------------------------------- /apps/web/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "next/core-web-vitals", 4 | "plugin:@typescript-eslint/recommended" 5 | ], 6 | "parser": "@typescript-eslint/parser", 7 | "plugins": ["@typescript-eslint", "unused-imports", "@typescript-eslint/eslint-plugin"], 8 | "rules": { 9 | "@typescript-eslint/no-unused-vars": [ 10 | "error", 11 | { 12 | "argsIgnorePattern": "^_", 13 | "varsIgnorePattern": "^_|^UNUSED_", 14 | "caughtErrorsIgnorePattern": "^_", 15 | "destructuredArrayIgnorePattern": "^_" 16 | } 17 | ], 18 | "@typescript-eslint/no-explicit-any": "off", 19 | "@typescript-eslint/no-empty-object-type": "off", 20 | "unused-imports/no-unused-imports": "error" 21 | } 22 | } -------------------------------------------------------------------------------- /apps/web/src/lib/cookies.ts: -------------------------------------------------------------------------------- 1 | import Cookies from "js-cookie"; 2 | 3 | export const getCookie = (name: string): string | undefined => { 4 | if (typeof window === "undefined") { 5 | return undefined; 6 | } 7 | return Cookies.get(name); 8 | }; 9 | 10 | export const setCookie = ( 11 | name: string, 12 | value: string, 13 | options?: Cookies.CookieAttributes 14 | ): void => { 15 | if (typeof window === "undefined") { 16 | return; 17 | } 18 | Cookies.set(name, value, { 19 | expires: 365, // Default to 1 year expiration 20 | ...(options || {}), 21 | }); 22 | }; 23 | 24 | export const removeCookie = (name: string): void => { 25 | if (typeof window === "undefined") { 26 | return; 27 | } 28 | Cookies.remove(name); 29 | }; 30 | -------------------------------------------------------------------------------- /apps/web/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Canvas } from "@/components/canvas"; 4 | import { AssistantProvider } from "@/contexts/AssistantContext"; 5 | import { GraphProvider } from "@/contexts/GraphContext"; 6 | import { ThreadProvider } from "@/contexts/ThreadProvider"; 7 | import { UserProvider } from "@/contexts/UserContext"; 8 | import { Suspense } from "react"; 9 | 10 | export default function Home() { 11 | return ( 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /apps/web/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import "./globals.css"; 3 | import { Inter } from "next/font/google"; 4 | import { cn } from "@/lib/utils"; 5 | import { NuqsAdapter } from "nuqs/adapters/next/app"; 6 | 7 | const inter = Inter({ 8 | subsets: ["latin"], 9 | }); 10 | 11 | export const metadata: Metadata = { 12 | title: "Open Canvas", 13 | description: "Open Canvas Chat UX by LangChain", 14 | }; 15 | 16 | export default function RootLayout({ 17 | children, 18 | }: Readonly<{ 19 | children: React.ReactNode; 20 | }>) { 21 | return ( 22 | 23 | 24 | {children} 25 | 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /apps/agents/src/web-search/state.ts: -------------------------------------------------------------------------------- 1 | import { Annotation, MessagesAnnotation } from "@langchain/langgraph"; 2 | import { SearchResult } from "@opencanvas/shared/types"; 3 | 4 | export const WebSearchGraphAnnotation = Annotation.Root({ 5 | /** 6 | * The chat history to search the web for. 7 | * Will use the latest user message as the query. 8 | */ 9 | ...MessagesAnnotation.spec, 10 | /** 11 | * The search query. 12 | */ 13 | query: Annotation, 14 | /** 15 | * The search results 16 | */ 17 | webSearchResults: Annotation, 18 | /** 19 | * Whether or not to search the web based on the user's latest message. 20 | */ 21 | shouldSearch: Annotation, 22 | }); 23 | 24 | export type WebSearchState = typeof WebSearchGraphAnnotation.State; 25 | -------------------------------------------------------------------------------- /apps/agents/src/web-search/nodes/search.ts: -------------------------------------------------------------------------------- 1 | import { SearchResult } from "@opencanvas/shared/types"; 2 | import { WebSearchState } from "../state.js"; 3 | import ExaClient from "exa-js"; 4 | import { ExaRetriever } from "@langchain/exa"; 5 | 6 | export async function search( 7 | state: WebSearchState 8 | ): Promise> { 9 | const exaClient = new ExaClient(process.env.EXA_API_KEY || ""); 10 | const retriever = new ExaRetriever({ 11 | client: exaClient, 12 | searchArgs: { 13 | filterEmptyResults: true, 14 | numResults: 5, 15 | }, 16 | }); 17 | 18 | const query = state.messages[state.messages.length - 1].content as string; 19 | const results = await retriever.invoke(query); 20 | 21 | return { 22 | webSearchResults: results as SearchResult[], 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # This file should contain secrets which are used inside the agent (apps/agents) 2 | 3 | # LangSmith tracing 4 | LANGCHAIN_TRACING_V2="true" 5 | LANGCHAIN_API_KEY="" 6 | # Optional, unless using a non default project 7 | # LANGCHAIN_PROJECT="" 8 | 9 | # ---LLM API Keys--- 10 | 11 | # Anthropic 12 | ANTHROPIC_API_KEY="" 13 | # OpenAI 14 | OPENAI_API_KEY="" 15 | # Azure OpenAI 16 | _AZURE_OPENAI_API_KEY="" 17 | _AZURE_OPENAI_API_DEPLOYMENT_NAME="" 18 | _AZURE_OPENAI_API_VERSION="" 19 | _AZURE_OPENAI_API_BASE_PATH="" 20 | # Fireworks 21 | FIREWORKS_API_KEY="" 22 | # Gemini 23 | GOOGLE_API_KEY="" 24 | # Groq - STT 25 | GROQ_API_KEY="" 26 | # ------------------ 27 | 28 | # Supabase 29 | NEXT_PUBLIC_SUPABASE_URL="" 30 | NEXT_PUBLIC_SUPABASE_ANON_KEY="" 31 | SUPABASE_SERVICE_ROLE="" 32 | 33 | FIRECRAWL_API_KEY="" 34 | -------------------------------------------------------------------------------- /packages/evals/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/recommended", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "rootDir": "./src", 6 | "target": "ES2021", 7 | "lib": [ 8 | "ES2021", 9 | "ES2022.Object", 10 | "DOM" 11 | ], 12 | "module": "NodeNext", 13 | "moduleResolution": "NodeNext", 14 | "esModuleInterop": true, 15 | "declaration": true, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "useDefineForClassFields": true, 21 | "strictPropertyInitialization": false, 22 | "allowJs": true, 23 | "strict": true 24 | }, 25 | "include": [ 26 | "src/" 27 | ], 28 | "exclude": [ 29 | "node_modules/", 30 | "dist" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /packages/shared/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/recommended", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "rootDir": "./src", 6 | "target": "ES2021", 7 | "lib": [ 8 | "ES2021", 9 | "ES2022.Object", 10 | "DOM" 11 | ], 12 | "module": "NodeNext", 13 | "moduleResolution": "NodeNext", 14 | "esModuleInterop": true, 15 | "declaration": true, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "useDefineForClassFields": true, 21 | "strictPropertyInitialization": false, 22 | "allowJs": true, 23 | "strict": true 24 | }, 25 | "include": [ 26 | "src/" 27 | ], 28 | "exclude": [ 29 | "node_modules/", 30 | "dist" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /apps/agents/src/thread-title/prompts.ts: -------------------------------------------------------------------------------- 1 | export const TITLE_SYSTEM_PROMPT = `You are tasked with generating a concise, descriptive title for a conversation between a user and an AI assistant. The title should capture the main topic or purpose of the conversation. 2 | 3 | Guidelines for title generation: 4 | - Keep titles extremely short (ideally 2-5 words) 5 | - Focus on the main topic or goal of the conversation 6 | - Use natural, readable language 7 | - Avoid unnecessary articles (a, an, the) when possible 8 | - Do not include quotes or special characters 9 | - Capitalize important words 10 | 11 | Use the 'generate_title' tool to output your title.`; 12 | 13 | export const TITLE_USER_PROMPT = `Based on the following conversation, generate a very short and descriptive title for: 14 | 15 | {conversation} 16 | 17 | {artifact_context}`; 18 | -------------------------------------------------------------------------------- /apps/agents/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/recommended", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "rootDir": "./src", 6 | "baseUrl": ".", 7 | "target": "ES2021", 8 | "lib": [ 9 | "ES2021", 10 | "ES2022.Object", 11 | "DOM", 12 | "es2023" 13 | ], 14 | "module": "NodeNext", 15 | "moduleResolution": "NodeNext", 16 | "esModuleInterop": true, 17 | "declaration": true, 18 | "noImplicitReturns": true, 19 | "noFallthroughCasesInSwitch": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "useDefineForClassFields": true, 23 | "strictPropertyInitialization": false, 24 | "allowJs": true, 25 | "strict": true 26 | }, 27 | "include": [ 28 | "src/" 29 | ], 30 | "exclude": [ 31 | "node_modules/", 32 | "dist" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /apps/web/src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | export interface TextareaProps 6 | extends React.TextareaHTMLAttributes {} 7 | 8 | const Textarea = React.forwardRef( 9 | ({ className, ...props }, ref) => { 10 | return ( 11 |