refactor: Move to monorepo structure

This commit is contained in:
bracesproul
2025-02-05 14:12:00 -08:00
parent 280291f961
commit e0a055bbbb
232 changed files with 4259 additions and 2337 deletions
+4
View File
@@ -2,6 +2,7 @@
# dependencies
/node_modules
**/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
@@ -16,6 +17,9 @@
# production
/build
/dist
**/dist
.turbo/
# misc
.DS_Store
+61
View File
@@ -0,0 +1,61 @@
module.exports = {
extends: [
"eslint:recommended",
"prettier",
"plugin:@typescript-eslint/recommended",
],
parserOptions: {
ecmaVersion: 12,
parser: "@typescript-eslint/parser",
project: "./tsconfig.json",
sourceType: "module",
},
plugins: ["import", "@typescript-eslint", "no-instanceof"],
ignorePatterns: [
".eslintrc.cjs",
"scripts",
"src/utils/lodash/*",
"node_modules",
"dist",
"dist-cjs",
"*.js",
"*.cjs",
"*.d.ts",
],
rules: {
"@typescript-eslint/explicit-module-boundary-types": 0,
"@typescript-eslint/no-empty-function": 0,
"@typescript-eslint/no-shadow": 0,
"@typescript-eslint/no-empty-interface": 0,
"@typescript-eslint/no-use-before-define": ["error", "nofunc"],
"@typescript-eslint/no-unused-vars": ["warn", { args: "none" }],
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-misused-promises": "error",
"@typescript-eslint/no-explicit-any": 0,
camelcase: 0,
"class-methods-use-this": 0,
"import/extensions": [2, "ignorePackages"],
"import/no-extraneous-dependencies": [
"error",
{ devDependencies: ["**/*.test.ts"] },
],
"import/no-unresolved": 0,
"import/prefer-default-export": 0,
"keyword-spacing": "error",
"max-classes-per-file": 0,
"max-len": 0,
"no-await-in-loop": 0,
"no-bitwise": 0,
"no-console": 0,
"no-restricted-syntax": 0,
"no-shadow": 0,
"no-continue": 0,
"no-underscore-dangle": 0,
"no-use-before-define": 0,
"no-useless-constructor": 0,
"no-return-await": 0,
"consistent-return": 0,
"no-else-return": 0,
"new-cap": ["error", { properties: false, capIsNew: false }],
},
};
+43
View File
@@ -0,0 +1,43 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
.yarn/cache
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
.env
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
credentials.json
# LangGraph API
.langgraph_api
+67
View File
@@ -0,0 +1,67 @@
{
"name": "@opencanvas/agents",
"author": "Brace Sproul",
"repository": "https://github.com/langchain-ai/open-canvas",
"version": "0.0.1",
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"files": [
"dist/**/*"
],
"license": "MIT",
"private": true,
"scripts": {
"build": "turbo build:internal --filter=@opencanvas/agents",
"build:internal": "yarn clean && tsc",
"clean": "rm -rf ./dist .turbo || true",
"format": "prettier --config .prettierrc --write \"src\"",
"lint": "eslint src",
"lint:fix": "eslint src --fix"
},
"dependencies": {
"@ffmpeg/ffmpeg": "^0.12.15",
"@ffmpeg/util": "^0.12.2",
"@langchain/anthropic": "^0.3.12",
"@langchain/community": "^0.3.28",
"@langchain/core": "^0.3.38",
"@langchain/google-genai": "^0.1.7",
"@langchain/groq": "^0.1.3",
"@langchain/langgraph": "^0.2.41",
"@langchain/langgraph-sdk": "^0.0.36",
"@langchain/ollama": "^0.1.4",
"@langchain/openai": "^0.4.2",
"@mendable/firecrawl-js": "1.10.1",
"@supabase/supabase-js": "^2.45.5",
"date-fns": "^4.1.0",
"dotenv": "^16.4.5",
"framer-motion": "^11.11.9",
"groq-sdk": "^0.13.0",
"langchain": "^0.3.14",
"langsmith": "^0.3.3",
"lodash": "^4.17.21",
"pdf-parse": "^1.1.1",
"uuid": "^10.0.0",
"zod": "^3.23.8",
"@opencanvas/shared": "*"
},
"devDependencies": {
"turbo": "latest",
"@eslint/js": "^9.12.0",
"@types/eslint__js": "^8.42.3",
"eslint-plugin-unused-imports": "^4.1.4",
"@types/lodash": "^4.17.12",
"@types/node": "^20",
"@types/pdf-parse": "^1.1.4",
"@types/uuid": "^10.0.0",
"@typescript-eslint/eslint-plugin": "^8.12.2",
"@typescript-eslint/parser": "^8.8.1",
"eslint": "^8",
"eslint-config-next": "14.2.10",
"prettier": "^3.3.3",
"tsx": "^4.19.1",
"typescript": "^5",
"typescript-eslint": "^8.8.1",
"vitest": "^3.0.4"
}
}
@@ -1,19 +1,19 @@
import { END, Send, START, StateGraph } from "@langchain/langgraph";
import { DEFAULT_INPUTS } from "../../constants";
import { customAction } from "./nodes/customAction";
import { generateArtifact } from "./nodes/generate-artifact";
import { generateFollowup } from "./nodes/generateFollowup";
import { generatePath } from "./nodes/generatePath";
import { reflectNode } from "./nodes/reflect";
import { rewriteArtifact } from "./nodes/rewrite-artifact";
import { rewriteArtifactTheme } from "./nodes/rewriteArtifactTheme";
import { updateArtifact } from "./nodes/updateArtifact";
import { replyToGeneralInput } from "./nodes/replyToGeneralInput";
import { rewriteCodeArtifactTheme } from "./nodes/rewriteCodeArtifactTheme";
import { generateTitleNode } from "./nodes/generateTitle";
import { updateHighlightedText } from "./nodes/updateHighlightedText";
import { OpenCanvasGraphAnnotation } from "./state";
import { summarizer } from "./nodes/summarizer";
import { DEFAULT_INPUTS } from "@opencanvas/shared/dist/constants";
import { customAction } from "./nodes/customAction.js";
import { generateArtifact } from "./nodes/generate-artifact/index.js";
import { generateFollowup } from "./nodes/generateFollowup.js";
import { generatePath } from "./nodes/generatePath.js";
import { reflectNode } from "./nodes/reflect.js";
import { rewriteArtifact } from "./nodes/rewrite-artifact/index.js";
import { rewriteArtifactTheme } from "./nodes/rewriteArtifactTheme.js";
import { updateArtifact } from "./nodes/updateArtifact.js";
import { replyToGeneralInput } from "./nodes/replyToGeneralInput.js";
import { rewriteCodeArtifactTheme } from "./nodes/rewriteCodeArtifactTheme.js";
import { generateTitleNode } from "./nodes/generateTitle.js";
import { updateHighlightedText } from "./nodes/updateHighlightedText.js";
import { OpenCanvasGraphAnnotation } from "./state.js";
import { summarizer } from "./nodes/summarizer.js";
const routeNode = (state: typeof OpenCanvasGraphAnnotation.State) => {
if (!state.next) {
@@ -1,26 +1,31 @@
import { BaseMessage } from "@langchain/core/messages";
import { LangGraphRunnableConfig } from "@langchain/langgraph";
import { getArtifactContent } from "../../../contexts/utils";
import { isArtifactMarkdownContent } from "../../../lib/artifact_content_types";
import {
getArtifactContent,
isArtifactMarkdownContent,
} from "@opencanvas/shared/dist/utils/artifacts";
import {
ArtifactCodeV3,
ArtifactMarkdownV3,
ArtifactV3,
CustomQuickAction,
Reflections,
} from "../../../types";
} from "@opencanvas/shared/dist/types";
import {
ensureStoreInConfig,
formatReflections,
getModelFromConfig,
} from "../../utils";
} from "../../utils.js";
import {
CUSTOM_QUICK_ACTION_ARTIFACT_CONTENT_PROMPT,
CUSTOM_QUICK_ACTION_ARTIFACT_PROMPT_PREFIX,
CUSTOM_QUICK_ACTION_CONVERSATION_CONTEXT,
REFLECTIONS_QUICK_ACTION_PROMPT,
} from "../prompts";
import { OpenCanvasGraphAnnotation, OpenCanvasGraphReturnType } from "../state";
} from "../prompts.js";
import {
OpenCanvasGraphAnnotation,
OpenCanvasGraphReturnType,
} from "../state.js";
const formatMessages = (messages: BaseMessage[]): string =>
messages
@@ -5,15 +5,15 @@ import {
getModelFromConfig,
isUsingO1MiniModel,
optionallyGetSystemPromptFromConfig,
} from "@/agent/utils";
import { ArtifactV3 } from "@/types";
} from "@/utils.js";
import { ArtifactV3 } from "@opencanvas/shared/dist/types";
import { LangGraphRunnableConfig } from "@langchain/langgraph";
import {
OpenCanvasGraphAnnotation,
OpenCanvasGraphReturnType,
} from "../../state";
import { ARTIFACT_TOOL_SCHEMA } from "./schemas";
import { createArtifactContent, formatNewArtifactPrompt } from "./utils";
} from "../../state.js";
import { ARTIFACT_TOOL_SCHEMA } from "./schemas.js";
import { createArtifactContent, formatNewArtifactPrompt } from "./utils.js";
/**
* Generate a new artifact based on the user's query.
@@ -1,4 +1,4 @@
import { PROGRAMMING_LANGUAGES } from "@/types";
import { PROGRAMMING_LANGUAGES } from "@opencanvas/shared/dist/constants";
import { z } from "zod";
export const ARTIFACT_TOOL_SCHEMA = z.object({
@@ -1,11 +1,11 @@
import { NEW_ARTIFACT_PROMPT } from "../../prompts";
import { NEW_ARTIFACT_PROMPT } from "../../prompts.js";
import {
ArtifactCodeV3,
ArtifactMarkdownV3,
ProgrammingLanguageOptions,
} from "@/types";
} from "@opencanvas/shared/dist/types";
import { z } from "zod";
import { ARTIFACT_TOOL_SCHEMA } from "./schemas";
import { ARTIFACT_TOOL_SCHEMA } from "./schemas.js";
export const formatNewArtifactPrompt = (
memoriesAsString: string,
@@ -1,11 +1,16 @@
import { LangGraphRunnableConfig } from "@langchain/langgraph";
import { getModelFromConfig } from "../../utils";
import { getArtifactContent } from "../../../contexts/utils";
import { isArtifactMarkdownContent } from "../../../lib/artifact_content_types";
import { Reflections } from "../../../types";
import { ensureStoreInConfig, formatReflections } from "../../utils";
import { FOLLOWUP_ARTIFACT_PROMPT } from "../prompts";
import { OpenCanvasGraphAnnotation, OpenCanvasGraphReturnType } from "../state";
import { getModelFromConfig } from "../../utils.js";
import {
getArtifactContent,
isArtifactMarkdownContent,
} from "@opencanvas/shared/dist/utils/artifacts";
import { Reflections } from "@opencanvas/shared/dist/types";
import { ensureStoreInConfig, formatReflections } from "../../utils.js";
import { FOLLOWUP_ARTIFACT_PROMPT } from "../prompts.js";
import {
OpenCanvasGraphAnnotation,
OpenCanvasGraphReturnType,
} from "../state.js";
/**
* Generate a followup message after generating or updating an artifact.
@@ -1,29 +1,29 @@
import { v4 as uuidv4 } from "uuid";
import { LangGraphRunnableConfig } from "@langchain/langgraph";
import { z } from "zod";
import { getArtifactContent } from "../../../contexts/utils";
import { getArtifactContent } from "@opencanvas/shared/dist/utils/artifacts";
import {
convertPDFToText,
createContextDocumentMessages,
formatArtifactContentWithTemplate,
getModelConfig,
getModelFromConfig,
} from "../../utils";
} from "../../utils.js";
import {
CURRENT_ARTIFACT_PROMPT,
NO_ARTIFACT_PROMPT,
ROUTE_QUERY_OPTIONS_HAS_ARTIFACTS,
ROUTE_QUERY_OPTIONS_NO_ARTIFACTS,
ROUTE_QUERY_PROMPT,
} from "../prompts";
import { OpenCanvasGraphAnnotation } from "../state";
import { ContextDocument } from "@/hooks/useAssistants";
} from "../prompts.js";
import { OpenCanvasGraphAnnotation } from "../state.js";
import { ContextDocument } from "@opencanvas/shared/dist/types";
import {
BaseMessage,
HumanMessage,
RemoveMessage,
} from "@langchain/core/messages";
import { OC_HIDE_FROM_UI_KEY } from "@/constants";
import { OC_HIDE_FROM_UI_KEY } from "@opencanvas/shared/dist/constants";
/**
* Checks for context documents in a human message, and if found, converts
@@ -1,6 +1,6 @@
import { LangGraphRunnableConfig } from "@langchain/langgraph";
import { Client } from "@langchain/langgraph-sdk";
import { OpenCanvasGraphAnnotation } from "../state";
import { OpenCanvasGraphAnnotation } from "../state.js";
export const generateTitleNode = async (
state: typeof OpenCanvasGraphAnnotation.State,
@@ -1,5 +1,5 @@
import { Client } from "@langchain/langgraph-sdk";
import { OpenCanvasGraphAnnotation } from "../state";
import { OpenCanvasGraphAnnotation } from "../state.js";
import { LangGraphRunnableConfig } from "@langchain/langgraph";
export const reflectNode = async (
@@ -1,6 +1,6 @@
import { LangGraphRunnableConfig } from "@langchain/langgraph";
import { getArtifactContent } from "../../../contexts/utils";
import { Reflections } from "../../../types";
import { getArtifactContent } from "@opencanvas/shared/dist/utils/artifacts";
import { Reflections } from "@opencanvas/shared/dist/types";
import {
createContextDocumentMessages,
ensureStoreInConfig,
@@ -8,9 +8,12 @@ import {
formatReflections,
getModelFromConfig,
isUsingO1MiniModel,
} from "../../utils";
import { CURRENT_ARTIFACT_PROMPT, NO_ARTIFACT_PROMPT } from "../prompts";
import { OpenCanvasGraphAnnotation, OpenCanvasGraphReturnType } from "../state";
} from "../../utils.js";
import { CURRENT_ARTIFACT_PROMPT, NO_ARTIFACT_PROMPT } from "../prompts.js";
import {
OpenCanvasGraphAnnotation,
OpenCanvasGraphReturnType,
} from "../state.js";
/**
* Generate responses to questions. Does not generate artifacts.
@@ -2,10 +2,14 @@ import { v4 as uuidv4 } from "uuid";
import {
OpenCanvasGraphAnnotation,
OpenCanvasGraphReturnType,
} from "../../state";
} from "../../state.js";
import { LangGraphRunnableConfig } from "@langchain/langgraph";
import { optionallyUpdateArtifactMeta } from "./update-meta";
import { buildPrompt, createNewArtifactContent, validateState } from "./utils";
import { optionallyUpdateArtifactMeta } from "./update-meta.js";
import {
buildPrompt,
createNewArtifactContent,
validateState,
} from "./utils.js";
import {
createContextDocumentMessages,
getFormattedReflections,
@@ -13,13 +17,13 @@ import {
getModelFromConfig,
isUsingO1MiniModel,
optionallyGetSystemPromptFromConfig,
} from "@/agent/utils";
import { isArtifactMarkdownContent } from "@/lib/artifact_content_types";
} from "@/utils.js";
import { isArtifactMarkdownContent } from "@opencanvas/shared/dist/utils/artifacts";
import { AIMessage } from "@langchain/core/messages";
import {
extractThinkingAndResponseTokens,
isThinkingModel,
} from "@/contexts/utils";
} from "@opencanvas/shared/dist/utils/thinking";
export const rewriteArtifact = async (
state: typeof OpenCanvasGraphAnnotation.State,
@@ -1,4 +1,4 @@
import { PROGRAMMING_LANGUAGES } from "@/types";
import { PROGRAMMING_LANGUAGES } from "@opencanvas/shared/dist/constants";
import { z } from "zod";
export const OPTIONALLY_UPDATE_ARTIFACT_META_SCHEMA = z
@@ -1,14 +1,14 @@
import { LangGraphRunnableConfig } from "@langchain/langgraph";
import { OpenCanvasGraphAnnotation } from "../../state";
import { OpenCanvasGraphAnnotation } from "../../state.js";
import {
formatArtifactContent,
getModelFromConfig,
isUsingO1MiniModel,
} from "@/agent/utils";
import { getArtifactContent } from "@/contexts/utils";
import { GET_TITLE_TYPE_REWRITE_ARTIFACT } from "../../prompts";
import { OPTIONALLY_UPDATE_ARTIFACT_META_SCHEMA } from "./schemas";
import { getFormattedReflections } from "../../../utils";
} from "@/utils.js";
import { getArtifactContent } from "@opencanvas/shared/dist/utils/artifacts";
import { GET_TITLE_TYPE_REWRITE_ARTIFACT } from "../../prompts.js";
import { OPTIONALLY_UPDATE_ARTIFACT_META_SCHEMA } from "./schemas.js";
import { getFormattedReflections } from "../../../utils.js";
import { z } from "zod";
export async function optionallyUpdateArtifactMeta(
@@ -1,17 +1,19 @@
import { getArtifactContent } from "@/contexts/utils";
import { isArtifactCodeContent } from "@/lib/artifact_content_types";
import {
getArtifactContent,
isArtifactCodeContent,
} from "@opencanvas/shared/dist/utils/artifacts";
import {
ArtifactCodeV3,
ArtifactMarkdownV3,
ProgrammingLanguageOptions,
} from "@/types";
} from "@opencanvas/shared/dist/types";
import {
OPTIONALLY_UPDATE_META_PROMPT,
UPDATE_ENTIRE_ARTIFACT_PROMPT,
} from "../../prompts";
import { OpenCanvasGraphAnnotation } from "../../state";
} from "../../prompts.js";
import { OpenCanvasGraphAnnotation } from "../../state.js";
import { z } from "zod";
import { OPTIONALLY_UPDATE_ARTIFACT_META_SCHEMA } from "./schemas";
import { OPTIONALLY_UPDATE_ARTIFACT_META_SCHEMA } from "./schemas.js";
export const validateState = (
state: typeof OpenCanvasGraphAnnotation.State
@@ -78,6 +80,15 @@ interface CreateNewArtifactContentArgs {
newContent: string;
}
const getLanguage = (
artifactMetaToolCall: z.infer<typeof OPTIONALLY_UPDATE_ARTIFACT_META_SCHEMA>,
currentArtifactContent: ArtifactCodeV3 | ArtifactMarkdownV3 // Replace 'any' with proper type
) =>
artifactMetaToolCall?.language ||
(isArtifactCodeContent(currentArtifactContent)
? currentArtifactContent.language
: "other");
export const createNewArtifactContent = ({
artifactType,
state,
@@ -108,12 +119,3 @@ export const createNewArtifactContent = ({
fullMarkdown: newContent,
};
};
const getLanguage = (
artifactMetaToolCall: z.infer<typeof OPTIONALLY_UPDATE_ARTIFACT_META_SCHEMA>,
currentArtifactContent: ArtifactCodeV3 | ArtifactMarkdownV3 // Replace 'any' with proper type
) =>
artifactMetaToolCall?.language ||
(isArtifactCodeContent(currentArtifactContent)
? currentArtifactContent.language
: "other");
@@ -2,25 +2,30 @@ import { v4 as uuidv4 } from "uuid";
import { LangGraphRunnableConfig } from "@langchain/langgraph";
import {
extractThinkingAndResponseTokens,
getArtifactContent,
isThinkingModel,
} from "../../../contexts/utils";
import { isArtifactMarkdownContent } from "../../../lib/artifact_content_types";
import { ArtifactV3, Reflections } from "../../../types";
} from "@opencanvas/shared/dist/utils/thinking";
import {
isArtifactMarkdownContent,
getArtifactContent,
} from "@opencanvas/shared/dist/utils/artifacts";
import { ArtifactV3, Reflections } from "@opencanvas/shared/dist/types";
import {
ensureStoreInConfig,
formatReflections,
getModelConfig,
getModelFromConfig,
} from "../../utils";
} from "../../utils.js";
import {
ADD_EMOJIS_TO_ARTIFACT_PROMPT,
CHANGE_ARTIFACT_LANGUAGE_PROMPT,
CHANGE_ARTIFACT_LENGTH_PROMPT,
CHANGE_ARTIFACT_READING_LEVEL_PROMPT,
CHANGE_ARTIFACT_TO_PIRATE_PROMPT,
} from "../prompts";
import { OpenCanvasGraphAnnotation, OpenCanvasGraphReturnType } from "../state";
} from "../prompts.js";
import {
OpenCanvasGraphAnnotation,
OpenCanvasGraphReturnType,
} from "../state.js";
import { AIMessage } from "@langchain/core/messages";
export const rewriteArtifactTheme = async (
@@ -2,19 +2,24 @@ import { v4 as uuidv4 } from "uuid";
import { LangGraphRunnableConfig } from "@langchain/langgraph";
import {
extractThinkingAndResponseTokens,
getArtifactContent,
isThinkingModel,
} from "../../../contexts/utils";
import { isArtifactCodeContent } from "../../../lib/artifact_content_types";
import { ArtifactCodeV3, ArtifactV3 } from "../../../types";
import { getModelConfig, getModelFromConfig } from "../../utils";
} from "@opencanvas/shared/dist/utils/thinking";
import {
isArtifactCodeContent,
getArtifactContent,
} from "@opencanvas/shared/dist/utils/artifacts";
import { ArtifactCodeV3, ArtifactV3 } from "@opencanvas/shared/dist/types";
import { getModelConfig, getModelFromConfig } from "../../utils.js";
import {
ADD_COMMENTS_TO_CODE_ARTIFACT_PROMPT,
ADD_LOGS_TO_CODE_ARTIFACT_PROMPT,
FIX_BUGS_CODE_ARTIFACT_PROMPT,
PORT_LANGUAGE_CODE_ARTIFACT_PROMPT,
} from "../prompts";
import { OpenCanvasGraphAnnotation, OpenCanvasGraphReturnType } from "../state";
} from "../prompts.js";
import {
OpenCanvasGraphAnnotation,
OpenCanvasGraphReturnType,
} from "../state.js";
import { AIMessage } from "@langchain/core/messages";
export const rewriteCodeArtifactTheme = async (
@@ -1,5 +1,5 @@
import { Client } from "@langchain/langgraph-sdk";
import { OpenCanvasGraphAnnotation } from "../state";
import { OpenCanvasGraphAnnotation } from "../state.js";
import { LangGraphRunnableConfig } from "@langchain/langgraph";
export async function summarizer(
@@ -1,7 +1,13 @@
import { LangGraphRunnableConfig } from "@langchain/langgraph";
import { getArtifactContent } from "../../../contexts/utils";
import { isArtifactCodeContent } from "../../../lib/artifact_content_types";
import { ArtifactCodeV3, ArtifactV3, Reflections } from "../../../types";
import {
getArtifactContent,
isArtifactCodeContent,
} from "@opencanvas/shared/dist/utils/artifacts";
import {
ArtifactCodeV3,
ArtifactV3,
Reflections,
} from "@opencanvas/shared/dist/types";
import {
createContextDocumentMessages,
ensureStoreInConfig,
@@ -9,9 +15,12 @@ import {
getModelConfig,
getModelFromConfig,
isUsingO1MiniModel,
} from "../../utils";
import { UPDATE_HIGHLIGHTED_ARTIFACT_PROMPT } from "../prompts";
import { OpenCanvasGraphAnnotation, OpenCanvasGraphReturnType } from "../state";
} from "../../utils.js";
import { UPDATE_HIGHLIGHTED_ARTIFACT_PROMPT } from "../prompts.js";
import {
OpenCanvasGraphAnnotation,
OpenCanvasGraphReturnType,
} from "../state.js";
/**
* Update an existing artifact based on the user's query.
@@ -3,16 +3,21 @@ import {
getModelConfig,
getModelFromConfig,
isUsingO1MiniModel,
} from "@/agent/utils";
} from "@/utils.js";
import { BaseLanguageModelInput } from "@langchain/core/language_models/base";
import { AIMessageChunk } from "@langchain/core/messages";
import { RunnableBinding } from "@langchain/core/runnables";
import { LangGraphRunnableConfig } from "@langchain/langgraph";
import { ConfigurableChatModelCallOptions } from "langchain/chat_models/universal";
import { getArtifactContent } from "../../../contexts/utils";
import { isArtifactMarkdownContent } from "../../../lib/artifact_content_types";
import { ArtifactMarkdownV3 } from "../../../types";
import { OpenCanvasGraphAnnotation, OpenCanvasGraphReturnType } from "../state";
import {
getArtifactContent,
isArtifactMarkdownContent,
} from "@opencanvas/shared/dist/utils/artifacts";
import { ArtifactMarkdownV3 } from "@opencanvas/shared/dist/types";
import {
OpenCanvasGraphAnnotation,
OpenCanvasGraphReturnType,
} from "../state.js";
const PROMPT = `You are an expert AI writing assistant, tasked with rewriting some text a user has selected. The selected text is nested inside a larger 'block'. You should always respond with ONLY the updated text block in accordance with the user's request.
You should always respond with the full markdown text block, as it will simply replace the existing block in the artifact.
@@ -7,13 +7,13 @@ import {
CodeHighlight,
ArtifactV3,
TextHighlight,
} from "../../types";
} from "@opencanvas/shared/dist/types";
import {
Annotation,
MessagesAnnotation,
messagesStateReducer,
} from "@langchain/langgraph";
import { OC_SUMMARIZED_MESSAGE_KEY } from "@/constants";
import { OC_SUMMARIZED_MESSAGE_KEY } from "@opencanvas/shared/dist/constants";
export type Messages =
| Array<BaseMessage | BaseMessageLike>
@@ -4,13 +4,18 @@ import {
StateGraph,
START,
} from "@langchain/langgraph";
import { ReflectionGraphAnnotation, ReflectionGraphReturnType } from "./state";
import { Reflections } from "../../types";
import { REFLECT_SYSTEM_PROMPT, REFLECT_USER_PROMPT } from "./prompts";
import {
ReflectionGraphAnnotation,
ReflectionGraphReturnType,
} from "./state.js";
import { Reflections } from "@opencanvas/shared/dist/types";
import { REFLECT_SYSTEM_PROMPT, REFLECT_USER_PROMPT } from "./prompts.js";
import { z } from "zod";
import { ensureStoreInConfig, formatReflections } from "../utils";
import { getArtifactContent } from "../../contexts/utils";
import { isArtifactMarkdownContent } from "../../lib/artifact_content_types";
import { ensureStoreInConfig, formatReflections } from "../utils.js";
import {
getArtifactContent,
isArtifactMarkdownContent,
} from "@opencanvas/shared/dist/utils/artifacts";
export const reflect = async (
state: typeof ReflectionGraphAnnotation.State,
@@ -1,4 +1,4 @@
import { ArtifactV3 } from "../../types";
import { ArtifactV3 } from "@opencanvas/shared/dist/types";
import { Annotation, MessagesAnnotation } from "@langchain/langgraph";
export const ReflectionGraphAnnotation = Annotation.Root({
@@ -1,8 +1,8 @@
import { ChatAnthropic } from "@langchain/anthropic";
import { StateGraph, START } from "@langchain/langgraph";
import { SummarizerGraphAnnotation, SummarizeState } from "./state";
import { SummarizerGraphAnnotation, SummarizeState } from "./state.js";
import { BaseMessage, HumanMessage } from "@langchain/core/messages";
import { OC_SUMMARIZED_MESSAGE_KEY } from "@/constants";
import { OC_SUMMARIZED_MESSAGE_KEY } from "@opencanvas/shared/dist/constants";
import { v4 as uuidv4 } from "uuid";
import { Client } from "@langchain/langgraph-sdk";
@@ -6,10 +6,15 @@ import {
import { Client } from "@langchain/langgraph-sdk";
import { ChatOpenAI } from "@langchain/openai";
import { z } from "zod";
import { getArtifactContent } from "../../contexts/utils";
import { isArtifactMarkdownContent } from "../../lib/artifact_content_types";
import { TITLE_SYSTEM_PROMPT, TITLE_USER_PROMPT } from "./prompts";
import { TitleGenerationAnnotation, TitleGenerationReturnType } from "./state";
import {
getArtifactContent,
isArtifactMarkdownContent,
} from "@opencanvas/shared/dist/utils/artifacts";
import { TITLE_SYSTEM_PROMPT, TITLE_USER_PROMPT } from "./prompts.js";
import {
TitleGenerationAnnotation,
TitleGenerationReturnType,
} from "./state.js";
export const generateTitle = async (
state: typeof TitleGenerationAnnotation.State,
@@ -1,5 +1,5 @@
import { Annotation, MessagesAnnotation } from "@langchain/langgraph";
import { ArtifactV3 } from "../../types";
import { ArtifactV3 } from "@opencanvas/shared/dist/types";
export const TitleGenerationAnnotation = Annotation.Root({
/**
+19 -15
View File
@@ -1,9 +1,13 @@
import { isArtifactCodeContent } from "@/lib/artifact_content_types";
import { CustomModelConfig } from "@/types";
import { isArtifactCodeContent } from "@opencanvas/shared/dist/utils/artifacts";
import {
CustomModelConfig,
ArtifactCodeV3,
ArtifactMarkdownV3,
Reflections,
ContextDocument,
} from "@opencanvas/shared/dist/types";
import { BaseStore, LangGraphRunnableConfig } from "@langchain/langgraph";
import { initChatModel } from "langchain/chat_models/universal";
import { ArtifactCodeV3, ArtifactMarkdownV3, Reflections } from "../types";
import { ContextDocument } from "@/hooks/useAssistants";
import pdfParse from "pdf-parse";
import {
MessageContentComplex,
@@ -13,7 +17,7 @@ import {
CONTEXT_DOCUMENTS_NAMESPACE,
LANGCHAIN_USER_ONLY_MODELS,
TEMPERATURE_EXCLUDED_MODELS,
} from "@/constants";
} from "@opencanvas/shared/dist/constants";
import { createClient, Session, User } from "@supabase/supabase-js";
export const formatReflections = (
@@ -88,6 +92,15 @@ export const formatReflections = (
return styleString + "\n\n" + contentString;
};
export const ensureStoreInConfig = (
config: LangGraphRunnableConfig
): BaseStore => {
if (!config.store) {
throw new Error("`store` not found in config");
}
return config.store;
};
export async function getFormattedReflections(
config: LangGraphRunnableConfig
): Promise<string> {
@@ -109,15 +122,6 @@ export async function getFormattedReflections(
return memoriesAsString;
}
export const ensureStoreInConfig = (
config: LangGraphRunnableConfig
): BaseStore => {
if (!config.store) {
throw new Error("`store` not found in config");
}
return config.store;
};
export const formatArtifactContent = (
content: ArtifactMarkdownV3 | ArtifactCodeV3,
shortenContent?: boolean
@@ -333,7 +337,7 @@ export async function getModelFromConfig(
maxTokens?: number;
isToolCalling?: boolean;
}
) {
): Promise<ReturnType<typeof initChatModel>> {
const {
modelName,
modelProvider,
+37
View File
@@ -0,0 +1,37 @@
{
"extends": "@tsconfig/recommended",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
"target": "ES2021",
"lib": [
"ES2021",
"ES2022.Object",
"DOM",
"es2023"
],
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"declaration": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"useDefineForClassFields": true,
"strictPropertyInitialization": false,
"allowJs": true,
"strict": true
},
"include": [
"src/"
],
"exclude": [
"node_modules/",
"dist"
]
}
+11
View File
@@ -0,0 +1,11 @@
{
"extends": ["//"],
"tasks": {
"build": {
"outputs": ["**/dist/**"]
},
"build:internal": {
"dependsOn": ["^build:internal"]
}
}
}
+45
View File
@@ -0,0 +1,45 @@
# LangSmith tracing
# Set this to `false` to disable tracing to LangSmith
LANGCHAIN_TRACING_V2=true
LANGCHAIN_API_KEY=
# LLM API keys
# Anthropic used for reflection
ANTHROPIC_API_KEY=
# OpenAI used for content generation
OPENAI_API_KEY=
# Optional, only required if using `Gemini 1.5 Flash` as the model.
GOOGLE_API_KEY=
# Optional, only required if using `Fireworks` as the model.
FIREWORKS_API_KEY=
# Feature flags for hiding/showing specific models
NEXT_PUBLIC_FIREWORKS_ENABLED=true
NEXT_PUBLIC_GEMINI_ENABLED=true
NEXT_PUBLIC_ANTHROPIC_ENABLED=true
NEXT_PUBLIC_OPENAI_ENABLED=true
# Set to false by default since the base OpenAI API is more common than the Azure OpenAI API.
NEXT_PUBLIC_AZURE_ENABLED=false
NEXT_PUBLIC_OLLAMA_ENABLED=false
# If using Ollama, set the API URL here. Only needs to be set if using the non default Ollama server port.
# It will default to `http://host.docker.internal:11434` if not set.
# OLLAMA_API_URL="http://host.docker.internal:11434"
# LangGraph Deployment, or local development server via LangGraph Studio.
# If running locally, this URL should be set in the `constants.ts` file.
# LANGGRAPH_API_URL=
# Supabase for authentication
# Public keys
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
# Azure OpenAI Configuration
# ENSURE THEY ARE PREFIXED WITH AN UNDERSCORE.
_AZURE_OPENAI_API_KEY=your-azure-openai-api-key
_AZURE_OPENAI_API_INSTANCE_NAME=your-instance-name
_AZURE_OPENAI_API_DEPLOYMENT_NAME=your-deployment-name
_AZURE_OPENAI_API_VERSION=2024-08-01-preview
# Optional: Azure OpenAI Base Path (if using a different domain)
# _AZURE_OPENAI_API_BASE_PATH=https://your-custom-domain.com/openai/deployments
+43
View File
@@ -0,0 +1,43 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
.yarn/cache
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
.env
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
credentials.json
# LangGraph API
.langgraph_api
+19
View File
@@ -0,0 +1,19 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": false,
"quoteProps": "as-needed",
"jsxSingleQuote": false,
"trailingComma": "es5",
"bracketSpacing": true,
"arrowParens": "always",
"requirePragma": false,
"insertPragma": false,
"proseWrap": "preserve",
"htmlWhitespaceSensitivity": "css",
"vueIndentScriptAndStyle": false,
"endOfLine": "lf"
}
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) LangChain, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+182
View File
@@ -0,0 +1,182 @@
# Open Canvas
[TRY IT OUT HERE](https://opencanvas.langchain.com/)
![Screenshot of app](./public/screenshot.png)
Open Canvas is an open source web application for collaborating with agents to better write documents. It is inspired by [OpenAI's "Canvas"](https://openai.com/index/introducing-canvas/), but with a few key differences.
1. **Open Source**: All the code, from the frontend, to the content generation agent, to the reflection agent is open source and MIT licensed.
2. **Built in memory**: Open Canvas ships out of the box with a [reflection agent](https://langchain-ai.github.io/langgraphjs/tutorials/reflection/reflection/) which stores style rules and user insights in a [shared memory store](https://langchain-ai.github.io/langgraphjs/concepts/memory/). This allows Open Canvas to remember facts about you across sessions.
3. **Start from existing documents**: Open Canvas allows users to start with a blank text, or code editor in the language of their choice, allowing you to start the session with your existing content, instead of being forced to start with a chat interaction. We believe this is an ideal UX because many times you will already have some content to start with, and want to iterate on-top of it.
## Features
- **Memory**: Open Canvas has a built in memory system which will automatically generate reflections and memories on you, and your chat history. These are then included in subsequent chat interactions to give a more personalized experience.
- **Custom quick actions**: Custom quick actions allow you to define your own prompts which are tied to your user, and persist across sessions. These then can be easily invoked through a single click, and apply to the artifact you're currently viewing.
- **Pre-built quick actions**: There are also a series of pre-built quick actions for common writing and coding tasks that are always available.
- **Artifact versioning**: All artifacts have a "version" tied to them, allowing you to travel back in time and see previous versions of your artifact.
- **Code, Markdown, or both**: The artifact view allows for viewing and editing both code, and markdown. You can even have chats which generate code, and markdown artifacts, and switch between them.
- **Live markdown rendering & editing**: Open Canvas's markdown editor allows you to view the rendered markdown while you're editing, without having to toggle back and fourth.
## Setup locally
This guide will cover how to setup and run Open Canvas locally. If you prefer a YouTube video guide, check out [this video](https://youtu.be/sBzcQYPMekc).
### Prerequisites
Open Canvas requires the following API keys and external services:
#### Package Manager
- [Yarn](https://yarnpkg.com/)
#### LLM APIs
- [OpenAI API key](https://platform.openai.com/signup/)
- [Anthropic API key](https://console.anthropic.com/)
- (optional) [Google GenAI API key](https://aistudio.google.com/apikey)
- (optional) [Fireworks AI API key](https://fireworks.ai/login)
#### Authentication
- [Supabase](https://supabase.com/) account for authentication
#### LangGraph Server
- [LangGraph CLI](https://langchain-ai.github.io/langgraph/cloud/reference/cli/) for running the graph locally
#### LangSmith
- [LangSmith](https://smith.langchain.com/) for tracing & observability
### Installation
First, clone the repository:
```bash
git clone https://github.com/langchain-ai/open-canvas.git
cd open-canvas
```
Next, install the dependencies:
```bash
yarn install
```
After installing dependencies, copy the `.env.example` file contents into `.env` and set the required values:
```bash
cp .env.example .env
```
Then, setup authentication with Supabase.
### Setup Authentication
After creating a Supabase account, visit your [dashboard](https://supabase.com/dashboard/projects) and create a new project.
Next, navigate to the `Project Settings` page inside your project, and then to the `API` tag. Copy the `Project URL`, and `anon public` project API key. Paste them into the `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_ANON_KEY` environment variables in the `.env` file.
After this, navigate to the `Authentication` page, and the `Providers` tab. Make sure `Email` is enabled (also ensure you've enabled `Confirm Email`). You may also enable `GitHub`, and/or `Google` if you'd like to use those for authentication. (see these pages for documentation on how to setup each provider: [GitHub](https://supabase.com/docs/guides/auth/social-login/auth-github), [Google](https://supabase.com/docs/guides/auth/social-login/auth-google))
#### Test authentication
To verify authentication works, run `yarn dev` and visit [localhost:3000](http://localhost:3000). This should redirect you to the [login page](http://localhost:3000/auth/login). From here, you can either login with Google or GitHub, or if you did not configure these providers, navigate to the [signup page](http://localhost:3000/auth/signup) and create a new account with an email and password. This should then redirect you to a conformation page, and after confirming your email you should be redirected to the [home page](http://localhost:3000).
### Setup LangGraph Server
Now we'll cover how to setup and run the LangGraph server locally.
Follow the [`Installation` instructions in the LangGraph docs](https://langchain-ai.github.io/langgraph/cloud/reference/cli/#installation) to install the LangGraph CLI.
Once installed, navigate to the root of the Open Canvas repo and run `yarn dev:server` (this runs `npx @langchain/langgraph-cli dev --port 54367`).
Once it finishes pulling the docker image and installing dependencies, you should see it log:
```
Ready!
- 🚀 API: http://localhost:54367
- 🎨 Studio UI: https://smith.langchain.com/studio?baseUrl=http://localhost:54367
```
After your LangGraph server is running, execute the following command to start the Open Canvas app:
```bash
yarn dev
```
On initial load, compilation may take a little bit of time.
Then, open [localhost:3000](http://localhost:3000) with your browser and start interacting!
## LLM Models
Open Canvas is designed to be compatible with any LLM model. The current deployment has the following models configured:
- **Anthropic Claude 3 Haiku 👤**: Haiku is Anthropic's fastest model, great for quick tasks like making edits to your document. Sign up for an Anthropic account [here](https://console.anthropic.com/).
- **Fireworks Llama 3 70B 🦙**: Llama 3 is a SOTA open source model from Meta, powered by [Fireworks AI](https://fireworks.ai/). You can sign up for an account [here](https://fireworks.ai/login).
- **OpenAI GPT 4o Mini 💨**: GPT 4o Mini is OpenAI's newest, smallest model. You can sign up for an API key [here](https://platform.openai.com/signup/).
If you'd like to add a new model, follow these simple steps:
1. Add to or update the model provider variables in `constants.ts`.
2. Install the necessary package for the provider (e.g. `@langchain/anthropic`).
3. Update the `getModelConfig` function in `src/agent/utils.ts` to include an `if` statement for your new model name and provider.
4. Manually test by checking you can:
> - 4a. Generate a new artifact
> - 4b. Generate a followup message (happens automatically after generating an artifact)
> - 4c. Update an artifact via a message in chat
> - 4d. Update an artifact via a quick action
> - 4e. Repeat for text/code (ensure both work)
### Local Ollama models
Open Canvas supports calling local LLMs running on Ollama. This is not enabled in the hosted version of Open Canvas, but you can use this in your own local/deployed Open Canvas instance.
To use a local Ollama model, first ensure you have [Ollama](https://ollama.com) installed, and a model that supports tool calling pulled (the default model is `llama3.3`).
Next, start the Ollama server by running `ollama run llama3.3`.
Then, set the `NEXT_PUBLIC_OLLAMA_ENABLED` environment variable to `true`, and the `OLLAMA_API_URL` environment variable to the URL of your Ollama server (defaults to `http://host.docker.internal:11434`. If you do not set a custom port when starting your Ollama server, you should not need to set this environment variable).
> [!NOTE]
> Open source LLMs are typically not as good at instruction following as proprietary models like GPT-4o or Claude Sonnet. Because of this, you may experience errors or unexpected behavior when using local LLMs.
## Troubleshooting
Below are some common issues you may run into if running Open Canvas yourself:
- **I have the LangGraph server running successfully, and my client can make requests, but no text is being generated:** This can happen if you start & connect to multiple different LangGraph servers locally in the same browser. Try clearing the `oc_thread_id_v2` cookie and refreshing the page. This is because each unique LangGraph server has its own database where threads are stored, so a thread ID from one server will not be found in the database of another server.
- **I'm getting 500 network errors when I try to make requests on the client:** Ensure you have the LangGraph server running, and you're making requests to the correct port. You can specify the port to use by passing the `--port <PORT>` flag to the `npx @langchain/langgraph-cli dev` command, and you can set the URL to make requests to by either setting the `LANGGRAPH_API_URL` environment variable, or by changing the fallback value of the `LANGGRAPH_API_URL` variable in `constants.ts`.
- **I'm getting "thread ID not found" error toasts when I try to make requests on the client:** Ensure you have the LangGraph server running, and you're making requests to the correct port. You can specify the port to use by passing the `--port <PORT>` flag to the `npx @langchain/langgraph-cli dev` command, and you can set the URL to make requests to by either setting the `LANGGRAPH_API_URL` environment variable, or by changing the fallback value of the `LANGGRAPH_API_URL` variable in `constants.ts`.
- **`Model name is missing in config.` error is being thrown when I make requests:** This error occurs when the `customModelName` is not specified in the config. You can resolve this by setting the `customModelName` field inside `config.configurable` to the name of the model you want to use when invoking the graph. See [this doc](https://langchain-ai.github.io/langgraphjs/how-tos/configuration/) on how to use configurable fields in LangGraph.
## Roadmap
### Features
Below is a list of features we'd like to add to Open Canvas in the near future:
- **Render React in the editor**: Ideally, if you have Open Canvas generate React (or HTML) code, we should be able to render it live in the editor. **Edit**: This is in the planning stage now!
- **Multiple assistants**: Users should be able to create multiple assistants, each having their own memory store.
- **Give assistants custom 'tools'**: Once we've implemented `RemoteGraph` in LangGraph.js, users should be able to give assistants access to call their own graphs as tools. This means you could customize your assistant to have access to current events, your own personal knowledge graph, etc.
Do you have a feature request? Please [open an issue](https://github.com/langchain-ai/open-canvas/issues/new)!
### Contributing
We'd like to continue developing and improving Open Canvas, and want your help!
To start, there are a handful of GitHub issues with feature requests outlining improvements and additions to make the app's UX even better.
There are three main labels:
- `frontend`: This label is added to issues which are UI focused, and do not require much if any work on the agent(s).
- `ai`: This label is added to issues which are focused on improving the LLM agent(s).
- `fullstack`: This label is added to issues which require touching both the frontend and agent code.
If you have questions about contributing, please reach out to me via email: `brace(at)langchain(dot)dev`. For general bugs/issues with the code, please [open an issue on GitHub](https://github.com/langchain-ai/open-canvas/issues/new).
+50
View File
@@ -0,0 +1,50 @@
How AppFolio transformed property management workflows with Realm-X, built using LangGraph and LangSmith
See how AppFolio's AI-powered copilot Realm-X has saved property managers over 10 hours per week. Learn how they improved Realm-X's performance 2x using LangSmith and built an agent architecture with LangGraph.
Case Studies
4 min read
Dec 16, 2024
AppFolio, the technology leader powering the future of the real estate industry, has released Realm-X Assistant, an AI-powered copilot designed to improve the efficiency of property managers day-to-day work. Realm-X is embedded generative AI that provides intelligent, real-time assistance by combining the latest foundation models with industry-specific context. Realm-X provides a conversational interface that helps users understand the state of their business, get help, and execute actions in bulk whether its querying information, sending messages, or scheduling actions related to residents, vendors, units, bills, or work orders and many more. Early users have reported saving over 10 hours a week in completing their to-do list.
While designing Realm-X, AppFolio realized that they needed a better natural language interface to help property managers engage with the platform and simplify operational processes. The team turned to LangChain, LangGraph, and LangSmith to improve their execution flow and add visibility into how users were interacting with Realm-X.
Realm-X assistant interface
Choosing LangGraph for flexibility and control
Realm-X Assistant was initially built using LangChain for interoperability, making it easy to switch out model providers without changing any code. LangChain also made it easy to use tools and structured outputs when making requests with their tool calling agent.
As the Assistant evolved, AppFolio needed a way to handle even more complexity with their requests. Their team made a strategic transition from LangChain to LangGraph, which simplified response aggregation from different nodes to display to the user.
Realm-X architecture with LangGraph
After switching to LangGraph, the AppFolio team could clearly see the execution flow of the Realm-X Assistant, which allowed them to design workflows that could reason before acting. By parallelizing branch execution, AppFolio has also been able to reduce latency and code complexity.
One major benefit of LangGraph has been its ability to run independent code branches in parallel. While determining relevant actions, it simultaneously calculates fallbacks and runs a question-answering bot over help pages. This allows Realm-X to offer related suggestions which enhances the user experience, while keeping latency at a minimum.
Leveraging LangSmith to monitor and pinpoint issues in production
To add a layer of visibility into their production traffic, AppFolio used LangSmith to facilitate debugging during development and gain more insights into user interactions. In production, the AppFolio team keeps a close eye on real-time feedback charts in LangSmith — tracking error rates, costs, and latency to keep everything on course. The team also added in automatic triggers to collect feedback when users submit an action drafted by Realm-X.
In addition, automatic feedback is generated based on LLM or heuristic evaluators to continuously monitor system health.
LangSmiths tracing also makes it easy for AppFolio to pinpoint issues when they occur. During development, engineers rely on comparison views and the LangSmith playground to iterate on workflows, ensuring they're battle-tested before deployment. Traces are also shareable across the team, streamlining the process of collaborating across stakeholders.
Iterating on prompts to improve system performance
One key innovation that LangSmith supports in AppFolios system is dynamic few-shot prompting — which involves dynamically pulling relevant examples to deliver more personalized and accurate responses to Realm-X users. With LangSmith, the AppFolio team has been able to quickly identify whether wrong samples were used or if a relevant sample was missing or poorly formatted, ultimately helping optimize the examples pulled for a given query.
The comparison view in LangSmith was particularly useful for identifying subtle differences in how prompts were constructed, helping AppFolio ensure precise outputs. The LangSmith Playground also enabled the team to rapidly iterate on prompts, base models, or tool descriptions with modifying the underlying code shortening the feedback cycle between stakeholders.
Dynamic few-shot prompting has been crucial in improving the performance of specific Realm-X features like its text-to-data functionality, where performance significantly increased from ~40% to ~80%. This showcases the impact of tailored examples in improving system accuracy. AppFolio has also maintained high performance as theyve increased the number of actions and data models that users can query in Realm-X.
LLM evaluations for continuous improvement
AppFolio prioritizes user experience and feedback in its testing and evaluation process. Every step in the Realm-X workflow— from individual actions to end-to-end executions— is rigorously tested using custom evaluators and LangSmiths tools.
To ensure quality, AppFolio maintains a central repository of sample cases containing message history, metadata, and ideal outputs. These can be used as evals, unit tests, or examples. Evals are run in CI, with results tracked and integrated into PRs. To avoid regressions, code changes merge only when all unit tests pass and eval thresholds are met.
LangGraph also helps streamline complex workflows like text-to-data processing by organizing intricate if-statement logic into clear, flexible code paths. This, combined with rigorous testing, ensures that Realm-X Assistant produces accurate and reliable responses for property managers.
The path forward for AppFolio
As AppFolio continues to innovate with the Realm-X Assistant, the focus remains on enhancing user-centric features and optimizing performance. As an early LangSmith user, AppFolio seamlessly integrated LangGraph with LangSmiths robust monitoring, which helped Realm-X Assistant aggregate responses from multiple system parts to ensure clear, actionable user outputs.
Looking ahead, AppFolio is expanding Realm-X to further improve overall performance and reliability by using LangGraph for state management and self-validation loops. By combining these tools with their commitment to innovation, AppFolio continues to empower property managers everywhere to succeed.
+131
View File
@@ -0,0 +1,131 @@
{
"name": "@opencanvas/web",
"author": "Brace Sproul",
"homepage": "https://open-canvas-lc.vercel.app",
"repository": "https://github.com/langchain-ai/open-canvas",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "next dev",
"dev:server": "npx @langchain/langgraph-cli dev --port 54367",
"build": "turbo build:internal --filter=@opencanvas/web",
"build:internal": "next build",
"start": "next start",
"lint": "next lint",
"lint:fix": "next lint --fix",
"format": "prettier --config .prettierrc --write \"src\"",
"eval": "vitest run --config ls.vitest.config.ts",
"eval:highlights": "yarn tsx evals/highlights.ts"
},
"dependencies": {
"@opencanvas/shared": "*",
"@assistant-ui/react": "^0.7.68",
"@assistant-ui/react-markdown": "^0.7.2",
"@assistant-ui/react-syntax-highlighter": "^0.7.2",
"@blocknote/core": "^0.17.1",
"@blocknote/mantine": "^0.17.1",
"@blocknote/react": "^0.17.1",
"@blocknote/shadcn": "^0.17.1",
"@codemirror/lang-cpp": "^6.0.2",
"@codemirror/lang-html": "^6.4.9",
"@codemirror/lang-java": "^6.0.1",
"@codemirror/lang-javascript": "^6.2.2",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-php": "^6.0.1",
"@codemirror/lang-python": "^6.1.6",
"@codemirror/lang-rust": "^6.0.1",
"@codemirror/lang-sql": "^6.8.0",
"@codemirror/lang-xml": "^6.1.0",
"@ffmpeg/ffmpeg": "^0.12.15",
"@ffmpeg/util": "^0.12.2",
"@langchain/anthropic": "^0.3.12",
"@langchain/community": "^0.3.28",
"@langchain/core": "^0.3.38",
"@langchain/google-genai": "^0.1.7",
"@langchain/groq": "^0.1.3",
"@langchain/langgraph": "^0.2.41",
"@langchain/langgraph-sdk": "^0.0.36",
"@langchain/ollama": "^0.1.4",
"@langchain/openai": "^0.4.2",
"@mendable/firecrawl-js": "1.10.1",
"@nextjournal/lang-clojure": "^1.0.0",
"@radix-ui/react-avatar": "^1.1.2",
"@radix-ui/react-accordion": "^1.2.2",
"@radix-ui/react-checkbox": "^1.1.2",
"@radix-ui/react-dialog": "^1.1.5",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-hover-card": "^1.1.2",
"@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-navigation-menu": "^1.2.1",
"@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-progress": "^1.1.0",
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-slider": "^1.2.1",
"@radix-ui/react-slot": "^1.1.1",
"@radix-ui/react-toast": "^1.2.2",
"@radix-ui/react-tooltip": "^1.1.7",
"@replit/codemirror-lang-csharp": "^6.2.0",
"@supabase/ssr": "^0.5.1",
"@supabase/supabase-js": "^2.45.5",
"@types/react-syntax-highlighter": "^15.5.13",
"@uiw/react-codemirror": "^4.23.5",
"@uiw/react-md-editor": "^4.0.4",
"@vercel/kv": "^2.0.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "1.0.0",
"date-fns": "^4.1.0",
"dotenv": "^16.4.5",
"eslint-plugin-unused-imports": "^4.1.4",
"framer-motion": "^11.11.9",
"groq-sdk": "^0.13.0",
"js-cookie": "^3.0.5",
"langchain": "^0.3.14",
"langsmith": "^0.3.3",
"lodash": "^4.17.21",
"lucide-react": "^0.474.0",
"next": "14.2.10",
"pdf-parse": "^1.1.1",
"react": "^18",
"react-colorful": "^5.6.1",
"react-dom": "^18",
"react-icons": "^5.3.0",
"react-json-view": "^1.21.3",
"react-markdown": "^9.0.1",
"react-syntax-highlighter": "^15.5.0",
"rehype-katex": "^7.0.1",
"remark-gfm": "^4.0.0",
"remark-math": "^6.0.0",
"tailwind-merge": "^2.5.2",
"tailwind-scrollbar-hide": "^1.1.7",
"tailwindcss-animate": "^1.0.7",
"uuid": "^10.0.0",
"zod": "^3.23.8",
"zustand": "^5.0.3"
},
"devDependencies": {
"turbo": "latest",
"@eslint/js": "^9.12.0",
"@types/eslint__js": "^8.42.3",
"@types/js-cookie": "^3.0.6",
"@types/lodash": "^4.17.12",
"@types/node": "^20",
"@types/pdf-parse": "^1.1.4",
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/uuid": "^10.0.0",
"@typescript-eslint/eslint-plugin": "^8.12.2",
"@typescript-eslint/parser": "^8.8.1",
"eslint": "^8",
"eslint-config-next": "14.2.10",
"postcss": "^8",
"prettier": "^3.3.3",
"tailwind-scrollbar": "^3.1.0",
"tailwindcss": "^3.4.1",
"tsx": "^4.19.1",
"typescript": "^5",
"typescript-eslint": "^8.8.1",
"vitest": "^3.0.4"
}
}

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Before

Width:  |  Height:  |  Size: 183 KiB

After

Width:  |  Height:  |  Size: 183 KiB

Before

Width:  |  Height:  |  Size: 280 KiB

After

Width:  |  Height:  |  Size: 280 KiB

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

@@ -3,7 +3,7 @@ import {
CUSTOM_QUICK_ACTION_ARTIFACT_PROMPT_PREFIX,
CUSTOM_QUICK_ACTION_CONVERSATION_CONTEXT,
REFLECTIONS_QUICK_ACTION_PROMPT,
} from "@/agent/open-canvas/prompts";
} from "@opencanvas/shared/dist/prompts/quick-actions";
import {
Tooltip,
TooltipContent,

Some files were not shown because too many files have changed in this diff Show More