Patrick Rathje commited on
Commit
26a3c72
·
1 Parent(s): 28e48f7

add local project build tool

Browse files
Files changed (4) hide show
  1. Dockerfile +26 -3
  2. app.py +109 -27
  3. gradio_mcp_server.py +0 -114
  4. requirements.txt +1 -1
Dockerfile CHANGED
@@ -1,17 +1,40 @@
1
  FROM python:3.10-slim
2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  # Install Gradio and copy local files
4
  WORKDIR /app
5
-
6
- COPY requirements.txt .
7
  RUN pip install --no-cache-dir -r requirements.txt
8
-
9
  COPY . .
10
 
11
  RUN mkdir -p /app/public
12
 
13
  RUN useradd -m -u 1000 user
14
 
 
15
  RUN chown -R user:user /app
16
  USER user
17
 
 
1
  FROM python:3.10-slim
2
 
3
+ # Install Node to run Motion Canvas
4
+ WORKDIR /tmp/node
5
+ RUN apt-get update && apt-get install -y nodejs npm
6
+
7
+
8
+ # without v prefix!
9
+ ARG MC_VERSION="3.17.2"
10
+ ENV NODE_ENV=$MC_VERSION
11
+
12
+ WORKDIR /motion-canvas
13
+
14
+ RUN yes '' | npm init -y @motion-canvas@$MC_VERSION
15
+
16
+ WORKDIR /motion-canvas/my-animation
17
+ ENV MC_PROJECT_DIR=/motion-canvas/my-animation
18
+
19
+ # copy the vite.config.ts file to the project to get deterministic output filenames under dist/project.js
20
+ COPY docker/vite.config.ts ./vite.config.ts
21
+
22
+ # if we build here, we have to clean up the project every time before building a new one...
23
+ RUN npm install
24
+ RUN npm run build
25
+
26
+
27
  # Install Gradio and copy local files
28
  WORKDIR /app
29
+ COPY ./requirements.txt .
 
30
  RUN pip install --no-cache-dir -r requirements.txt
 
31
  COPY . .
32
 
33
  RUN mkdir -p /app/public
34
 
35
  RUN useradd -m -u 1000 user
36
 
37
+ RUN chown -R user:user /motion-canvas
38
  RUN chown -R user:user /app
39
  USER user
40
 
app.py CHANGED
@@ -8,40 +8,122 @@ from threading import Timer
8
  from functools import partial
9
  import time
10
 
11
- from huggingface_hub import InferenceClient
12
- from smolagents import ToolCollection, CodeAgent, InferenceClientModel
13
 
14
- MODEL = "Qwen/Qwen2.5-Coder-32B-Instruct"
15
 
16
- BUILD_SERVER_MCP_CONFIG = {"url": "https://agents-mcp-hackathon-gradio-motioncanvas-mcp-server.hf.space/gradio_api/mcp/sse", "transport": "sse"}
17
  DOCS_SERVER_MCP_CONFIG = {"url": "https://prathje-gradio-motioncanvas-docs-mcp-server.hf.space/gradio_api/mcp/sse", "transport": "sse"}
18
 
19
- all_tools = []
20
- with ToolCollection.from_mcp(BUILD_SERVER_MCP_CONFIG, trust_remote_code=True) as build_tool_collection:
21
- with ToolCollection.from_mcp(DOCS_SERVER_MCP_CONFIG, trust_remote_code=True) as docs_tool_collection:
22
- all_tools = build_tool_collection.tools + docs_tool_collection.tools
 
 
 
 
 
 
 
 
 
23
 
24
- if os.environ.get("HF_TOKEN"):
 
 
 
 
 
25
 
26
- model = InferenceClientModel(model_id=MODEL)
27
-
28
- agent = CodeAgent(tools=[*all_tools], model=model)
29
-
30
- def generate(message, history, code, logs):
31
- try:
32
- res = agent.run(
33
- "From the following prompt, generate code for a standalone motion canvas scene.tsx and build. You can browse the docs to help you.",
34
- additional_args={'prompt': 'Please animate the formula for the area of a circle'}
35
- )
36
- print(res)
37
- except Exception as e:
38
- print(e)
39
- return "An error occurred while generating the code", "", ""
40
- else:
41
- print("No HF_TOKEN found, Zero GPU space not implemented")
42
- def generate(message, history, code, logs):
43
- return "LLM not available", "", ""
44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
 
47
  from gradio_motioncanvasplayer import MotionCanvasPlayer
 
8
  from functools import partial
9
  import time
10
 
11
+ from smolagents import ToolCollection, ToolCallingAgent, InferenceClientModel, EMPTY_PROMPT_TEMPLATES
 
12
 
13
+ MODEL = "meta-llama/Llama-3.3-70B-Instruct"
14
 
15
+ BUILD_SERVER_MCP_CONFIG = {"url": "https://prathje-gradio-motioncanvas-mcp-server.hf.space/gradio_api/mcp/sse", "transport": "sse"}
16
  DOCS_SERVER_MCP_CONFIG = {"url": "https://prathje-gradio-motioncanvas-docs-mcp-server.hf.space/gradio_api/mcp/sse", "transport": "sse"}
17
 
18
+ SYSTEM_PROMPT = "You are a helpful assistant that generates motion canvas scenes. The user prompts his ideas. You should use the recursive_list to check for available classes and examples. You should generate the code for a single standalone motion canvas scene.tsx and build it using the build tool. If the build response is empty, it means the build failed and the code is not valid. You can use all tools provided to you to help you with your task."
19
+
20
+ from smolagents import tool
21
+
22
+
23
+
24
+ gr.set_static_paths(paths=[os.path.join(os.path.dirname(__file__), "public")])
25
+
26
+ def get_local_path(project_id):
27
+ return os.path.join(os.path.dirname(__file__), "public", "project-" + project_id + ".js")
28
+
29
+ def get_public_path(project_id):
30
+ return "/gradio_api/file=" + get_local_path(project_id)
31
 
32
+ BUILD_TIMEOUT=30
33
+
34
+ # In theory, we should be using the build server mcp, but it not working right now and we are running out of time ;)
35
+ @tool
36
+ def build_project(code: str) -> str:
37
+ """Build a Motion Canvas project.
38
 
39
+ Args:
40
+ code: TypeScript code for the scene to build
41
+ """
42
+
43
+ # generate a random uuid for the project
44
+ project_id = str(uuid.uuid4())
45
+
46
+ tmp_dir = os.path.join("/tmp/", project_id)
 
 
 
 
 
 
 
 
 
 
47
 
48
+ shutil.copytree(os.environ['MC_PROJECT_DIR'], tmp_dir, dirs_exist_ok=False, symlinks=True)
49
+ acc_logs = ""
50
+
51
+ try:
52
+
53
+ # write code to scene.ts
54
+ with open(os.path.join(tmp_dir, "src", "scenes", "example.tsx"), "w") as f:
55
+ f.write(code)
56
+
57
+ process = subprocess.Popen(
58
+ "npm run build",
59
+ stdout=subprocess.PIPE,
60
+ stderr=subprocess.PIPE,
61
+ text=True,
62
+ shell=True,
63
+ cwd=tmp_dir
64
+ )
65
+
66
+ timer = Timer(BUILD_TIMEOUT, process.kill)
67
+ timer.start()
68
+
69
+ while True:
70
+ line = process.stdout.readline()
71
+ if line:
72
+ acc_logs += line.rstrip() + "\n"
73
+ elif process.poll() is not None:
74
+ break
75
+ timer.cancel()
76
+
77
+ # Check for errors
78
+ stderr_output = process.stderr.read()
79
+ if stderr_output:
80
+ acc_logs += "\n" + stderr_output
81
+
82
+ # check if the build was successful
83
+ if process.returncode != 0:
84
+ return "build failed", acc_logs
85
+ else:
86
+ # copy dist/project.js to get_local_path(id)
87
+ shutil.copy(os.path.join(tmp_dir, "dist", "project.js"), get_local_path(project_id))
88
+ return get_public_path(project_id), acc_logs
89
+
90
+ except Exception as e:
91
+ return "build failed", acc_logs + "\n" + "Error building project: " + str(e)
92
+
93
+ finally:
94
+ # cleanup tmp dir
95
+ shutil.rmtree(tmp_dir)
96
+
97
+ all_tools = []
98
+ with ToolCollection.from_mcp(BUILD_SERVER_MCP_CONFIG, trust_remote_code=True) as build_tool_collection:
99
+ with ToolCollection.from_mcp(DOCS_SERVER_MCP_CONFIG, trust_remote_code=True) as docs_tool_collection:
100
+ all_tools = docs_tool_collection.tools + [build_project]
101
+
102
+ if os.environ.get("HF_TOKEN"):
103
+ model = InferenceClientModel(model_id=MODEL)
104
+
105
+ agent = ToolCallingAgent(tools=[*all_tools], model=model, return_full_result=True)
106
+
107
+ agent.prompt_templates["system_prompt"] = SYSTEM_PROMPT
108
+
109
+ def generate(message, history, code, logs):
110
+ try:
111
+ res = agent.run(
112
+ "From the following prompt, generate code for a single standalone motion canvas scene.tsx and build. You can browse the docs to help you.",
113
+ additional_args={'prompt': 'Please animate the formula for the area of a circle'}
114
+ )
115
+ print(res.messages)
116
+ print(res.output)
117
+ except Exception as e:
118
+ print(e)
119
+ return "An error occurred while generating the code", "", ""
120
+ else:
121
+ print("No HF_TOKEN found, Zero GPU space not implemented")
122
+ def generate(message, history, code, logs):
123
+ return "LLM not available", "", ""
124
+
125
+ generate("test", [], "", "")
126
+ exit()
127
 
128
 
129
  from gradio_motioncanvasplayer import MotionCanvasPlayer
gradio_mcp_server.py DELETED
@@ -1,114 +0,0 @@
1
- from mcp.server.fastmcp import FastMCP
2
- import json
3
- import sys
4
- import io
5
- import time
6
- from gradio_client import Client
7
-
8
-
9
-
10
- sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
11
- sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
12
-
13
- mcp = FastMCP("Gradio MCP Server")
14
-
15
- clients = {}
16
-
17
-
18
-
19
- def get_client(space_id: str) -> Client:
20
- """Get or create a Gradio client for the specified space."""
21
- if space_id not in clients:
22
- clients[space_id] = Client(space_id)
23
- return clients[space_id]
24
-
25
- @mcp.tool()
26
- async def build_project(code: str) -> str:
27
- """Build a Motion Canvas project using the Gradio Motion MCP server.
28
-
29
- Args:
30
- code: TypeScript code for the scene to build
31
- """
32
-
33
- try:
34
- result = get_client(BUILD_SERVER_SPACE_ID).predict(
35
- code,
36
- api_name="/build"
37
- )
38
-
39
- if isinstance(result, list) and len(result) >= 2:
40
- project_path = result[0]
41
- logs = result[1]
42
-
43
- if project_path:
44
- return json.dumps({
45
- "type": "mc_project",
46
- "project_path": project_path,
47
- "message": f"Built project at {project_path}",
48
- "logs": result[1]
49
- })
50
- else:
51
- return json.dumps({
52
- "type": "error",
53
- "message": f"Error building project:\n" + logs
54
- })
55
- else:
56
- return json.dumps({
57
- "type": "error",
58
- "message": f"Error building project"
59
- })
60
-
61
- except Exception as e:
62
- return json.dumps({
63
- "type": "error",
64
- "message": f"Error building project: {str(e)}"
65
- })
66
-
67
- @mcp.tool()
68
- async def build_project(code: str) -> str:
69
- """Build a Motion Canvas project using the Gradio Motion MCP server.
70
-
71
- Args:
72
- code: TypeScript code for the scene to build
73
- """
74
- client = Client("https://agents-mcp-hackathon-gradio-motioncanvas-mcp-server.hf.space/")
75
-
76
-
77
-
78
-
79
- try:
80
- result = client.predict(
81
- code,
82
- api_name="/build"
83
- )
84
-
85
- if isinstance(result, list) and len(result) >= 2:
86
- project_path = result[0]
87
- logs = result[1]
88
-
89
- if project_path:
90
- return json.dumps({
91
- "type": "mc_project",
92
- "project_path": project_path,
93
- "message": f"Built project at {project_path}",
94
- "logs": result[1]
95
- })
96
- else:
97
- return json.dumps({
98
- "type": "error",
99
- "message": f"Error building project:\n" + logs
100
- })
101
- else:
102
- return json.dumps({
103
- "type": "error",
104
- "message": f"Error building project"
105
- })
106
-
107
- except Exception as e:
108
- return json.dumps({
109
- "type": "error",
110
- "message": f"Error building project: {str(e)}"
111
- })
112
-
113
- if __name__ == "__main__":
114
- mcp.run(transport='stdio')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -1,4 +1,4 @@
1
  gradio[mcp]
2
  gradio_motioncanvasplayer
3
  huggingface_hub
4
- smolagents[mcp]
 
1
  gradio[mcp]
2
  gradio_motioncanvasplayer
3
  huggingface_hub
4
+ smolagents[mcp]==1.18.0