diff --git a/chainforge/flask_app.py b/chainforge/flask_app.py index dd58d11b3..d79c2ccf9 100644 --- a/chainforge/flask_app.py +++ b/chainforge/flask_app.py @@ -487,6 +487,7 @@ def fetchEnvironAPIKeys(): 'AWS_SESSION_TOKEN': 'AWS_Session_Token', 'TOGETHER_API_KEY': 'Together', 'DEEPSEEK_API_KEY': 'DeepSeek', + 'MINIMAX_API_KEY': 'MiniMax', } d = { alias: os.environ.get(key) for key, alias in keymap.items() } ret = jsonify(d) diff --git a/chainforge/react-server/craco.config.js b/chainforge/react-server/craco.config.js index 72e996b17..d951d95b0 100644 --- a/chainforge/react-server/craco.config.js +++ b/chainforge/react-server/craco.config.js @@ -9,6 +9,14 @@ module.exports = { }, webpack: { configure: { + // WebLLM currently publishes sourcemap references to TS sources that are + // not included in the npm package. Ignore only those warnings. + ignoreWarnings: [ + { + module: /@mlc-ai\/web-llm/, + message: /Failed to parse source map/, + }, + ], resolve: { fallback: { process: require.resolve("process/browser"), diff --git a/chainforge/react-server/package-lock.json b/chainforge/react-server/package-lock.json index 57ab72576..45a703745 100644 --- a/chainforge/react-server/package-lock.json +++ b/chainforge/react-server/package-lock.json @@ -22,6 +22,7 @@ "@mantine/form": "^6.0.11", "@mantine/prism": "^6.0.15", "@mirai73/bedrock-fm": "^0.4.10", + "@mlc-ai/web-llm": "^0.2.84", "@reactflow/background": "^11.2.0", "@reactflow/controls": "^11.1.11", "@reactflow/core": "^11.7.0", @@ -4740,6 +4741,15 @@ "@aws-sdk/client-bedrock-runtime": "^3.507.0" } }, + "node_modules/@mlc-ai/web-llm": { + "version": "0.2.84", + "resolved": "https://registry.npmjs.org/@mlc-ai/web-llm/-/web-llm-0.2.84.tgz", + "integrity": "sha512-hrOWzK4/nGNmgoRKT8pgVmZZ2oEPpbblIWQOwpqNyvK2dysHw3KVB1gNJOuRcQfKOPhucEhX1NJzXzgMDnwSCQ==", + "license": "Apache-2.0", + "dependencies": { + "loglevel": "^1.9.1" + } + }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -17488,6 +17498,19 @@ "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" }, + "node_modules/loglevel": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", + "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", diff --git a/chainforge/react-server/package.json b/chainforge/react-server/package.json index d47badb9f..16d61faf5 100644 --- a/chainforge/react-server/package.json +++ b/chainforge/react-server/package.json @@ -20,6 +20,7 @@ "@mantine/form": "^6.0.11", "@mantine/prism": "^6.0.15", "@mirai73/bedrock-fm": "^0.4.10", + "@mlc-ai/web-llm": "^0.2.84", "@reactflow/background": "^11.2.0", "@reactflow/controls": "^11.1.11", "@reactflow/core": "^11.7.0", diff --git a/chainforge/react-server/src/App.tsx b/chainforge/react-server/src/App.tsx index e1b5a4f54..ad72926ad 100644 --- a/chainforge/react-server/src/App.tsx +++ b/chainforge/react-server/src/App.tsx @@ -67,6 +67,7 @@ import { getDefaultModelFormData, getDefaultModelSettings, } from "./ModelSettingSchemas"; +import { NativeLLM } from "./backend/models"; import { v4 as uuid } from "uuid"; import axios from "axios"; import LZString from "lz-string"; @@ -112,6 +113,7 @@ import { isEdgeChromium, isChromium, isMobileSafari, + isSafari, } from "react-device-detect"; import MultiEvalNode from "./MultiEvalNode"; import FlowSidebar from "./FlowSidebar"; @@ -125,6 +127,7 @@ const IS_ACCEPTED_BROWSER = isChromium || isEdgeChromium || isFirefox || + isSafari || (navigator as any)?.brave !== undefined) && (!isMobile || (isTablet && !isMobileSafari)); @@ -167,37 +170,19 @@ const selector = (state: StoreHandles) => ({ // The initial LLM to use when new flows are created, or upon first load const INITIAL_LLM = () => { - if (!IS_RUNNING_LOCALLY) { - // Prefer HF if running on server, as it's free. - const falcon7b = { - key: uuid(), - name: "Mistral-7B", - emoji: "🤗", - model: "mistralai/Mistral-7B-Instruct-v0.1", - base_model: "hf", - temp: 1.0, - settings: getDefaultModelSettings("hf"), - formData: getDefaultModelFormData("hf"), - } satisfies LLMSpec; - falcon7b.formData.shortname = falcon7b.name; - falcon7b.formData.model = falcon7b.model; - return falcon7b; - } else { - // Prefer OpenAI for majority of local users. - const chatgpt = { - key: uuid(), - name: "GPT-4o-mini", - emoji: "🤖", - model: "gpt-4o-mini", - base_model: "gpt-4", - temp: 1.0, - settings: getDefaultModelSettings("gpt-4"), - formData: getDefaultModelFormData("gpt-4"), - } satisfies LLMSpec; - chatgpt.formData.shortname = chatgpt.name; - chatgpt.formData.model = chatgpt.model; - return chatgpt; - } + const qwenWebLLM = { + key: uuid(), + name: "Qwen2.5 0.5B", + emoji: "🌐", + model: NativeLLM.WebLLM_Qwen2_5_0_5B, + base_model: "webllm", + temp: 0.7, + settings: getDefaultModelSettings("webllm"), + formData: getDefaultModelFormData("webllm"), + } satisfies LLMSpec; + qwenWebLLM.formData.shortname = qwenWebLLM.name; + qwenWebLLM.formData.model = qwenWebLLM.model; + return qwenWebLLM; }; const nodeTypes = { @@ -1497,6 +1482,7 @@ const App = () => { Mozilla Firefox Microsoft Edge (Chromium) Brave + Safari @@ -1677,8 +1663,9 @@ const App = () => { variant={colorScheme === "light" ? "gradient" : "filled"} color={colorScheme === "light" ? "blue" : "gray"} compact + style={{ width: "32px", minWidth: "32px", padding: 0 }} > - +
= ({ {icon} {text} ))} - + { + handleDuplicateNode(); + setContextMenuOpened(false); + }} + >  Duplicate Node {IS_RUNNING_LOCALLY && ( setFavoriteNameModalOpen(true)} + onClick={() => { + setFavoriteNameModalOpen(true); + setContextMenuOpened(false); + }} > = ({  Favorite Node )} - + { + handleRemoveNode(); + setContextMenuOpened(false); + }} + >  Delete Node diff --git a/chainforge/react-server/src/CodeEvaluatorNode.tsx b/chainforge/react-server/src/CodeEvaluatorNode.tsx index ee0149dfa..2a5f5f026 100644 --- a/chainforge/react-server/src/CodeEvaluatorNode.tsx +++ b/chainforge/react-server/src/CodeEvaluatorNode.tsx @@ -48,6 +48,7 @@ import { toStandardResponseFormat, } from "./backend/utils"; import InspectFooter from "./InspectFooter"; +import ResizeHandle from "./ResizeHandle"; import { escapeBraces } from "./backend/template"; import LLMResponseInspectorDrawer from "./LLMResponseInspectorDrawer"; import { AIGenCodeEvaluatorPopover } from "./AiPopover"; @@ -243,6 +244,8 @@ export const CodeEvaluatorComponent = forwardRef< const [codeTextOnLastRun, setCodeTextOnLastRun] = useState( false, ); + const aceEditorRef = useRef(null); + const aceContainerRef = useRef(null); // Color theme const { colorScheme } = useMantineColorScheme(); @@ -352,7 +355,11 @@ export const CodeEvaluatorComponent = forwardRef< return (
{showUserInstruction ? code_instruct_header : <>} -
+
{ - // Make Ace Editor div resizeable. - editorInstance.container.style.resize = "both"; - document.addEventListener("mouseup", () => editorInstance.resize()); + aceEditorRef.current = editorInstance; }} /> + aceEditorRef.current?.resize()} + />
); diff --git a/chainforge/react-server/src/InspectorNode.tsx b/chainforge/react-server/src/InspectorNode.tsx index 86f8bd6cc..340348ef3 100644 --- a/chainforge/react-server/src/InspectorNode.tsx +++ b/chainforge/react-server/src/InspectorNode.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useContext } from "react"; +import React, { useState, useEffect, useContext, useRef } from "react"; import { Handle, Position } from "reactflow"; import useStore from "./store"; import BaseNode from "./BaseNode"; @@ -7,6 +7,7 @@ import LLMResponseInspector, { exportToExcel } from "./LLMResponseInspector"; import { grabResponses } from "./backend/backend"; import { LLMResponse } from "./backend/typing"; import { AlertModalContext } from "./AlertModal"; +import ResizeHandle from "./ResizeHandle"; export interface InspectorNodeProps { data: { @@ -28,6 +29,7 @@ const InspectorNode: React.FC = ({ data, id }) => { const inputEdgesForNode = useStore((state) => state.inputEdgesForNode); const setDataPropsForNode = useStore((state) => state.setDataPropsForNode); const showAlert = useContext(AlertModalContext); + const containerRef = useRef(null); const handleOnConnect = () => { // For some reason, 'on connect' is called twice upon connection. @@ -89,6 +91,7 @@ const InspectorNode: React.FC = ({ data, id }) => { ]} />
@@ -97,6 +100,7 @@ const InspectorNode: React.FC = ({ data, id }) => { isOpen={true} wideFormat={false} /> +
- - - {hideTrashIcon ? ( - <> - ) : ( - - )} + + + {hideTrashIcon ? ( + <> + ) : ( - -
+ )} + + ); } diff --git a/chainforge/react-server/src/LLMListComponent.tsx b/chainforge/react-server/src/LLMListComponent.tsx index e54cd94e7..24bd8cd99 100644 --- a/chainforge/react-server/src/LLMListComponent.tsx +++ b/chainforge/react-server/src/LLMListComponent.tsx @@ -24,6 +24,7 @@ import ModelSettingsModal, { ModelSettingsModalRef, } from "./ModelSettingsModal"; import { getDefaultModelSettings } from "./ModelSettingSchemas"; +import { NativeLLM } from "./backend/models"; import useStore, { initLLMProviders, initLLMProviderMenu } from "./store"; import { Dict, JSONCompatible, LLMGroup, LLMSpec } from "./backend/typing"; import { ContextMenuItemOptions } from "mantine-contextmenu/dist/types"; @@ -31,9 +32,9 @@ import { deepcopy, ensureUniqueName } from "./backend/utils"; import NestedMenu, { NestedMenuItemProps } from "./NestedMenu"; // The LLM(s) to include by default on a PromptNode whenever one is created. -// Defaults to a cheap non-reasoning OpenAI model. +// Defaults to an in-browser Qwen 2.5 model. const DEFAULT_INIT_LLMS = [ - initLLMProviders.find((m) => m.model === "gpt-4o-mini")!, + initLLMProviders.find((m) => m.model === NativeLLM.WebLLM_Qwen2_5_0_5B)!, ]; // Helper funcs @@ -499,7 +500,6 @@ export const LLMListContainer = forwardRef< items={menuItems} button={(closeMenu) => (