UNPKG

cranberrry

Version:

AI Agentic UI Framework For Frontend

732 lines (705 loc) 22.8 kB
import { Emitter } from 'mitt'; import * as react$1 from 'react'; import react__default, { ReactNode } from 'react'; import * as react_jsx_runtime from 'react/jsx-runtime'; type CBListener = () => void; interface CBStore { getState: () => CBState; dispatch: (action: CBAction) => void; subscribe: (listener: CBListener) => () => void; } type CBAgentID = string; interface CBAgent { id: CBAgentID; name: string; face?: string; designation?: string; description?: string; introduction?: string; isActive: boolean; isAvailable: boolean; isBusy: boolean; } type CBTaskStatus = 'idle' | 'ongoing' | 'completed' | 'failed'; type CBBlockProcessor = 'TEXT' | 'JSON'; interface CBTagConfig { tag: string; processor: CBBlockProcessor; component: React.ComponentType<{ ai: any; }>; } interface CBMessageBlock { id: string; tag: string; taskId: string; content: any; createdAt: number; component: React.ComponentType<{ ai: any; }>; } interface CBTask { id: string; agentId: CBAgentID; input: string; status: CBTaskStatus; createdAt: number; updatedAt: number; meta?: Record<string, any>; response?: string; messageBlocks?: CBMessageBlock[]; } interface CBState { agents: CBAgent[]; tasks: CBTask[]; } type CBAction = { type: 'ADD_AGENT'; payload: CBAgent; } | { type: 'UPDATE_AGENT'; payload: Partial<CBAgent> & { id: CBAgentID; }; } | { type: 'ADD_TASK'; payload: CBTask; } | { type: 'UPDATE_TASK'; payload: Partial<CBTask> & { id: string; }; } | { type: 'REMOVE_TASK'; payload: { id: string; }; } | { type: 'ADD_MESSAGE_BLOCK'; payload: { taskId: string; block: CBMessageBlock; }; }; type CBBlockCallback<T = any> = (block: T) => void; type CBTaskCallbacks = { [blockTag: string]: CBBlockCallback<any>; } & { onComplete?: () => void; onError?: (error: string) => void; }; type CBStartTaskParams = { agentId: CBAgentID; input: string; callbacks: CBTaskCallbacks; meta?: Record<string, any>; }; declare function CBAgentReducer$1(state: CBState | undefined, action: CBAction): CBState; declare const addAgent$1: (agent: CBAgent) => CBAction; declare const updateAgent$1: (agent: Partial<CBAgent> & { id: string; }) => CBAction; declare const addTask$1: (task: CBTask) => CBAction; declare const updateTask$1: (task: Partial<CBTask> & { id: string; }) => CBAction; declare const removeTask$1: (id: string) => CBAction; declare const addMessageBlock$1: (taskId: string, block: CBMessageBlock) => CBAction; declare const getTasksForAgent$1: (state: CBState, agentId: string) => CBTask[]; type CBBlockSupervisorCallbacks = { onBlockFound?: (tag: string) => void; onBlockProcessed?: (block: CBMessageBlock) => void; onComplete?: () => void; onError?: (err: string) => void; }; declare function createAgentSupervisor$1({ callbacks, tagConfigs, emitter, }: { callbacks: CBBlockSupervisorCallbacks; tagConfigs: CBTagConfig[]; emitter?: Emitter<any>; }): { getStatus: () => CBTaskStatus; startTask: (taskId: string) => void; parseChunk: (chunk: string) => void; complete: () => void; error: (err: string) => void; reset: () => void; }; declare function getTask$1(tasks: CBTask[], id: string): CBTask | undefined; declare function setTaskStatus$1(tasks: CBTask[], id: string, status: CBTaskStatus): CBTask[]; declare function createCBStore$1(reducer: (state: CBState, action: CBAction) => CBState, preloadedState: CBState): CBStore; declare function useCBTaskManager$1(): { tasks: CBTask[]; createTask: (agentId: string, input: string, meta?: Record<string, any>) => string; updateTask: (id: string, updates: Partial<CBTask>) => void; removeTask: (id: string) => void; getTask: (id: string) => CBTask | undefined; setTaskStatus: (id: string, status: CBTaskStatus) => void; addMessageBlockToTask: (taskId: string, block: CBMessageBlock) => void; }; var nanoid = require('nanoid'); var mitt = require('mitt'); var react = require('react'); var jsxRuntime = require('react/jsx-runtime'); function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } var mitt__default = /*#__PURE__*/_interopDefault(mitt); // cranberrry - AI Agentic UI Framework For Frontend // https://github.com/Ritvyk/cranberrry // core/agentSlice.ts var initialState = { agents: [], tasks: [] }; function CBAgentReducer(state = initialState, action) { switch (action.type) { case "ADD_AGENT": return { ...state, agents: [...state.agents, action.payload] }; case "UPDATE_AGENT": return { ...state, agents: state.agents.map( (agent) => agent.id === action.payload.id ? { ...agent, ...action.payload } : agent ) }; case "ADD_TASK": return { ...state, tasks: [...state.tasks, action.payload] }; case "UPDATE_TASK": return { ...state, tasks: state.tasks.map( (task) => task.id === action.payload.id ? { ...task, ...action.payload } : task ) }; case "REMOVE_TASK": return { ...state, tasks: state.tasks.filter((task) => task.id !== action.payload.id) }; case "ADD_MESSAGE_BLOCK": return { ...state, tasks: state.tasks.map( (task) => task.id === action.payload.taskId ? { ...task, messageBlocks: [...task.messageBlocks || [], action.payload.block] } : task ) }; default: return state; } } var addAgent = (agent) => ({ type: "ADD_AGENT", payload: agent }); var updateAgent = (agent) => ({ type: "UPDATE_AGENT", payload: agent }); var addTask = (task) => ({ type: "ADD_TASK", payload: task }); var updateTask = (task) => ({ type: "UPDATE_TASK", payload: task }); var removeTask = (id) => ({ type: "REMOVE_TASK", payload: { id } }); var addMessageBlock = (taskId, block) => ({ type: "ADD_MESSAGE_BLOCK", payload: { taskId, block } }); var getTasksForAgent = (state, agentId) => { return state.tasks.filter((task) => task.agentId === agentId); }; var globalEmitter = typeof window !== "undefined" ? window.__cranberrryEmitter || (window.__cranberrryEmitter = mitt__default.default()) : mitt__default.default(); function createAgentSupervisor({ callbacks, tagConfigs, emitter = globalEmitter }) { let status = "idle"; let buffer = ""; let currentTaskId = ""; let openTagPositions = []; function getStatus() { return status; } function startTask(taskId) { status = "ongoing"; buffer = ""; openTagPositions = []; currentTaskId = taskId; } function parseChunk(chunk) { if (status !== "ongoing") return; buffer += chunk; let foundBlock = false; for (const tagConfig of tagConfigs) { const { tag, processor, component } = tagConfig; const openingTagRegex = new RegExp(`<${tag}>`, "g"); let match; while ((match = openingTagRegex.exec(buffer)) !== null) { if (!openTagPositions.some((t) => t.tag === tag && t.index === match.index)) { openTagPositions.push({ tag, index: match.index }); callbacks.onBlockFound?.(tag); } } const blockRegex = new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`, "g"); while ((match = blockRegex.exec(buffer)) !== null) { let rawContent = match[1]; let content = rawContent; if (processor === "JSON") { try { content = JSON.parse(rawContent); } catch { content = rawContent; } } const block = { id: nanoid.nanoid(), tag, content, createdAt: Date.now(), component, taskId: currentTaskId }; callbacks.onBlockProcessed?.(block); emitter.emit("message", { taskId: currentTaskId, block }); buffer = buffer.slice(0, match.index) + buffer.slice(match.index + match[0].length); openTagPositions = openTagPositions.filter((t) => !(t.tag === tag && t.index === match.index)); blockRegex.lastIndex = 0; foundBlock = true; } } if (foundBlock) { parseChunk(""); } else if (chunk === "" && buffer === "" && status === "ongoing") { status = "completed"; callbacks.onComplete?.(); } } function complete() { status = "completed"; callbacks.onComplete?.(); } function error(err) { status = "failed"; callbacks.onError?.(err); } function reset() { status = "idle"; buffer = ""; openTagPositions = []; } return { getStatus, startTask, parseChunk, complete, error, reset }; } // core/agentTaskManager.ts function getTask(tasks, id) { return tasks.find((task) => task.id === id); } function setTaskStatus(tasks, id, status) { return tasks.map((task) => task.id === id ? { ...task, status } : task); } // core/store.ts function createCBStore(reducer, preloadedState) { let state = preloadedState; let listeners = []; function getState() { return state; } function dispatch(action) { state = reducer(state, action); listeners.forEach((listener) => listener()); } function subscribe(listener) { listeners.push(listener); return () => { const index = listeners.indexOf(listener); if (index > -1) listeners.splice(index, 1); }; } return { getState, dispatch, subscribe }; } var CranberrryStoreContext = react.createContext(null); CranberrryStoreContext.displayName = "CranberrryStoreContext"; function CranberrryProvider$1({ store, children }) { return /* @__PURE__ */ jsxRuntime.jsx(CranberrryStoreContext.Provider, { value: store, children }); } function useCranberrryStore$1() { const store = react.useContext(CranberrryStoreContext); if (!store) { throw new Error("useCranberrryStore must be used within a CranberrryProvider"); } return store; } // hooks/useAgentDispatch.ts function useCBDispatch$1() { return useCranberrryStore$1().dispatch; } function useCBSelector$1(selector) { const store = useCranberrryStore$1(); const [selected, setSelected] = react.useState(() => selector(store.getState())); const latestSelected = react.useRef(selected); react.useEffect(() => { function checkForUpdates() { const newSelected = selector(store.getState()); if (newSelected !== latestSelected.current) { latestSelected.current = newSelected; setSelected(newSelected); } } const unsubscribe = store.subscribe(checkForUpdates); return unsubscribe; }, [store, selector]); return selected; } var useAgentSelector$1 = useCBSelector$1; // hooks/useAgentTaskManager.ts function useCBTaskManager() { const dispatch = useCBDispatch$1(); const tasks = useCBSelector$1((state) => state.tasks); const createTask = react.useCallback((agentId, input, meta) => { const id = Math.random().toString(36).substr(2, 9); const now = Date.now(); dispatch( addTask({ id, agentId, input, status: "ongoing", createdAt: now, updatedAt: now, meta }) ); return id; }, [dispatch]); const updateTask2 = react.useCallback((id, updates) => { dispatch(updateTask({ id, ...updates })); }, [dispatch]); const removeTask2 = react.useCallback((id) => { dispatch(removeTask(id)); }, [dispatch]); const getTask2 = react.useCallback((id) => { return tasks.find((task) => task.id === id); }, [tasks]); const setTaskStatus2 = react.useCallback((id, status) => { dispatch(updateTask({ id, status, updatedAt: Date.now() })); }, [dispatch]); const addMessageBlockToTask = react.useCallback((taskId, block) => { dispatch(addMessageBlock(taskId, block)); }, [dispatch]); return { tasks, createTask, updateTask: updateTask2, removeTask: removeTask2, getTask: getTask2, setTaskStatus: setTaskStatus2, addMessageBlockToTask }; } // hooks/useAIAgent.ts function useCBAgent$1() { const agents = useCBSelector$1((state) => state.agents); const getAgentById = (id) => agents.find((agent) => agent.id === id); return { agents, getAgentById }; } var globalEmitter2 = typeof window !== "undefined" ? window.__cranberrryEmitter || (window.__cranberrryEmitter = mitt__default.default()) : mitt__default.default(); function useCBTaskSupervisor$1({ callbacks, tagConfigs, emitter = globalEmitter2 }) { const supervisorRef = react.useRef(null); const [status, setStatus] = react.useState("idle"); const [error, setError] = react.useState(null); if (!supervisorRef.current) { supervisorRef.current = createAgentSupervisor({ callbacks, tagConfigs, emitter }); } const startTask = react.useCallback((taskId) => { supervisorRef.current?.startTask(taskId); setStatus("ongoing"); setError(null); }, []); const parseChunk = react.useCallback((chunk) => { supervisorRef.current?.parseChunk(chunk); }, []); const complete = react.useCallback(() => { supervisorRef.current?.complete(); setStatus("completed"); }, []); const errorCallback = react.useCallback((err) => { supervisorRef.current?.error(err); setStatus("failed"); setError(err); }, []); const reset = react.useCallback(() => { supervisorRef.current?.reset(); setStatus("idle"); setError(null); }, []); return { status, startTask, parseChunk, complete, error, errorCallback, reset }; } var globalEmitter3 = typeof window !== "undefined" ? window.__cranberrryEmitter || (window.__cranberrryEmitter = mitt__default.default()) : mitt__default.default(); function useCBController$1({ agentId, tagConfigs, callbacks }) { const [prompt, setPrompt] = react.useState(""); const [errorMessage, setErrorMessage] = react.useState(null); const [currentTaskId, setCurrentTaskId] = react.useState(null); const currentTaskIdRef = react.useRef(null); const dispatch = useCBDispatch$1(); const emitter = globalEmitter3; const { tasks, createTask, updateTask: updateTask2, removeTask: removeTask2, getTask: getTask2, setTaskStatus: setTaskStatus2, addMessageBlockToTask // addMessageBlock, getMessageBlocks imported above } = useCBTaskManager(); currentTaskIdRef.current = currentTaskId; const wrappedCallbacks = { ...callbacks, onComplete: () => { if (currentTaskIdRef.current) { updateTask2(currentTaskIdRef.current, { status: "completed", updatedAt: Date.now() }); } dispatch(updateAgent({ id: agentId, isBusy: false })); callbacks.onComplete?.(); }, onError: (err) => { if (currentTaskIdRef.current) { updateTask2(currentTaskIdRef.current, { status: "failed", updatedAt: Date.now() }); } dispatch(updateAgent({ id: agentId, isBusy: false })); callbacks.onError?.(err); } }; const [messageBlocks, setMessageBlocks] = react.useState([]); react.useEffect(() => { if (currentTaskId) { setMessageBlocks(getTask2(currentTaskId)?.messageBlocks || []); } else { setMessageBlocks([]); } }, [currentTaskId, tasks]); const supervisor = useCBTaskSupervisor$1({ callbacks: { ...wrappedCallbacks, onBlockProcessed: (block) => { if (currentTaskIdRef.current) { addMessageBlockToTask(currentTaskIdRef.current, block); } callbacks.onBlockProcessed?.(block); }, onBlockFound: (tag) => { callbacks.onBlockFound?.(tag); } }, tagConfigs, emitter }); const { status, startTask, parseChunk, complete, error: supervisorError, errorCallback, reset } = supervisor; const isRunning = status === "ongoing"; const startAgentTask = react.useCallback( (newPrompt, newMeta) => { if (!newPrompt) throw new Error("Prompt is required"); setPrompt(newPrompt); setErrorMessage(null); const newTaskId = createTask(agentId, newPrompt, newMeta); setCurrentTaskId(newTaskId); currentTaskIdRef.current = newTaskId; dispatch(updateAgent({ id: agentId, isBusy: true })); startTask(newTaskId); return newTaskId; }, [startTask, createTask, tagConfigs, agentId, dispatch] ); const startExistingAgentTask = react.useCallback( (taskId) => { const existingTask = getTask2(taskId); if (!existingTask) { throw new Error(`Task with ID ${taskId} not found`); } if (existingTask.agentId !== agentId) { throw new Error(`Task ${taskId} does not belong to agent ${agentId}`); } if (existingTask.status === "completed") { throw new Error(`Task ${taskId} is already completed and cannot be resumed`); } if (existingTask.status === "failed") { updateTask2(taskId, { status: "ongoing", updatedAt: Date.now() }); } setPrompt(existingTask.input); setErrorMessage(null); setCurrentTaskId(taskId); currentTaskIdRef.current = taskId; dispatch(updateAgent({ id: agentId, isBusy: true })); startTask(taskId); return taskId; }, [getTask2, agentId, updateTask2, startTask, dispatch] ); const completeWithError = react.useCallback((err) => { setErrorMessage(err); callbacks.onError?.(err); errorCallback(err); }, [callbacks, errorCallback]); return { prompt, setPrompt, isRunning, error: errorMessage || supervisorError, startAgentTask, startExistingAgentTask, parseChunk, complete, reset, status, tasks, updateTask: updateTask2, removeTask: removeTask2, getTask: getTask2, setTaskStatus: setTaskStatus2, completeWithError, messageBlocks, emitter }; } var globalEmitter4 = typeof window !== "undefined" ? window.__cranberrryEmitter || (window.__cranberrryEmitter = mitt__default.default()) : mitt__default.default(); var CranberrryRenderer$1 = ({ taskId }) => { const messageBlocks = useAgentSelector$1( (state) => state.tasks.find((t) => t.id === taskId)?.messageBlocks || [] ); const [liveBlocks, setLiveBlocks] = react.useState(messageBlocks); react.useEffect(() => { setLiveBlocks(messageBlocks); }, [messageBlocks]); react.useEffect(() => { function onMessage({ taskId: incomingTaskId, block }) { if (incomingTaskId === taskId) { setLiveBlocks((prev) => [...prev, block]); } } globalEmitter4.on("message", onMessage); return () => { globalEmitter4.off("message", onMessage); }; }, [taskId]); return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cranberrry-renderer-messages space-y-2", children: liveBlocks.map((block) => { const BlockComponent = block.component; return /* @__PURE__ */ jsxRuntime.jsx(BlockComponent, { ai: block.content }, block.id); }) }); }; exports.CBAgentReducer = CBAgentReducer; exports.CranberrryProvider = CranberrryProvider$1; exports.CranberrryRenderer = CranberrryRenderer$1; exports.addAgent = addAgent; exports.addMessageBlock = addMessageBlock; exports.addTask = addTask; exports.createAgentSupervisor = createAgentSupervisor; exports.createCBStore = createCBStore; exports.getTask = getTask; exports.getTasksForAgent = getTasksForAgent; exports.removeTask = removeTask; exports.setTaskStatus = setTaskStatus; exports.updateAgent = updateAgent; exports.updateTask = updateTask; exports.useAgentSelector = useAgentSelector$1; exports.useCBAgent = useCBAgent$1; exports.useCBController = useCBController$1; exports.useCBDispatch = useCBDispatch$1; exports.useCBSelector = useCBSelector$1; exports.useCBTaskManager = useCBTaskManager; exports.useCBTaskSupervisor = useCBTaskSupervisor$1; exports.useCranberrryStore = useCranberrryStore$1; declare function useCBDispatch(): (action: undefined) => void; declare function useCBSelector<T>(selector: (state: CBState) => T): T; declare const useAgentSelector: typeof useCBSelector; declare function useCBAgent(): { agents: CBAgent[]; getAgentById: (id: string) => CBAgent | undefined; }; declare function useCBController({ agentId, tagConfigs, callbacks, }: { agentId: string; tagConfigs: CBTagConfig[]; callbacks: CBBlockSupervisorCallbacks; }): { prompt: string; setPrompt: react$1.Dispatch<react$1.SetStateAction<string>>; isRunning: boolean; error: string | null; startAgentTask: (newPrompt: string, newMeta?: Record<string, any>) => string; startExistingAgentTask: (taskId: string) => string; parseChunk: (chunk: string) => void; complete: () => void; reset: () => void; status: undefined; tasks: undefined[]; updateTask: (id: string, updates: Partial<undefined>) => void; removeTask: (id: string) => void; getTask: (id: string) => undefined | undefined; setTaskStatus: (id: string, status: undefined) => void; completeWithError: (err: string) => void; messageBlocks: CBMessageBlock[]; emitter: Emitter<any>; }; declare function useCBTaskSupervisor({ callbacks, tagConfigs, emitter, }: { callbacks: CBBlockSupervisorCallbacks; tagConfigs: CBTagConfig[]; emitter?: Emitter<any>; }): { status: CBTaskStatus; startTask: (taskId: string) => void; parseChunk: (chunk: string) => void; complete: () => void; error: string | null; errorCallback: (err: string) => void; reset: () => void; }; declare function CranberrryProvider({ store, children, }: { store: CBStore; children: ReactNode; }): react_jsx_runtime.JSX.Element; declare function useCranberrryStore(): CBStore; interface CranberrryRendererProps { taskId: string; } declare const CranberrryRenderer: react__default.FC<CranberrryRendererProps>; export { type CBAction, type CBAgent, type CBAgentID, CBAgentReducer$1 as CBAgentReducer, type CBBlockCallback, type CBBlockProcessor, type CBBlockSupervisorCallbacks, type CBListener, type CBMessageBlock, type CBStartTaskParams, type CBState, type CBStore, type CBTagConfig, type CBTask, type CBTaskCallbacks, type CBTaskStatus, CranberrryProvider, CranberrryRenderer, addAgent$1 as addAgent, addMessageBlock$1 as addMessageBlock, addTask$1 as addTask, createAgentSupervisor$1 as createAgentSupervisor, createCBStore$1 as createCBStore, getTask$1 as getTask, getTasksForAgent$1 as getTasksForAgent, removeTask$1 as removeTask, setTaskStatus$1 as setTaskStatus, updateAgent$1 as updateAgent, updateTask$1 as updateTask, useAgentSelector, useCBAgent, useCBController, useCBDispatch, useCBSelector, useCBTaskManager$1 as useCBTaskManager, useCBTaskSupervisor, useCranberrryStore };