Spaces:
Sleeping
Sleeping
Patrick Rathje
commited on
Commit
·
26a3c72
1
Parent(s):
28e48f7
add local project build tool
Browse files- Dockerfile +26 -3
- app.py +109 -27
- gradio_mcp_server.py +0 -114
- 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
|
12 |
-
from smolagents import ToolCollection, CodeAgent, InferenceClientModel
|
13 |
|
14 |
-
MODEL = "
|
15 |
|
16 |
-
BUILD_SERVER_MCP_CONFIG = {"url": "https://
|
17 |
DOCS_SERVER_MCP_CONFIG = {"url": "https://prathje-gradio-motioncanvas-docs-mcp-server.hf.space/gradio_api/mcp/sse", "transport": "sse"}
|
18 |
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
23 |
|
24 |
-
|
|
|
|
|
|
|
|
|
|
|
25 |
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
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
|