hwonder commited on
Commit
26cd782
·
1 Parent(s): 5a42256

Use MCP tool calls for image/video generation

Browse files

- Add submit_tool_task method to client for MCP tool calls
- Update ImageService to use generate_image_5 and generate_image_edit_5
- Update VideoService to use generate_video_2 and generate_image_to_video_2

Files changed (3) hide show
  1. src/api/client.py +63 -0
  2. src/services/image.py +28 -21
  3. src/services/video.py +16 -21
src/api/client.py CHANGED
@@ -62,6 +62,69 @@ class StackNetClient:
62
  self.timeout = timeout
63
  self._temp_dir = tempfile.mkdtemp(prefix="stacknet_")
64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  async def submit_media_task(
66
  self,
67
  action: MediaAction,
 
62
  self.timeout = timeout
63
  self._temp_dir = tempfile.mkdtemp(prefix="stacknet_")
64
 
65
+ async def submit_tool_task(
66
+ self,
67
+ tool_name: str,
68
+ parameters: dict,
69
+ server_name: str = "geoff",
70
+ on_progress: Optional[Callable[[float, str], None]] = None
71
+ ) -> TaskResult:
72
+ """
73
+ Submit an MCP tool task and wait for completion.
74
+
75
+ Args:
76
+ tool_name: The tool to invoke (e.g., generate_image_5)
77
+ parameters: Tool parameters
78
+ server_name: MCP server name (default: geoff)
79
+ on_progress: Callback for progress updates
80
+
81
+ Returns:
82
+ TaskResult with success status and output data
83
+ """
84
+ payload = {
85
+ "type": "mcp-tool",
86
+ "serverName": server_name,
87
+ "toolName": tool_name,
88
+ "stream": True,
89
+ "parameters": parameters
90
+ }
91
+
92
+ headers = {"Content-Type": "application/json"}
93
+ if self.api_key:
94
+ auth_header = self.api_key if self.api_key.startswith("Bearer ") else f"Bearer {self.api_key}"
95
+ headers["Authorization"] = auth_header
96
+
97
+ async with httpx.AsyncClient(timeout=self.timeout) as client:
98
+ try:
99
+ async with client.stream(
100
+ "POST",
101
+ f"{self.base_url}/tasks",
102
+ json=payload,
103
+ headers=headers
104
+ ) as response:
105
+ if response.status_code != 200:
106
+ error_text = await response.aread()
107
+ return TaskResult(
108
+ success=False,
109
+ data={},
110
+ error=f"API request failed ({response.status_code}): {error_text.decode()[:200]}"
111
+ )
112
+
113
+ return await self._process_sse_stream(response, on_progress)
114
+
115
+ except httpx.TimeoutException:
116
+ return TaskResult(
117
+ success=False,
118
+ data={},
119
+ error="Request timed out. The operation took too long."
120
+ )
121
+ except httpx.RequestError as e:
122
+ return TaskResult(
123
+ success=False,
124
+ data={},
125
+ error=f"Network error: {str(e)}"
126
+ )
127
+
128
  async def submit_media_task(
129
  self,
130
  action: MediaAction,
src/services/image.py CHANGED
@@ -8,7 +8,7 @@ Abstracts all API complexity from the UI layer.
8
  from typing import Callable, Optional, List
9
  from dataclasses import dataclass
10
 
11
- from ..api.client import StackNetClient, MediaAction
12
 
13
 
14
  @dataclass
@@ -41,7 +41,7 @@ class ImageService:
41
  on_progress: Optional[Callable[[float, str], None]] = None
42
  ) -> List[GeneratedImage]:
43
  """
44
- Generate image from a text prompt.
45
 
46
  Args:
47
  prompt: Description of desired image
@@ -56,14 +56,24 @@ class ImageService:
56
  if style and style != "Photorealistic":
57
  full_prompt = f"{prompt}, {style.lower()} style"
58
 
59
- options = {}
60
- if aspect_ratio:
61
- options["aspect_ratio"] = aspect_ratio
62
-
63
- result = await self.client.submit_media_task(
64
- action=MediaAction.ANALYZE_VISUAL,
65
- prompt=full_prompt,
66
- options=options if options else None,
 
 
 
 
 
 
 
 
 
 
67
  on_progress=on_progress
68
  )
69
 
@@ -80,7 +90,7 @@ class ImageService:
80
  on_progress: Optional[Callable[[float, str], None]] = None
81
  ) -> List[GeneratedImage]:
82
  """
83
- Edit/transform an existing image.
84
 
85
  Args:
86
  image_url: URL to source image
@@ -91,16 +101,13 @@ class ImageService:
91
  Returns:
92
  List of edited images
93
  """
94
- options = {
95
- "strength": strength,
96
- "edit_mode": True
97
- }
98
-
99
- result = await self.client.submit_media_task(
100
- action=MediaAction.ANALYZE_VISUAL,
101
- media_url=image_url,
102
- prompt=edit_prompt,
103
- options=options,
104
  on_progress=on_progress
105
  )
106
 
 
8
  from typing import Callable, Optional, List
9
  from dataclasses import dataclass
10
 
11
+ from ..api.client import StackNetClient
12
 
13
 
14
  @dataclass
 
41
  on_progress: Optional[Callable[[float, str], None]] = None
42
  ) -> List[GeneratedImage]:
43
  """
44
+ Generate image from a text prompt using generate_image_5 tool.
45
 
46
  Args:
47
  prompt: Description of desired image
 
56
  if style and style != "Photorealistic":
57
  full_prompt = f"{prompt}, {style.lower()} style"
58
 
59
+ # Determine dimensions from aspect ratio
60
+ width, height = 1024, 1024
61
+ if aspect_ratio == "16:9":
62
+ width, height = 1280, 720
63
+ elif aspect_ratio == "9:16":
64
+ width, height = 720, 1280
65
+ elif aspect_ratio == "4:3":
66
+ width, height = 1024, 768
67
+ elif aspect_ratio == "3:4":
68
+ width, height = 768, 1024
69
+
70
+ result = await self.client.submit_tool_task(
71
+ tool_name="generate_image_5",
72
+ parameters={
73
+ "prompt": full_prompt,
74
+ "width": width,
75
+ "height": height
76
+ },
77
  on_progress=on_progress
78
  )
79
 
 
90
  on_progress: Optional[Callable[[float, str], None]] = None
91
  ) -> List[GeneratedImage]:
92
  """
93
+ Edit/transform an existing image using generate_image_edit_5 tool.
94
 
95
  Args:
96
  image_url: URL to source image
 
101
  Returns:
102
  List of edited images
103
  """
104
+ result = await self.client.submit_tool_task(
105
+ tool_name="generate_image_edit_5",
106
+ parameters={
107
+ "prompt": edit_prompt,
108
+ "image_url": image_url,
109
+ "strength": strength
110
+ },
 
 
 
111
  on_progress=on_progress
112
  )
113
 
src/services/video.py CHANGED
@@ -8,7 +8,7 @@ Abstracts all API complexity from the UI layer.
8
  from typing import Callable, Optional, List
9
  from dataclasses import dataclass
10
 
11
- from ..api.client import StackNetClient, MediaAction
12
 
13
 
14
  @dataclass
@@ -41,7 +41,7 @@ class VideoService:
41
  on_progress: Optional[Callable[[float, str], None]] = None
42
  ) -> List[GeneratedVideo]:
43
  """
44
- Generate video from a text prompt.
45
 
46
  Args:
47
  prompt: Description of desired video
@@ -56,14 +56,12 @@ class VideoService:
56
  if style and style != "Cinematic":
57
  full_prompt = f"{prompt}, {style.lower()} style"
58
 
59
- options = {
60
- "duration": duration
61
- }
62
-
63
- result = await self.client.submit_media_task(
64
- action=MediaAction.DESCRIBE_VIDEO,
65
- prompt=full_prompt,
66
- options=options,
67
  on_progress=on_progress
68
  )
69
 
@@ -80,7 +78,7 @@ class VideoService:
80
  on_progress: Optional[Callable[[float, str], None]] = None
81
  ) -> List[GeneratedVideo]:
82
  """
83
- Animate a static image into video.
84
 
85
  Args:
86
  image_url: URL to source image
@@ -91,16 +89,13 @@ class VideoService:
91
  Returns:
92
  List of animated videos
93
  """
94
- options = {
95
- "duration": duration,
96
- "animate_mode": True
97
- }
98
-
99
- result = await self.client.submit_media_task(
100
- action=MediaAction.DESCRIBE_VIDEO,
101
- media_url=image_url,
102
- prompt=motion_prompt,
103
- options=options,
104
  on_progress=on_progress
105
  )
106
 
 
8
  from typing import Callable, Optional, List
9
  from dataclasses import dataclass
10
 
11
+ from ..api.client import StackNetClient
12
 
13
 
14
  @dataclass
 
41
  on_progress: Optional[Callable[[float, str], None]] = None
42
  ) -> List[GeneratedVideo]:
43
  """
44
+ Generate video from a text prompt using generate_video_2 tool.
45
 
46
  Args:
47
  prompt: Description of desired video
 
56
  if style and style != "Cinematic":
57
  full_prompt = f"{prompt}, {style.lower()} style"
58
 
59
+ result = await self.client.submit_tool_task(
60
+ tool_name="generate_video_2",
61
+ parameters={
62
+ "prompt": full_prompt,
63
+ "duration": duration
64
+ },
 
 
65
  on_progress=on_progress
66
  )
67
 
 
78
  on_progress: Optional[Callable[[float, str], None]] = None
79
  ) -> List[GeneratedVideo]:
80
  """
81
+ Animate a static image into video using generate_image_to_video_2 tool.
82
 
83
  Args:
84
  image_url: URL to source image
 
89
  Returns:
90
  List of animated videos
91
  """
92
+ result = await self.client.submit_tool_task(
93
+ tool_name="generate_image_to_video_2",
94
+ parameters={
95
+ "prompt": motion_prompt,
96
+ "image_url": image_url,
97
+ "duration": duration
98
+ },
 
 
 
99
  on_progress=on_progress
100
  )
101