@datalayer/agent-runtimes 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/chat/protocols/VercelAIAdapter.d.ts +2 -0
- package/lib/components/chat/protocols/VercelAIAdapter.js +5 -0
- package/lib/examples/AgentRuntimeCustomExample.d.ts +2 -1
- package/lib/examples/AgentRuntimeCustomExample.js +120 -5
- package/lib/hooks/useNotebookAIAgent.d.ts +2 -2
- package/lib/hooks/useNotebookAIAgent.js +26 -9
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/state/substates/AIAgentState.d.ts +80 -10
- package/lib/state/substates/AIAgentState.js +89 -23
- package/package.json +10 -8
- package/patches/.gitkeep +1 -0
- package/scripts/apply-patches.sh +26 -0
- package/scripts/create-patches.sh +40 -0
- package/scripts/download-ai-elements.py +86 -0
- package/scripts/sync-jupyter.sh +123 -0
|
@@ -61,6 +61,8 @@ export declare class VercelAIAdapter extends BaseProtocolAdapter {
|
|
|
61
61
|
model?: string;
|
|
62
62
|
/** Full conversation history to send with the message */
|
|
63
63
|
messages?: ChatMessage[];
|
|
64
|
+
/** Builtin tools / MCP tools to enable for this request */
|
|
65
|
+
builtinTools?: string[];
|
|
64
66
|
}): Promise<void>;
|
|
65
67
|
/**
|
|
66
68
|
* Parse SSE stream from Vercel AI
|
|
@@ -107,6 +107,11 @@ export class VercelAIAdapter extends BaseProtocolAdapter {
|
|
|
107
107
|
...(options?.tools && { tools: options.tools }),
|
|
108
108
|
// Model override for per-request model selection
|
|
109
109
|
...(options?.model && { model: options.model }),
|
|
110
|
+
// Builtin tools / MCP tools to enable
|
|
111
|
+
...(options?.builtinTools &&
|
|
112
|
+
options.builtinTools.length > 0 && {
|
|
113
|
+
builtinTools: options.builtinTools,
|
|
114
|
+
}),
|
|
110
115
|
};
|
|
111
116
|
if (options?.model) {
|
|
112
117
|
console.log('[VercelAIAdapter] Sending with model:', options.model);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent Runtime Custom Example Component
|
|
3
3
|
*
|
|
4
|
-
* Demonstrates the unified Chat component with
|
|
4
|
+
* Demonstrates the unified Chat component with AG-UI transport
|
|
5
5
|
* and all necessary providers:
|
|
6
6
|
* - QueryClientProvider for data fetching
|
|
7
|
+
* - Auto-creates agent on the server if it doesn't exist
|
|
7
8
|
*/
|
|
8
9
|
declare const AgentRuntimeCustomExample: React.FC;
|
|
9
10
|
export default AgentRuntimeCustomExample;
|
|
@@ -3,7 +3,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
4
4
|
* Distributed under the terms of the Modified BSD License.
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
6
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
7
|
+
import { Spinner, Text } from '@primer/react';
|
|
7
8
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
8
9
|
import { Box } from '@datalayer/primer-addons';
|
|
9
10
|
import { datalayerTheme, DatalayerThemeProvider } from '@datalayer/core';
|
|
@@ -19,14 +20,107 @@ const queryClient = new QueryClient({
|
|
|
19
20
|
},
|
|
20
21
|
},
|
|
21
22
|
});
|
|
23
|
+
const AGENT_NAME = 'datalayer-assistant';
|
|
24
|
+
const BASE_URL = 'http://localhost:8765';
|
|
25
|
+
/**
|
|
26
|
+
* Hook to ensure the agent exists on the server
|
|
27
|
+
* Creates the agent if it doesn't exist
|
|
28
|
+
*/
|
|
29
|
+
function useEnsureAgent() {
|
|
30
|
+
const [agentId, setAgentId] = useState(null);
|
|
31
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
32
|
+
const [error, setError] = useState(null);
|
|
33
|
+
const createAgent = useCallback(async () => {
|
|
34
|
+
try {
|
|
35
|
+
const response = await fetch(`${BASE_URL}/api/v1/agents`, {
|
|
36
|
+
method: 'POST',
|
|
37
|
+
headers: {
|
|
38
|
+
'Content-Type': 'application/json',
|
|
39
|
+
},
|
|
40
|
+
body: JSON.stringify({
|
|
41
|
+
name: AGENT_NAME,
|
|
42
|
+
description: 'Datalayer AI Assistant - Your helpful coding companion',
|
|
43
|
+
agent_library: 'pydantic-ai',
|
|
44
|
+
transport: 'ag-ui',
|
|
45
|
+
model: 'openai:gpt-4o-mini',
|
|
46
|
+
system_prompt: `You are Datalayer Assistant, a helpful AI assistant specialized in data science, Python programming, and Jupyter notebooks.
|
|
47
|
+
|
|
48
|
+
You can help users with:
|
|
49
|
+
- Writing and debugging Python code
|
|
50
|
+
- Data analysis with pandas, numpy, and other libraries
|
|
51
|
+
- Creating visualizations with matplotlib, plotly, etc.
|
|
52
|
+
- Machine learning with scikit-learn, pytorch, etc.
|
|
53
|
+
- General programming questions
|
|
54
|
+
|
|
55
|
+
Be concise, helpful, and provide working code examples when appropriate.`,
|
|
56
|
+
}),
|
|
57
|
+
});
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
const errorData = await response
|
|
60
|
+
.json()
|
|
61
|
+
.catch(() => ({ detail: 'Unknown error' }));
|
|
62
|
+
throw new Error(errorData.detail || `Failed to create agent: ${response.status}`);
|
|
63
|
+
}
|
|
64
|
+
const data = await response.json();
|
|
65
|
+
console.log('[AgentRuntimeCustomExample] Agent created:', data);
|
|
66
|
+
return data.id;
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
console.error('[AgentRuntimeCustomExample] Error creating agent:', err);
|
|
70
|
+
throw err;
|
|
71
|
+
}
|
|
72
|
+
}, []);
|
|
73
|
+
const checkOrCreateAgent = useCallback(async () => {
|
|
74
|
+
setIsLoading(true);
|
|
75
|
+
setError(null);
|
|
76
|
+
try {
|
|
77
|
+
// First, try to find existing agent by name
|
|
78
|
+
const listResponse = await fetch(`${BASE_URL}/api/v1/agents`);
|
|
79
|
+
if (listResponse.ok) {
|
|
80
|
+
const data = await listResponse.json();
|
|
81
|
+
// API may return { agents: [...] } or just [...]
|
|
82
|
+
const agents = Array.isArray(data) ? data : data.agents || [];
|
|
83
|
+
const existingAgent = agents.find((a) => a.name === AGENT_NAME);
|
|
84
|
+
if (existingAgent) {
|
|
85
|
+
console.log('[AgentRuntimeCustomExample] Found existing agent:', existingAgent.id);
|
|
86
|
+
setAgentId(existingAgent.id);
|
|
87
|
+
setIsLoading(false);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Agent doesn't exist, create it
|
|
92
|
+
console.log('[AgentRuntimeCustomExample] Creating new agent...');
|
|
93
|
+
const newAgentId = await createAgent();
|
|
94
|
+
if (newAgentId) {
|
|
95
|
+
setAgentId(newAgentId);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
setError('Failed to create agent');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to initialize agent';
|
|
103
|
+
setError(errorMessage);
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
setIsLoading(false);
|
|
107
|
+
}
|
|
108
|
+
}, [createAgent]);
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
checkOrCreateAgent();
|
|
111
|
+
}, [checkOrCreateAgent]);
|
|
112
|
+
return { agentId, isLoading, error, retry: checkOrCreateAgent };
|
|
113
|
+
}
|
|
22
114
|
/**
|
|
23
115
|
* Agent Runtime Custom Example Component
|
|
24
116
|
*
|
|
25
|
-
* Demonstrates the unified Chat component with
|
|
117
|
+
* Demonstrates the unified Chat component with AG-UI transport
|
|
26
118
|
* and all necessary providers:
|
|
27
119
|
* - QueryClientProvider for data fetching
|
|
120
|
+
* - Auto-creates agent on the server if it doesn't exist
|
|
28
121
|
*/
|
|
29
122
|
const AgentRuntimeCustomExample = () => {
|
|
123
|
+
const { agentId, isLoading, error, retry } = useEnsureAgent();
|
|
30
124
|
return (_jsx(QueryClientProvider, { client: queryClient, children: _jsx(DatalayerThemeProvider, { theme: datalayerTheme, children: _jsxs(Box, { sx: {
|
|
31
125
|
display: 'flex',
|
|
32
126
|
flexDirection: 'column',
|
|
@@ -41,12 +135,33 @@ const AgentRuntimeCustomExample = () => {
|
|
|
41
135
|
fontWeight: 'bold',
|
|
42
136
|
display: 'block',
|
|
43
137
|
marginBottom: 1,
|
|
44
|
-
}, children: "
|
|
138
|
+
}, children: "Datalayer Assistant" }), _jsx(Text, { sx: { fontSize: 1, color: 'fg.muted' }, children: "Interactive chat interface with AI assistance" })] }), _jsx(Box, { as: "main", sx: {
|
|
45
139
|
flex: 1,
|
|
46
140
|
overflow: 'hidden',
|
|
47
141
|
display: 'flex',
|
|
48
142
|
flexDirection: 'column',
|
|
49
|
-
}, children:
|
|
143
|
+
}, children: isLoading ? (_jsxs(Box, { sx: {
|
|
144
|
+
display: 'flex',
|
|
145
|
+
flexDirection: 'column',
|
|
146
|
+
alignItems: 'center',
|
|
147
|
+
justifyContent: 'center',
|
|
148
|
+
height: '100%',
|
|
149
|
+
gap: 3,
|
|
150
|
+
}, children: [_jsx(Spinner, { size: "large" }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Initializing agent..." })] })) : error ? (_jsxs(Box, { sx: {
|
|
151
|
+
display: 'flex',
|
|
152
|
+
flexDirection: 'column',
|
|
153
|
+
alignItems: 'center',
|
|
154
|
+
justifyContent: 'center',
|
|
155
|
+
height: '100%',
|
|
156
|
+
gap: 3,
|
|
157
|
+
p: 4,
|
|
158
|
+
}, children: [_jsx(Text, { sx: { color: 'danger.fg', fontWeight: 'bold' }, children: "Failed to initialize agent" }), _jsx(Text, { sx: { color: 'fg.muted', textAlign: 'center' }, children: error }), _jsx(Text, { as: "button", onClick: retry, sx: {
|
|
159
|
+
color: 'accent.fg',
|
|
160
|
+
cursor: 'pointer',
|
|
161
|
+
textDecoration: 'underline',
|
|
162
|
+
border: 'none',
|
|
163
|
+
background: 'none',
|
|
164
|
+
}, children: "Retry" })] })) : agentId ? (_jsx(Chat, { transport: "ag-ui", agentId: agentId, showModelSelector: true, showToolsMenu: true, height: "100%", suggestions: [
|
|
50
165
|
{
|
|
51
166
|
title: '👋 Say hello',
|
|
52
167
|
message: 'Hello! What can you help me with today?',
|
|
@@ -63,6 +178,6 @@ const AgentRuntimeCustomExample = () => {
|
|
|
63
178
|
title: '📊 Data analysis',
|
|
64
179
|
message: 'How do I analyze data with pandas?',
|
|
65
180
|
},
|
|
66
|
-
] }) })] }) }) }));
|
|
181
|
+
] })) : null })] }) }) }));
|
|
67
182
|
};
|
|
68
183
|
export default AgentRuntimeCustomExample;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Agent } from '../state';
|
|
2
2
|
/**
|
|
3
3
|
* Get the document AI Agent if any.
|
|
4
4
|
*
|
|
5
5
|
* It handles checking the AI Agent is alive so it should only be use once per document.
|
|
6
6
|
*/
|
|
7
|
-
export declare function useNotebookAIAgent(notebookId: string):
|
|
7
|
+
export declare function useNotebookAIAgent(notebookId: string): Agent | undefined;
|
|
8
8
|
export default useNotebookAIAgent;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
import { useEffect } from 'react';
|
|
6
|
-
import {
|
|
6
|
+
import { useAgentStore } from '../state';
|
|
7
7
|
import { useAIAgents } from './useAgents';
|
|
8
8
|
/**
|
|
9
9
|
* Get the document AI Agent if any.
|
|
@@ -12,7 +12,10 @@ import { useAIAgents } from './useAgents';
|
|
|
12
12
|
*/
|
|
13
13
|
export function useNotebookAIAgent(notebookId) {
|
|
14
14
|
const { getAIAgent } = useAIAgents();
|
|
15
|
-
const
|
|
15
|
+
const agents = useAgentStore(state => state.agents);
|
|
16
|
+
const upsertAgent = useAgentStore(state => state.upsertAgent);
|
|
17
|
+
const deleteAgent = useAgentStore(state => state.deleteAgent);
|
|
18
|
+
const getAgentById = useAgentStore(state => state.getAgentById);
|
|
16
19
|
// Check AI Agent is alive.
|
|
17
20
|
useEffect(() => {
|
|
18
21
|
let abortController;
|
|
@@ -23,25 +26,39 @@ export function useNotebookAIAgent(notebookId) {
|
|
|
23
26
|
signal: abortController.signal,
|
|
24
27
|
});
|
|
25
28
|
if (!response.success) {
|
|
26
|
-
|
|
29
|
+
deleteAgent(notebookId);
|
|
27
30
|
return;
|
|
28
31
|
}
|
|
29
|
-
const currentAgent =
|
|
32
|
+
const currentAgent = getAgentById(notebookId);
|
|
30
33
|
const runtimeId = response.agent.runtime?.id;
|
|
31
34
|
if (currentAgent) {
|
|
35
|
+
// Update existing agent if runtime changed
|
|
32
36
|
if (currentAgent.runtimeId !== runtimeId) {
|
|
33
|
-
|
|
37
|
+
upsertAgent({
|
|
38
|
+
id: notebookId,
|
|
39
|
+
baseUrl: currentAgent.baseUrl,
|
|
40
|
+
transport: currentAgent.transport,
|
|
41
|
+
runtimeId,
|
|
42
|
+
status: 'running',
|
|
43
|
+
});
|
|
34
44
|
}
|
|
35
45
|
}
|
|
36
46
|
else {
|
|
37
|
-
|
|
47
|
+
// Add new agent - assume it's a notebook agent with document tracking
|
|
48
|
+
upsertAgent({
|
|
49
|
+
id: notebookId,
|
|
50
|
+
name: `Notebook ${notebookId}`,
|
|
51
|
+
description: 'AI agent for notebook',
|
|
52
|
+
baseUrl: '', // Will be set by the actual agent implementation
|
|
53
|
+
transport: 'vercel-ai', // Default transport for notebook agents
|
|
38
54
|
documentId: notebookId,
|
|
39
55
|
runtimeId,
|
|
56
|
+
status: 'running',
|
|
40
57
|
});
|
|
41
58
|
}
|
|
42
59
|
}
|
|
43
60
|
catch (r) {
|
|
44
|
-
|
|
61
|
+
deleteAgent(notebookId);
|
|
45
62
|
}
|
|
46
63
|
};
|
|
47
64
|
const refreshInterval = setInterval(refreshAIAgent, 60_000);
|
|
@@ -49,8 +66,8 @@ export function useNotebookAIAgent(notebookId) {
|
|
|
49
66
|
abortController?.abort('Component unmounted');
|
|
50
67
|
clearInterval(refreshInterval);
|
|
51
68
|
};
|
|
52
|
-
}, [
|
|
53
|
-
const aiAgent =
|
|
69
|
+
}, [agents, notebookId, getAIAgent, deleteAgent, getAgentById, upsertAgent]);
|
|
70
|
+
const aiAgent = getAgentById(notebookId);
|
|
54
71
|
return aiAgent;
|
|
55
72
|
}
|
|
56
73
|
export default useNotebookAIAgent;
|
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -1,11 +1,81 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export type
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import type { Transport } from '../../components/chat/components/Chat';
|
|
2
|
+
export type AgentStatus = 'running' | 'paused' | 'initializing' | 'error';
|
|
3
|
+
export type { Transport };
|
|
4
|
+
/**
|
|
5
|
+
* Unified Agent model combining runtime tracking and UI state
|
|
6
|
+
*/
|
|
7
|
+
export interface Agent {
|
|
8
|
+
/** Unique agent identifier */
|
|
9
|
+
id: string;
|
|
10
|
+
/** Display name */
|
|
11
|
+
name: string;
|
|
12
|
+
/** Agent description */
|
|
13
|
+
description: string;
|
|
14
|
+
/** Base URL for the agent (for Jupyter: baseUrl, for FastAPI: baseUrl) */
|
|
15
|
+
baseUrl: string;
|
|
16
|
+
/** Transport protocol used */
|
|
17
|
+
transport: Transport;
|
|
18
|
+
/** Current status */
|
|
19
|
+
status: AgentStatus;
|
|
20
|
+
/** Last error message if status is 'error' */
|
|
21
|
+
error?: string | null;
|
|
22
|
+
/** Timestamp of last update */
|
|
23
|
+
lastUpdated: number;
|
|
24
|
+
/** Document ID (for document-based agents) */
|
|
25
|
+
documentId?: string;
|
|
26
|
+
/** Runtime ID (for Jupyter kernel-based agents) */
|
|
27
|
+
runtimeId?: string;
|
|
28
|
+
/** Author name (optional, for display) */
|
|
29
|
+
author?: string;
|
|
30
|
+
/** Avatar URL (optional, for display) */
|
|
31
|
+
avatarUrl?: string;
|
|
32
|
+
}
|
|
33
|
+
export type AgentState = {
|
|
34
|
+
/** All registered agents */
|
|
35
|
+
agents: readonly Agent[];
|
|
36
|
+
/** Add or update an agent */
|
|
37
|
+
upsertAgent: (agent: Partial<Agent> & {
|
|
38
|
+
id: string;
|
|
39
|
+
baseUrl: string;
|
|
40
|
+
transport: Transport;
|
|
41
|
+
}) => void;
|
|
42
|
+
/** Get agent by ID */
|
|
43
|
+
getAgentById: (id: string) => Agent | undefined;
|
|
44
|
+
/** Get agent by baseUrl and transport */
|
|
45
|
+
getAgentByUrl: (baseUrl: string, transport: Transport) => Agent | undefined;
|
|
46
|
+
/** Update agent status */
|
|
47
|
+
updateAgentStatus: (id: string, status: AgentStatus, error?: string | null) => void;
|
|
48
|
+
/** Toggle agent status between running/paused */
|
|
49
|
+
toggleAgentStatus: (id: string) => void;
|
|
50
|
+
/** Delete an agent */
|
|
51
|
+
deleteAgent: (id: string) => void;
|
|
52
|
+
/** Clear all agents */
|
|
53
|
+
clearAgents: () => void;
|
|
7
54
|
};
|
|
8
|
-
export declare const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
55
|
+
export declare const agentStore: Omit<import("zustand").StoreApi<AgentState>, "persist"> & {
|
|
56
|
+
persist: {
|
|
57
|
+
setOptions: (options: Partial<import("zustand/middleware").PersistOptions<AgentState, unknown>>) => void;
|
|
58
|
+
clearStorage: () => void;
|
|
59
|
+
rehydrate: () => Promise<void> | void;
|
|
60
|
+
hasHydrated: () => boolean;
|
|
61
|
+
onHydrate: (fn: (state: AgentState) => void) => () => void;
|
|
62
|
+
onFinishHydration: (fn: (state: AgentState) => void) => () => void;
|
|
63
|
+
getOptions: () => Partial<import("zustand/middleware").PersistOptions<AgentState, unknown>>;
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
export declare function useAgentStore(): AgentState;
|
|
67
|
+
export declare function useAgentStore<T>(selector: (state: AgentState) => T): T;
|
|
68
|
+
export type AIAgentState = AgentState;
|
|
69
|
+
export declare const aiAgentStore: Omit<import("zustand").StoreApi<AgentState>, "persist"> & {
|
|
70
|
+
persist: {
|
|
71
|
+
setOptions: (options: Partial<import("zustand/middleware").PersistOptions<AgentState, unknown>>) => void;
|
|
72
|
+
clearStorage: () => void;
|
|
73
|
+
rehydrate: () => Promise<void> | void;
|
|
74
|
+
hasHydrated: () => boolean;
|
|
75
|
+
onHydrate: (fn: (state: AgentState) => void) => () => void;
|
|
76
|
+
onFinishHydration: (fn: (state: AgentState) => void) => () => void;
|
|
77
|
+
getOptions: () => Partial<import("zustand/middleware").PersistOptions<AgentState, unknown>>;
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
export declare const useAIAgentStore: typeof useAgentStore;
|
|
81
|
+
export default useAgentStore;
|
|
@@ -4,39 +4,105 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { createStore } from 'zustand/vanilla';
|
|
6
6
|
import { useStore } from 'zustand';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
import { persist, createJSONStorage } from 'zustand/middleware';
|
|
8
|
+
export const agentStore = createStore()(persist((set, get) => ({
|
|
9
|
+
agents: [],
|
|
10
|
+
upsertAgent: agentData => {
|
|
11
|
+
set(state => {
|
|
12
|
+
const existingIndex = state.agents.findIndex(a => a.id === agentData.id);
|
|
13
|
+
const now = Date.now();
|
|
14
|
+
if (existingIndex >= 0) {
|
|
15
|
+
// Update existing agent
|
|
16
|
+
const updatedAgents = [...state.agents];
|
|
17
|
+
updatedAgents[existingIndex] = {
|
|
18
|
+
...updatedAgents[existingIndex],
|
|
19
|
+
...agentData,
|
|
20
|
+
lastUpdated: now,
|
|
21
|
+
};
|
|
22
|
+
return { agents: updatedAgents };
|
|
13
23
|
}
|
|
14
24
|
else {
|
|
15
|
-
|
|
25
|
+
// Add new agent
|
|
26
|
+
const newAgent = {
|
|
27
|
+
name: agentData.name || agentData.id,
|
|
28
|
+
description: agentData.description || '',
|
|
29
|
+
status: agentData.status || 'initializing',
|
|
30
|
+
lastUpdated: now,
|
|
31
|
+
...agentData,
|
|
32
|
+
};
|
|
33
|
+
return { agents: [...state.agents, newAgent] };
|
|
16
34
|
}
|
|
17
35
|
});
|
|
18
36
|
},
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
});
|
|
37
|
+
getAgentById: (id) => {
|
|
38
|
+
const { agents } = get();
|
|
39
|
+
return agents.find(agent => agent.id === id);
|
|
40
|
+
},
|
|
41
|
+
getAgentByUrl: (baseUrl, transport) => {
|
|
42
|
+
const { agents } = get();
|
|
43
|
+
return agents.find(agent => agent.baseUrl === baseUrl && agent.transport === transport);
|
|
25
44
|
},
|
|
26
|
-
|
|
27
|
-
set(
|
|
28
|
-
const index = state.
|
|
45
|
+
updateAgentStatus: (id, status, error = null) => {
|
|
46
|
+
set(state => {
|
|
47
|
+
const index = state.agents.findIndex(a => a.id === id);
|
|
29
48
|
if (index >= 0) {
|
|
30
|
-
state.
|
|
31
|
-
|
|
49
|
+
const updatedAgents = [...state.agents];
|
|
50
|
+
updatedAgents[index] = {
|
|
51
|
+
...updatedAgents[index],
|
|
52
|
+
status,
|
|
53
|
+
error,
|
|
54
|
+
lastUpdated: Date.now(),
|
|
55
|
+
};
|
|
56
|
+
return { agents: updatedAgents };
|
|
32
57
|
}
|
|
33
|
-
|
|
34
|
-
|
|
58
|
+
return {};
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
toggleAgentStatus: (id) => {
|
|
62
|
+
set(state => {
|
|
63
|
+
const index = state.agents.findIndex(a => a.id === id);
|
|
64
|
+
if (index >= 0) {
|
|
65
|
+
const updatedAgents = [...state.agents];
|
|
66
|
+
const currentStatus = updatedAgents[index].status;
|
|
67
|
+
updatedAgents[index] = {
|
|
68
|
+
...updatedAgents[index],
|
|
69
|
+
status: currentStatus === 'running' ? 'paused' : 'running',
|
|
70
|
+
lastUpdated: Date.now(),
|
|
71
|
+
};
|
|
72
|
+
return { agents: updatedAgents };
|
|
35
73
|
}
|
|
74
|
+
return {};
|
|
36
75
|
});
|
|
37
76
|
},
|
|
77
|
+
deleteAgent: (id) => {
|
|
78
|
+
set(state => ({
|
|
79
|
+
agents: state.agents.filter(a => a.id !== id),
|
|
80
|
+
}));
|
|
81
|
+
},
|
|
82
|
+
clearAgents: () => {
|
|
83
|
+
set({ agents: [] });
|
|
84
|
+
},
|
|
85
|
+
}), {
|
|
86
|
+
name: 'agent-runtimes-storage',
|
|
87
|
+
storage: createJSONStorage(() => localStorage),
|
|
88
|
+
// Only persist essential fields
|
|
89
|
+
partialize: state => ({
|
|
90
|
+
agents: state.agents.map(agent => ({
|
|
91
|
+
id: agent.id,
|
|
92
|
+
name: agent.name,
|
|
93
|
+
description: agent.description,
|
|
94
|
+
baseUrl: agent.baseUrl,
|
|
95
|
+
transport: agent.transport,
|
|
96
|
+
status: agent.status,
|
|
97
|
+
lastUpdated: agent.lastUpdated,
|
|
98
|
+
documentId: agent.documentId,
|
|
99
|
+
runtimeId: agent.runtimeId,
|
|
100
|
+
})),
|
|
101
|
+
}),
|
|
38
102
|
}));
|
|
39
|
-
export function
|
|
40
|
-
return useStore(
|
|
103
|
+
export function useAgentStore(selector) {
|
|
104
|
+
return useStore(agentStore, selector);
|
|
41
105
|
}
|
|
42
|
-
export
|
|
106
|
+
export const aiAgentStore = agentStore;
|
|
107
|
+
export const useAIAgentStore = useAgentStore;
|
|
108
|
+
export default useAgentStore;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datalayer/agent-runtimes",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"workspaces": [
|
|
6
6
|
".",
|
|
@@ -28,7 +28,9 @@
|
|
|
28
28
|
"files": [
|
|
29
29
|
"lib/**/*.{css,d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
|
|
30
30
|
"style/**/*.{css,js,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
|
|
31
|
-
"schema/*.json"
|
|
31
|
+
"schema/*.json",
|
|
32
|
+
"patches/",
|
|
33
|
+
"scripts/"
|
|
32
34
|
],
|
|
33
35
|
"main": "lib/index.js",
|
|
34
36
|
"types": "lib/index.d.ts",
|
|
@@ -118,7 +120,7 @@
|
|
|
118
120
|
"@ag-ui/encoder": "^0.0.42",
|
|
119
121
|
"@ag-ui/proto": "^0.0.42",
|
|
120
122
|
"@agentclientprotocol/sdk": "^0.8.0",
|
|
121
|
-
"@ai-sdk/react": "3.0.
|
|
123
|
+
"@ai-sdk/react": "3.0.30",
|
|
122
124
|
"@anthropic-ai/sdk": "^0.52.0",
|
|
123
125
|
"@datalayer/core": "^0.0.24",
|
|
124
126
|
"@datalayer/icons-react": "^1.0.6",
|
|
@@ -177,7 +179,7 @@
|
|
|
177
179
|
"@tanstack/react-query": "^5.90.6",
|
|
178
180
|
"@toon-format/toon": "^1.3.0",
|
|
179
181
|
"@xyflow/react": "^12.10.0",
|
|
180
|
-
"ai": "
|
|
182
|
+
"ai": "6.0.28",
|
|
181
183
|
"ansi-to-html": "^0.7.2",
|
|
182
184
|
"axios": "^1.7.7",
|
|
183
185
|
"boring-avatars": "^2.0.1",
|
|
@@ -281,7 +283,7 @@
|
|
|
281
283
|
"vitest": "^3.2.4"
|
|
282
284
|
},
|
|
283
285
|
"resolutions": {
|
|
284
|
-
"@ai-sdk/react": "3.0.
|
|
286
|
+
"@ai-sdk/react": "3.0.30",
|
|
285
287
|
"@microsoft/fast-colors": "5.3.1",
|
|
286
288
|
"@microsoft/fast-components": "2.30.6",
|
|
287
289
|
"@microsoft/fast-element": "1.13.0",
|
|
@@ -291,14 +293,14 @@
|
|
|
291
293
|
"@microsoft/load-themed-styles": "1.10.295",
|
|
292
294
|
"@types/react": "18.3.20",
|
|
293
295
|
"@types/react-dom": "18.3.6",
|
|
294
|
-
"ai": "
|
|
296
|
+
"ai": "6.0.28",
|
|
295
297
|
"pyodide": "0.28.0",
|
|
296
298
|
"react": "18.3.1",
|
|
297
299
|
"react-dom": "18.3.1",
|
|
298
300
|
"typescript": "^5.8.3"
|
|
299
301
|
},
|
|
300
302
|
"overrides": {
|
|
301
|
-
"@ai-sdk/react": "3.0.
|
|
303
|
+
"@ai-sdk/react": "3.0.30",
|
|
302
304
|
"@microsoft/fast-colors": "5.3.1",
|
|
303
305
|
"@microsoft/fast-components": "2.30.6",
|
|
304
306
|
"@microsoft/fast-element": "1.13.0",
|
|
@@ -308,7 +310,7 @@
|
|
|
308
310
|
"@microsoft/load-themed-styles": "1.10.295",
|
|
309
311
|
"@types/react": "18.3.20",
|
|
310
312
|
"@types/react-dom": "18.3.6",
|
|
311
|
-
"ai": "
|
|
313
|
+
"ai": "6.0.28",
|
|
312
314
|
"pyodide": "0.28.0",
|
|
313
315
|
"react": "18.3.1",
|
|
314
316
|
"react-dom": "18.3.1",
|
package/patches/.gitkeep
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Patches directory for patch-package
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
# Distributed under the terms of the Modified BSD License.
|
|
4
|
+
|
|
5
|
+
# Apply patch-package patches for jupyter packages
|
|
6
|
+
# This is normally run automatically via npm's postinstall hook,
|
|
7
|
+
# but can be run manually if needed.
|
|
8
|
+
|
|
9
|
+
set -e
|
|
10
|
+
|
|
11
|
+
# Colors for output
|
|
12
|
+
GREEN='\033[0;32m'
|
|
13
|
+
BLUE='\033[0;34m'
|
|
14
|
+
NC='\033[0m' # No Color
|
|
15
|
+
|
|
16
|
+
echo -e "${BLUE}📝 Applying patches...${NC}"
|
|
17
|
+
|
|
18
|
+
# Get the script directory and project root
|
|
19
|
+
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
20
|
+
CORE_ROOT="$( cd "$SCRIPT_DIR/.." && pwd )"
|
|
21
|
+
|
|
22
|
+
cd "$CORE_ROOT"
|
|
23
|
+
|
|
24
|
+
npx patch-package
|
|
25
|
+
|
|
26
|
+
echo -e "${GREEN}✅ Patches applied successfully${NC}"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
# Distributed under the terms of the Modified BSD License.
|
|
4
|
+
|
|
5
|
+
# Create patch-package patches for locally modified jupyter packages
|
|
6
|
+
# This script generates patches that can be committed to the repo and
|
|
7
|
+
# applied automatically during npm install via the postinstall hook.
|
|
8
|
+
|
|
9
|
+
set -e
|
|
10
|
+
|
|
11
|
+
# Colors for output
|
|
12
|
+
GREEN='\033[0;32m'
|
|
13
|
+
BLUE='\033[0;34m'
|
|
14
|
+
YELLOW='\033[1;33m'
|
|
15
|
+
NC='\033[0m' # No Color
|
|
16
|
+
|
|
17
|
+
echo -e "${BLUE}🔧 Creating patches for jupyter packages...${NC}"
|
|
18
|
+
|
|
19
|
+
# Get the script directory and project root
|
|
20
|
+
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
21
|
+
CORE_ROOT="$( cd "$SCRIPT_DIR/.." && pwd )"
|
|
22
|
+
|
|
23
|
+
cd "$CORE_ROOT"
|
|
24
|
+
|
|
25
|
+
# First, sync the latest changes from jupyter-ui to ensure patches include all modifications
|
|
26
|
+
echo -e "${BLUE}🔄 Syncing latest changes from jupyter-ui...${NC}"
|
|
27
|
+
bash "$SCRIPT_DIR/sync-jupyter.sh"
|
|
28
|
+
|
|
29
|
+
# Ensure package-lock.json exists (required by patch-package)
|
|
30
|
+
if [ ! -f "package-lock.json" ]; then
|
|
31
|
+
echo -e "${YELLOW}⚠️ No package-lock.json found. Creating one...${NC}"
|
|
32
|
+
npm i --package-lock-only
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# Create patches
|
|
36
|
+
echo -e "${BLUE}📝 Generating patches with patch-package...${NC}"
|
|
37
|
+
npx patch-package @datalayer/jupyter-lexical @datalayer/jupyter-react
|
|
38
|
+
|
|
39
|
+
echo -e "${GREEN}✅ Patches created successfully in patches/ directory${NC}"
|
|
40
|
+
echo -e "${BLUE}ℹ️ Patches will be applied automatically on 'npm install' via postinstall hook${NC}"
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
# Distributed under the terms of the Modified BSD License.
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Download AI Elements components directly from the registry
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import urllib.request
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
BASE_URL = "https://registry.ai-sdk.dev"
|
|
15
|
+
COMPONENTS_DIR = "src/components/ai-elements"
|
|
16
|
+
|
|
17
|
+
# Components to download
|
|
18
|
+
COMPONENTS = [
|
|
19
|
+
"message",
|
|
20
|
+
"conversation",
|
|
21
|
+
"prompt-input",
|
|
22
|
+
"model-selector",
|
|
23
|
+
"artifact",
|
|
24
|
+
"code-block",
|
|
25
|
+
"suggestion",
|
|
26
|
+
"sources",
|
|
27
|
+
"reasoning",
|
|
28
|
+
"tool",
|
|
29
|
+
"loader",
|
|
30
|
+
"shimmer",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
def download_component(component_name):
|
|
34
|
+
"""Download a component from the registry"""
|
|
35
|
+
url = f"{BASE_URL}/{component_name}.json"
|
|
36
|
+
print(f"Downloading {component_name}...")
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
with urllib.request.urlopen(url) as response:
|
|
40
|
+
data = json.loads(response.read())
|
|
41
|
+
|
|
42
|
+
# Get the first file (main component file)
|
|
43
|
+
if 'files' in data and len(data['files']) > 0:
|
|
44
|
+
file_data = data['files'][0]
|
|
45
|
+
content = file_data.get('content', '')
|
|
46
|
+
target = file_data.get('target', f"components/ai-elements/{component_name}.tsx")
|
|
47
|
+
|
|
48
|
+
# Adjust target path
|
|
49
|
+
target_path = os.path.join(COMPONENTS_DIR, f"{component_name}.tsx")
|
|
50
|
+
|
|
51
|
+
# Ensure directory exists
|
|
52
|
+
os.makedirs(os.path.dirname(target_path), exist_ok=True)
|
|
53
|
+
|
|
54
|
+
# Write file
|
|
55
|
+
with open(target_path, 'w') as f:
|
|
56
|
+
f.write(content)
|
|
57
|
+
|
|
58
|
+
print(f"✓ Downloaded {component_name} to {target_path}")
|
|
59
|
+
return True
|
|
60
|
+
else:
|
|
61
|
+
print(f"✗ No files found for {component_name}")
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
except Exception as e:
|
|
65
|
+
print(f"✗ Error downloading {component_name}: {e}")
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
def main():
|
|
69
|
+
print(f"Downloading AI Elements components to {COMPONENTS_DIR}")
|
|
70
|
+
print(f"Components: {', '.join(COMPONENTS)}\n")
|
|
71
|
+
|
|
72
|
+
# Create components directory
|
|
73
|
+
os.makedirs(COMPONENTS_DIR, exist_ok=True)
|
|
74
|
+
|
|
75
|
+
success_count = 0
|
|
76
|
+
for component in COMPONENTS:
|
|
77
|
+
if download_component(component):
|
|
78
|
+
success_count += 1
|
|
79
|
+
|
|
80
|
+
print(f"\n✓ Successfully downloaded {success_count}/{len(COMPONENTS)} components")
|
|
81
|
+
|
|
82
|
+
if success_count < len(COMPONENTS):
|
|
83
|
+
sys.exit(1)
|
|
84
|
+
|
|
85
|
+
if __name__ == "__main__":
|
|
86
|
+
main()
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
# Distributed under the terms of the Modified BSD License.
|
|
4
|
+
|
|
5
|
+
# Sync local jupyter-ui packages to @datalayer/agent-runtimes node_modules
|
|
6
|
+
# This script builds the local jupyter packages and copies their lib/ outputs
|
|
7
|
+
# into the core package's node_modules for quick testing during development.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# ./sync-jupyter.sh # Run once and exit
|
|
11
|
+
# ./sync-jupyter.sh --watch # Watch for changes and auto-sync
|
|
12
|
+
|
|
13
|
+
set -e
|
|
14
|
+
|
|
15
|
+
# Colors for output
|
|
16
|
+
GREEN='\033[0;32m'
|
|
17
|
+
BLUE='\033[0;34m'
|
|
18
|
+
YELLOW='\033[1;33m'
|
|
19
|
+
NC='\033[0m' # No Color
|
|
20
|
+
|
|
21
|
+
# Get the script directory and project root
|
|
22
|
+
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
23
|
+
CORE_ROOT="$( cd "$SCRIPT_DIR/.." && pwd )"
|
|
24
|
+
JUPYTER_UI_ROOT="$( cd "$CORE_ROOT/../jupyter-ui" && pwd )"
|
|
25
|
+
|
|
26
|
+
# Function to perform the sync
|
|
27
|
+
sync_packages() {
|
|
28
|
+
echo -e "${BLUE}🔄 Syncing jupyter-ui packages to @datalayer/agent-runtimes...${NC}"
|
|
29
|
+
|
|
30
|
+
# Build jupyter-react FIRST (lexical depends on it)
|
|
31
|
+
echo -e "${BLUE}📦 Building @datalayer/jupyter-react...${NC}"
|
|
32
|
+
cd "$JUPYTER_UI_ROOT/packages/react"
|
|
33
|
+
echo -e "${YELLOW}[DEBUG] Current directory: $(pwd)${NC}"
|
|
34
|
+
rm -f tsconfig.tsbuildinfo
|
|
35
|
+
rm -rf lib
|
|
36
|
+
echo -e "${YELLOW}[DEBUG] Running gulp resources-to-lib...${NC}"
|
|
37
|
+
npx gulp resources-to-lib
|
|
38
|
+
echo -e "${YELLOW}[DEBUG] Running TypeScript...${NC}"
|
|
39
|
+
npx tsc --noEmitOnError false
|
|
40
|
+
TSC_EXIT=$?
|
|
41
|
+
echo -e "${YELLOW}[DEBUG] TypeScript exit code: $TSC_EXIT${NC}"
|
|
42
|
+
echo -e "${YELLOW}[DEBUG] Checking if lib exists...${NC}"
|
|
43
|
+
ls -la lib 2>&1 | head -5
|
|
44
|
+
|
|
45
|
+
# Verify lib was created
|
|
46
|
+
if [ ! -d "lib" ]; then
|
|
47
|
+
echo -e "${YELLOW}⚠️ lib directory was not created by TypeScript!${NC}"
|
|
48
|
+
exit 1
|
|
49
|
+
fi
|
|
50
|
+
echo -e "${YELLOW}[DEBUG] lib directory verified!${NC}"
|
|
51
|
+
|
|
52
|
+
# Copy react to core's node_modules for patch-package
|
|
53
|
+
echo -e "${BLUE}📋 Copying react to core/node_modules...${NC}"
|
|
54
|
+
cd "$CORE_ROOT"
|
|
55
|
+
# Only replace lib/ directory, preserving LICENSE, README.md, etc.
|
|
56
|
+
rm -rf node_modules/@datalayer/jupyter-react/lib
|
|
57
|
+
mkdir -p node_modules/@datalayer/jupyter-react/lib
|
|
58
|
+
cp -r "$JUPYTER_UI_ROOT/packages/react/lib/." node_modules/@datalayer/jupyter-react/lib/
|
|
59
|
+
cp "$JUPYTER_UI_ROOT/packages/react/package.json" node_modules/@datalayer/jupyter-react/
|
|
60
|
+
|
|
61
|
+
# Now build jupyter-lexical (finds react via workspace hoisting)
|
|
62
|
+
echo -e "${BLUE}📦 Building @datalayer/jupyter-lexical...${NC}"
|
|
63
|
+
cd "$JUPYTER_UI_ROOT/packages/lexical"
|
|
64
|
+
rm -f tsconfig.tsbuildinfo
|
|
65
|
+
rm -rf lib
|
|
66
|
+
echo -e "${YELLOW}[DEBUG] Running gulp resources-to-lib...${NC}"
|
|
67
|
+
npx gulp resources-to-lib
|
|
68
|
+
echo -e "${YELLOW}[DEBUG] Running TypeScript...${NC}"
|
|
69
|
+
npx tsc --noEmitOnError false
|
|
70
|
+
|
|
71
|
+
# Copy lexical to node_modules
|
|
72
|
+
echo -e "${BLUE}📋 Copying lexical to node_modules...${NC}"
|
|
73
|
+
cd "$CORE_ROOT"
|
|
74
|
+
# Only replace lib/ directory, preserving LICENSE, README.md, etc.
|
|
75
|
+
rm -rf node_modules/@datalayer/jupyter-lexical/lib
|
|
76
|
+
mkdir -p node_modules/@datalayer/jupyter-lexical/lib
|
|
77
|
+
cp -r "$JUPYTER_UI_ROOT/packages/lexical/lib/." node_modules/@datalayer/jupyter-lexical/lib/
|
|
78
|
+
cp "$JUPYTER_UI_ROOT/packages/lexical/package.json" node_modules/@datalayer/jupyter-lexical/
|
|
79
|
+
|
|
80
|
+
echo -e "${GREEN}✅ Successfully synced at $(date +"%H:%M:%S")${NC}"
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# Check if watch mode is requested
|
|
84
|
+
if [[ "$1" == "--watch" ]]; then
|
|
85
|
+
# Check if fswatch is installed
|
|
86
|
+
if ! command -v fswatch &> /dev/null; then
|
|
87
|
+
echo -e "${YELLOW}⚠️ fswatch not found. Installing via Homebrew...${NC}"
|
|
88
|
+
if command -v brew &> /dev/null; then
|
|
89
|
+
brew install fswatch
|
|
90
|
+
else
|
|
91
|
+
echo -e "${YELLOW}⚠️ Homebrew not found. Please install fswatch manually:${NC}"
|
|
92
|
+
echo -e "${YELLOW} brew install fswatch${NC}"
|
|
93
|
+
echo -e "${YELLOW} or visit: https://github.com/emcrisostomo/fswatch${NC}"
|
|
94
|
+
exit 1
|
|
95
|
+
fi
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
echo -e "${BLUE}👁️ Watch mode enabled. Monitoring jupyter-ui packages for changes...${NC}"
|
|
99
|
+
echo -e "${YELLOW}📁 Watching:${NC}"
|
|
100
|
+
echo -e "${YELLOW} - $JUPYTER_UI_ROOT/packages/lexical/src${NC}"
|
|
101
|
+
echo -e "${YELLOW} - $JUPYTER_UI_ROOT/packages/react/src${NC}"
|
|
102
|
+
echo -e "${YELLOW}Press Ctrl+C to stop${NC}"
|
|
103
|
+
echo ""
|
|
104
|
+
|
|
105
|
+
# Initial sync
|
|
106
|
+
sync_packages
|
|
107
|
+
|
|
108
|
+
# Watch for changes in src directories and trigger sync
|
|
109
|
+
# Using fswatch with:
|
|
110
|
+
# -r: recursive
|
|
111
|
+
# -e: exclude patterns (node_modules, lib, etc.)
|
|
112
|
+
# -l 1: latency 1 second (debounce rapid changes)
|
|
113
|
+
fswatch -r -l 1 \
|
|
114
|
+
-e ".*" -i "\\.tsx?$" -i "\\.jsx?$" -i "\\.css$" \
|
|
115
|
+
"$JUPYTER_UI_ROOT/packages/lexical/src" \
|
|
116
|
+
"$JUPYTER_UI_ROOT/packages/react/src" | while read -r file; do
|
|
117
|
+
echo -e "\n${YELLOW}📝 Change detected in: $(basename "$file")${NC}"
|
|
118
|
+
sync_packages
|
|
119
|
+
done
|
|
120
|
+
else
|
|
121
|
+
# Single run mode
|
|
122
|
+
sync_packages
|
|
123
|
+
fi
|