vibe-aigc 0.2.0__py3-none-any.whl → 0.3.0__py3-none-any.whl
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.
- vibe_aigc/__init__.py +3 -1
- vibe_aigc/comfyui.py +447 -0
- vibe_aigc/knowledge.py +177 -0
- vibe_aigc/llm.py +16 -1
- {vibe_aigc-0.2.0.dist-info → vibe_aigc-0.3.0.dist-info}/METADATA +2 -1
- {vibe_aigc-0.2.0.dist-info → vibe_aigc-0.3.0.dist-info}/RECORD +10 -9
- {vibe_aigc-0.2.0.dist-info → vibe_aigc-0.3.0.dist-info}/WHEEL +0 -0
- {vibe_aigc-0.2.0.dist-info → vibe_aigc-0.3.0.dist-info}/entry_points.txt +0 -0
- {vibe_aigc-0.2.0.dist-info → vibe_aigc-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {vibe_aigc-0.2.0.dist-info → vibe_aigc-0.3.0.dist-info}/top_level.txt +0 -0
vibe_aigc/__init__.py
CHANGED
|
@@ -98,4 +98,6 @@ __all__ = [
|
|
|
98
98
|
"create_default_agents",
|
|
99
99
|
# Asset Bank
|
|
100
100
|
"AssetBank", "Character", "StyleGuide", "Artifact", "create_asset_bank"
|
|
101
|
-
]
|
|
101
|
+
]
|
|
102
|
+
# ComfyUI backend for actual image generation
|
|
103
|
+
from .comfyui import ComfyUIBackend, ComfyUIConfig, ComfyUIImageTool, create_comfyui_registry
|
vibe_aigc/comfyui.py
ADDED
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
"""ComfyUI backend for actual image/video generation.
|
|
2
|
+
|
|
3
|
+
This implements the paper's vision of AIGC - AI Generated Content,
|
|
4
|
+
not just text orchestration but actual multimodal content generation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import json
|
|
9
|
+
import uuid
|
|
10
|
+
import aiohttp
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Dict, List, Optional, Union
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class ComfyUIConfig:
|
|
18
|
+
"""Configuration for ComfyUI connection."""
|
|
19
|
+
host: str = "127.0.0.1"
|
|
20
|
+
port: int = 8188
|
|
21
|
+
output_dir: Optional[str] = None # Where to save generated images
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def base_url(self) -> str:
|
|
25
|
+
return f"http://{self.host}:{self.port}"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class GenerationResult:
|
|
30
|
+
"""Result from a ComfyUI generation."""
|
|
31
|
+
success: bool
|
|
32
|
+
images: List[str] = field(default_factory=list) # Paths or URLs to generated images
|
|
33
|
+
prompt_id: Optional[str] = None
|
|
34
|
+
error: Optional[str] = None
|
|
35
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ComfyUIBackend:
|
|
39
|
+
"""Backend for interacting with ComfyUI for image/video generation.
|
|
40
|
+
|
|
41
|
+
This enables vibe-aigc to generate actual content, not just orchestrate.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, config: Optional[ComfyUIConfig] = None):
|
|
45
|
+
self.config = config or ComfyUIConfig()
|
|
46
|
+
self._client_id = str(uuid.uuid4())
|
|
47
|
+
|
|
48
|
+
async def is_available(self) -> bool:
|
|
49
|
+
"""Check if ComfyUI is running and accessible."""
|
|
50
|
+
try:
|
|
51
|
+
async with aiohttp.ClientSession() as session:
|
|
52
|
+
async with session.get(
|
|
53
|
+
f"{self.config.base_url}/system_stats",
|
|
54
|
+
timeout=aiohttp.ClientTimeout(total=5)
|
|
55
|
+
) as resp:
|
|
56
|
+
return resp.status == 200
|
|
57
|
+
except Exception:
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
async def get_system_stats(self) -> Dict[str, Any]:
|
|
61
|
+
"""Get ComfyUI system stats including GPU info."""
|
|
62
|
+
async with aiohttp.ClientSession() as session:
|
|
63
|
+
async with session.get(f"{self.config.base_url}/system_stats") as resp:
|
|
64
|
+
return await resp.json()
|
|
65
|
+
|
|
66
|
+
async def get_available_models(self) -> Dict[str, List[str]]:
|
|
67
|
+
"""Get available models (checkpoints, loras, etc.)."""
|
|
68
|
+
async with aiohttp.ClientSession() as session:
|
|
69
|
+
async with session.get(f"{self.config.base_url}/object_info") as resp:
|
|
70
|
+
obj_info = await resp.json()
|
|
71
|
+
|
|
72
|
+
models = {}
|
|
73
|
+
|
|
74
|
+
# Extract checkpoint models
|
|
75
|
+
if "CheckpointLoaderSimple" in obj_info:
|
|
76
|
+
ckpt_info = obj_info["CheckpointLoaderSimple"]
|
|
77
|
+
if "input" in ckpt_info and "required" in ckpt_info["input"]:
|
|
78
|
+
ckpt_input = ckpt_info["input"]["required"].get("ckpt_name", [])
|
|
79
|
+
if isinstance(ckpt_input, list) and len(ckpt_input) > 0:
|
|
80
|
+
if isinstance(ckpt_input[0], list):
|
|
81
|
+
models["checkpoints"] = ckpt_input[0]
|
|
82
|
+
|
|
83
|
+
return models
|
|
84
|
+
|
|
85
|
+
async def generate_image(
|
|
86
|
+
self,
|
|
87
|
+
prompt: str,
|
|
88
|
+
negative_prompt: str = "",
|
|
89
|
+
width: int = 512,
|
|
90
|
+
height: int = 512,
|
|
91
|
+
steps: int = 20,
|
|
92
|
+
cfg: float = 7.0,
|
|
93
|
+
seed: Optional[int] = None,
|
|
94
|
+
checkpoint: Optional[str] = None,
|
|
95
|
+
) -> GenerationResult:
|
|
96
|
+
"""Generate an image using ComfyUI's txt2img workflow.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
prompt: Positive prompt describing what to generate
|
|
100
|
+
negative_prompt: What to avoid in the generation
|
|
101
|
+
width: Image width
|
|
102
|
+
height: Image height
|
|
103
|
+
steps: Number of sampling steps
|
|
104
|
+
cfg: Classifier-free guidance scale
|
|
105
|
+
seed: Random seed (None for random)
|
|
106
|
+
checkpoint: Model checkpoint to use (None for default)
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
GenerationResult with paths to generated images
|
|
110
|
+
"""
|
|
111
|
+
if seed is None:
|
|
112
|
+
import random
|
|
113
|
+
seed = random.randint(0, 2**32 - 1)
|
|
114
|
+
|
|
115
|
+
# Build the workflow
|
|
116
|
+
workflow = self._build_txt2img_workflow(
|
|
117
|
+
prompt=prompt,
|
|
118
|
+
negative_prompt=negative_prompt,
|
|
119
|
+
width=width,
|
|
120
|
+
height=height,
|
|
121
|
+
steps=steps,
|
|
122
|
+
cfg=cfg,
|
|
123
|
+
seed=seed,
|
|
124
|
+
checkpoint=checkpoint
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
return await self._execute_workflow(workflow)
|
|
128
|
+
|
|
129
|
+
async def generate_image_from_image(
|
|
130
|
+
self,
|
|
131
|
+
image_path: str,
|
|
132
|
+
prompt: str,
|
|
133
|
+
negative_prompt: str = "",
|
|
134
|
+
denoise: float = 0.75,
|
|
135
|
+
steps: int = 20,
|
|
136
|
+
cfg: float = 7.0,
|
|
137
|
+
seed: Optional[int] = None,
|
|
138
|
+
) -> GenerationResult:
|
|
139
|
+
"""Generate an image using img2img workflow.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
image_path: Path to input image
|
|
143
|
+
prompt: Positive prompt
|
|
144
|
+
negative_prompt: Negative prompt
|
|
145
|
+
denoise: Denoising strength (0-1, higher = more change)
|
|
146
|
+
steps: Sampling steps
|
|
147
|
+
cfg: Guidance scale
|
|
148
|
+
seed: Random seed
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
GenerationResult with paths to generated images
|
|
152
|
+
"""
|
|
153
|
+
# TODO: Implement img2img workflow
|
|
154
|
+
# This requires uploading the image first via /upload/image
|
|
155
|
+
raise NotImplementedError("img2img not yet implemented")
|
|
156
|
+
|
|
157
|
+
def _build_txt2img_workflow(
|
|
158
|
+
self,
|
|
159
|
+
prompt: str,
|
|
160
|
+
negative_prompt: str,
|
|
161
|
+
width: int,
|
|
162
|
+
height: int,
|
|
163
|
+
steps: int,
|
|
164
|
+
cfg: float,
|
|
165
|
+
seed: int,
|
|
166
|
+
checkpoint: Optional[str] = None
|
|
167
|
+
) -> Dict[str, Any]:
|
|
168
|
+
"""Build a txt2img workflow in ComfyUI API format."""
|
|
169
|
+
|
|
170
|
+
# Default checkpoint if not specified
|
|
171
|
+
if checkpoint is None:
|
|
172
|
+
checkpoint = "v1-5-pruned-emaonly.safetensors"
|
|
173
|
+
|
|
174
|
+
workflow = {
|
|
175
|
+
"3": {
|
|
176
|
+
"class_type": "KSampler",
|
|
177
|
+
"inputs": {
|
|
178
|
+
"cfg": cfg,
|
|
179
|
+
"denoise": 1,
|
|
180
|
+
"latent_image": ["5", 0],
|
|
181
|
+
"model": ["4", 0],
|
|
182
|
+
"negative": ["7", 0],
|
|
183
|
+
"positive": ["6", 0],
|
|
184
|
+
"sampler_name": "euler",
|
|
185
|
+
"scheduler": "normal",
|
|
186
|
+
"seed": seed,
|
|
187
|
+
"steps": steps
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
"4": {
|
|
191
|
+
"class_type": "CheckpointLoaderSimple",
|
|
192
|
+
"inputs": {
|
|
193
|
+
"ckpt_name": checkpoint
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
"5": {
|
|
197
|
+
"class_type": "EmptyLatentImage",
|
|
198
|
+
"inputs": {
|
|
199
|
+
"batch_size": 1,
|
|
200
|
+
"height": height,
|
|
201
|
+
"width": width
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
"6": {
|
|
205
|
+
"class_type": "CLIPTextEncode",
|
|
206
|
+
"inputs": {
|
|
207
|
+
"clip": ["4", 1],
|
|
208
|
+
"text": prompt
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
"7": {
|
|
212
|
+
"class_type": "CLIPTextEncode",
|
|
213
|
+
"inputs": {
|
|
214
|
+
"clip": ["4", 1],
|
|
215
|
+
"text": negative_prompt
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
"8": {
|
|
219
|
+
"class_type": "VAEDecode",
|
|
220
|
+
"inputs": {
|
|
221
|
+
"samples": ["3", 0],
|
|
222
|
+
"vae": ["4", 2]
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
"9": {
|
|
226
|
+
"class_type": "SaveImage",
|
|
227
|
+
"inputs": {
|
|
228
|
+
"filename_prefix": "vibe_aigc",
|
|
229
|
+
"images": ["8", 0]
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return workflow
|
|
235
|
+
|
|
236
|
+
async def _execute_workflow(self, workflow: Dict[str, Any]) -> GenerationResult:
|
|
237
|
+
"""Execute a workflow and wait for completion."""
|
|
238
|
+
prompt_id = str(uuid.uuid4())
|
|
239
|
+
|
|
240
|
+
payload = {
|
|
241
|
+
"prompt": workflow,
|
|
242
|
+
"client_id": self._client_id
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
try:
|
|
246
|
+
async with aiohttp.ClientSession() as session:
|
|
247
|
+
# Queue the prompt
|
|
248
|
+
async with session.post(
|
|
249
|
+
f"{self.config.base_url}/prompt",
|
|
250
|
+
json=payload
|
|
251
|
+
) as resp:
|
|
252
|
+
if resp.status != 200:
|
|
253
|
+
error_text = await resp.text()
|
|
254
|
+
return GenerationResult(
|
|
255
|
+
success=False,
|
|
256
|
+
error=f"Failed to queue prompt: {error_text}"
|
|
257
|
+
)
|
|
258
|
+
result = await resp.json()
|
|
259
|
+
prompt_id = result.get("prompt_id", prompt_id)
|
|
260
|
+
|
|
261
|
+
# Wait for completion by polling history
|
|
262
|
+
images = await self._wait_for_completion(session, prompt_id)
|
|
263
|
+
|
|
264
|
+
return GenerationResult(
|
|
265
|
+
success=True,
|
|
266
|
+
images=images,
|
|
267
|
+
prompt_id=prompt_id,
|
|
268
|
+
metadata={"workflow": workflow}
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
except Exception as e:
|
|
272
|
+
return GenerationResult(
|
|
273
|
+
success=False,
|
|
274
|
+
error=str(e)
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
async def _wait_for_completion(
|
|
278
|
+
self,
|
|
279
|
+
session: aiohttp.ClientSession,
|
|
280
|
+
prompt_id: str,
|
|
281
|
+
timeout: float = 300,
|
|
282
|
+
poll_interval: float = 0.5
|
|
283
|
+
) -> List[str]:
|
|
284
|
+
"""Wait for a prompt to complete and return output images."""
|
|
285
|
+
import time
|
|
286
|
+
start_time = time.time()
|
|
287
|
+
|
|
288
|
+
while time.time() - start_time < timeout:
|
|
289
|
+
async with session.get(f"{self.config.base_url}/history/{prompt_id}") as resp:
|
|
290
|
+
if resp.status == 200:
|
|
291
|
+
history = await resp.json()
|
|
292
|
+
|
|
293
|
+
if prompt_id in history:
|
|
294
|
+
prompt_data = history[prompt_id]
|
|
295
|
+
|
|
296
|
+
# Check if completed
|
|
297
|
+
if prompt_data.get("status", {}).get("completed", False):
|
|
298
|
+
# Extract image paths from outputs
|
|
299
|
+
images = []
|
|
300
|
+
outputs = prompt_data.get("outputs", {})
|
|
301
|
+
|
|
302
|
+
for node_id, node_output in outputs.items():
|
|
303
|
+
if "images" in node_output:
|
|
304
|
+
for img in node_output["images"]:
|
|
305
|
+
filename = img.get("filename")
|
|
306
|
+
subfolder = img.get("subfolder", "")
|
|
307
|
+
if filename:
|
|
308
|
+
# Build the view URL
|
|
309
|
+
img_url = f"{self.config.base_url}/view?filename={filename}"
|
|
310
|
+
if subfolder:
|
|
311
|
+
img_url += f"&subfolder={subfolder}"
|
|
312
|
+
images.append(img_url)
|
|
313
|
+
|
|
314
|
+
return images
|
|
315
|
+
|
|
316
|
+
await asyncio.sleep(poll_interval)
|
|
317
|
+
|
|
318
|
+
raise TimeoutError(f"Generation timed out after {timeout}s")
|
|
319
|
+
|
|
320
|
+
async def download_image(self, url: str, output_path: str) -> str:
|
|
321
|
+
"""Download a generated image to a local file."""
|
|
322
|
+
async with aiohttp.ClientSession() as session:
|
|
323
|
+
async with session.get(url) as resp:
|
|
324
|
+
if resp.status == 200:
|
|
325
|
+
content = await resp.read()
|
|
326
|
+
Path(output_path).parent.mkdir(parents=True, exist_ok=True)
|
|
327
|
+
with open(output_path, "wb") as f:
|
|
328
|
+
f.write(content)
|
|
329
|
+
return output_path
|
|
330
|
+
else:
|
|
331
|
+
raise RuntimeError(f"Failed to download image: {resp.status}")
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
# Integration with vibe-aigc tools system
|
|
335
|
+
from .tools import BaseTool, ToolResult, ToolSpec, ToolCategory
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
class ComfyUIImageTool(BaseTool):
|
|
339
|
+
"""Tool for generating images via ComfyUI.
|
|
340
|
+
|
|
341
|
+
This is the actual content generation that makes vibe-aigc
|
|
342
|
+
a true AIGC system, not just text orchestration.
|
|
343
|
+
"""
|
|
344
|
+
|
|
345
|
+
def __init__(self, config: Optional[ComfyUIConfig] = None):
|
|
346
|
+
self.backend = ComfyUIBackend(config)
|
|
347
|
+
self._spec = ToolSpec(
|
|
348
|
+
name="comfyui_image",
|
|
349
|
+
description="Generate images using ComfyUI (local Stable Diffusion)",
|
|
350
|
+
category=ToolCategory.IMAGE,
|
|
351
|
+
input_schema={
|
|
352
|
+
"type": "object",
|
|
353
|
+
"required": ["prompt"],
|
|
354
|
+
"properties": {
|
|
355
|
+
"prompt": {"type": "string", "description": "Image generation prompt"},
|
|
356
|
+
"negative_prompt": {"type": "string", "description": "What to avoid"},
|
|
357
|
+
"width": {"type": "integer", "default": 512},
|
|
358
|
+
"height": {"type": "integer", "default": 512},
|
|
359
|
+
"steps": {"type": "integer", "default": 20},
|
|
360
|
+
"cfg": {"type": "number", "default": 7.0},
|
|
361
|
+
"seed": {"type": "integer", "description": "Random seed"},
|
|
362
|
+
"checkpoint": {"type": "string", "description": "Model checkpoint"}
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
output_schema={
|
|
366
|
+
"type": "object",
|
|
367
|
+
"properties": {
|
|
368
|
+
"images": {"type": "array", "items": {"type": "string"}},
|
|
369
|
+
"prompt_id": {"type": "string"}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
@property
|
|
375
|
+
def spec(self) -> ToolSpec:
|
|
376
|
+
return self._spec
|
|
377
|
+
|
|
378
|
+
async def execute(
|
|
379
|
+
self,
|
|
380
|
+
inputs: Dict[str, Any],
|
|
381
|
+
context: Optional[Dict[str, Any]] = None
|
|
382
|
+
) -> ToolResult:
|
|
383
|
+
"""Execute image generation.
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
inputs: Must contain 'prompt', optionally:
|
|
387
|
+
- negative_prompt
|
|
388
|
+
- width, height
|
|
389
|
+
- steps, cfg
|
|
390
|
+
- seed
|
|
391
|
+
- checkpoint
|
|
392
|
+
"""
|
|
393
|
+
prompt = inputs.get("prompt", "")
|
|
394
|
+
if not prompt:
|
|
395
|
+
return ToolResult(
|
|
396
|
+
success=False,
|
|
397
|
+
output=None,
|
|
398
|
+
error="No prompt provided"
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
# Check if ComfyUI is available
|
|
402
|
+
if not await self.backend.is_available():
|
|
403
|
+
return ToolResult(
|
|
404
|
+
success=False,
|
|
405
|
+
output=None,
|
|
406
|
+
error="ComfyUI is not running. Start it at http://127.0.0.1:8188"
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
result = await self.backend.generate_image(
|
|
410
|
+
prompt=prompt,
|
|
411
|
+
negative_prompt=inputs.get("negative_prompt", ""),
|
|
412
|
+
width=inputs.get("width", 512),
|
|
413
|
+
height=inputs.get("height", 512),
|
|
414
|
+
steps=inputs.get("steps", 20),
|
|
415
|
+
cfg=inputs.get("cfg", 7.0),
|
|
416
|
+
seed=inputs.get("seed"),
|
|
417
|
+
checkpoint=inputs.get("checkpoint")
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
if result.success:
|
|
421
|
+
return ToolResult(
|
|
422
|
+
success=True,
|
|
423
|
+
output={
|
|
424
|
+
"images": result.images,
|
|
425
|
+
"prompt_id": result.prompt_id
|
|
426
|
+
},
|
|
427
|
+
metadata=result.metadata
|
|
428
|
+
)
|
|
429
|
+
else:
|
|
430
|
+
return ToolResult(
|
|
431
|
+
success=False,
|
|
432
|
+
output=None,
|
|
433
|
+
error=result.error
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def create_comfyui_registry(config: Optional[ComfyUIConfig] = None):
|
|
438
|
+
"""Create a tool registry with ComfyUI tools.
|
|
439
|
+
|
|
440
|
+
This adds actual content generation capabilities to vibe-aigc.
|
|
441
|
+
"""
|
|
442
|
+
from .tools import ToolRegistry
|
|
443
|
+
|
|
444
|
+
registry = ToolRegistry()
|
|
445
|
+
registry.register(ComfyUIImageTool(config))
|
|
446
|
+
|
|
447
|
+
return registry
|
vibe_aigc/knowledge.py
CHANGED
|
@@ -63,6 +63,7 @@ class KnowledgeBase:
|
|
|
63
63
|
self._load_writing_knowledge()
|
|
64
64
|
self._load_design_knowledge()
|
|
65
65
|
self._load_music_knowledge()
|
|
66
|
+
self._load_visual_generation_knowledge()
|
|
66
67
|
|
|
67
68
|
def _load_film_knowledge(self) -> None:
|
|
68
69
|
"""Film/video production knowledge."""
|
|
@@ -269,6 +270,182 @@ class KnowledgeBase:
|
|
|
269
270
|
|
|
270
271
|
self._domains["music"] = music
|
|
271
272
|
|
|
273
|
+
def _load_visual_generation_knowledge(self) -> None:
|
|
274
|
+
"""Visual/image generation knowledge for Stable Diffusion & ComfyUI."""
|
|
275
|
+
visual = DomainKnowledge(domain="visual")
|
|
276
|
+
|
|
277
|
+
# === AESTHETIC STYLES ===
|
|
278
|
+
visual.add_concept(
|
|
279
|
+
name="cyberpunk",
|
|
280
|
+
description="Futuristic dystopian aesthetic with neon and tech",
|
|
281
|
+
technical_specs={
|
|
282
|
+
"sd_prompt_tags": ["cyberpunk", "neon lights", "futuristic city", "rain",
|
|
283
|
+
"holographic displays", "chrome", "dystopian"],
|
|
284
|
+
"color_palette": ["#FF00FF", "#00FFFF", "#FF0080", "#00FF80", "#1a1a2e"],
|
|
285
|
+
"lighting": ["neon glow", "rim lighting", "volumetric fog", "light rays"],
|
|
286
|
+
"composition": ["low angle", "wide shot", "reflective surfaces"],
|
|
287
|
+
"negative_prompt": ["natural lighting", "daytime", "pastoral", "bright colors"]
|
|
288
|
+
},
|
|
289
|
+
examples=["Blade Runner", "Ghost in the Shell", "Akira"]
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
visual.add_concept(
|
|
293
|
+
name="neon noir",
|
|
294
|
+
description="Modern noir with neon lighting and rain-slicked streets",
|
|
295
|
+
technical_specs={
|
|
296
|
+
"sd_prompt_tags": ["neon noir", "rain", "night city", "wet streets",
|
|
297
|
+
"reflections", "moody", "cinematic lighting"],
|
|
298
|
+
"color_palette": ["#FF00FF", "#00FFFF", "#1a1a2e", "#2d132c"],
|
|
299
|
+
"lighting": ["neon signs", "puddle reflections", "dramatic shadows", "rim light"],
|
|
300
|
+
"composition": ["dutch angle", "silhouettes", "deep shadows"],
|
|
301
|
+
"negative_prompt": ["bright", "cheerful", "daytime", "sunny"]
|
|
302
|
+
}
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
visual.add_concept(
|
|
306
|
+
name="anime",
|
|
307
|
+
description="Japanese animation style",
|
|
308
|
+
technical_specs={
|
|
309
|
+
"sd_prompt_tags": ["anime", "cel shading", "vibrant colors",
|
|
310
|
+
"detailed eyes", "dramatic pose"],
|
|
311
|
+
"models": ["anything-v5", "counterfeit-v3", "waifu-diffusion"],
|
|
312
|
+
"cfg_scale": 7,
|
|
313
|
+
"negative_prompt": ["realistic", "photographic", "3d render", "western"]
|
|
314
|
+
}
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
visual.add_concept(
|
|
318
|
+
name="photorealistic",
|
|
319
|
+
description="Realistic, photo-quality images",
|
|
320
|
+
technical_specs={
|
|
321
|
+
"sd_prompt_tags": ["photorealistic", "hyperrealistic", "8k", "uhd",
|
|
322
|
+
"RAW photo", "dslr", "soft lighting", "high detail"],
|
|
323
|
+
"models": ["realistic-vision", "deliberate", "photon"],
|
|
324
|
+
"cfg_scale": 5,
|
|
325
|
+
"steps": 30,
|
|
326
|
+
"negative_prompt": ["cartoon", "anime", "painting", "illustration",
|
|
327
|
+
"drawing", "artificial", "fake"]
|
|
328
|
+
}
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
visual.add_concept(
|
|
332
|
+
name="cinematic",
|
|
333
|
+
description="Movie-quality visuals with dramatic lighting",
|
|
334
|
+
technical_specs={
|
|
335
|
+
"sd_prompt_tags": ["cinematic", "dramatic lighting", "film grain",
|
|
336
|
+
"anamorphic", "depth of field", "bokeh", "lens flare"],
|
|
337
|
+
"aspect_ratio": "21:9 or 16:9",
|
|
338
|
+
"lighting": ["three-point lighting", "golden hour", "blue hour", "volumetric"],
|
|
339
|
+
"composition": ["rule of thirds", "leading lines", "depth layers"]
|
|
340
|
+
}
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
visual.add_concept(
|
|
344
|
+
name="concept art",
|
|
345
|
+
description="Professional concept art style for games/films",
|
|
346
|
+
technical_specs={
|
|
347
|
+
"sd_prompt_tags": ["concept art", "digital painting", "artstation",
|
|
348
|
+
"trending on artstation", "matte painting", "detailed"],
|
|
349
|
+
"artists": ["greg rutkowski", "craig mullins", "feng zhu"],
|
|
350
|
+
"negative_prompt": ["photo", "realistic", "amateur"]
|
|
351
|
+
}
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
visual.add_concept(
|
|
355
|
+
name="portrait",
|
|
356
|
+
description="Character portrait focus",
|
|
357
|
+
technical_specs={
|
|
358
|
+
"sd_prompt_tags": ["portrait", "headshot", "face focus", "detailed face",
|
|
359
|
+
"sharp focus", "studio lighting"],
|
|
360
|
+
"composition": ["centered", "eye level", "close-up"],
|
|
361
|
+
"lighting": ["rembrandt lighting", "butterfly lighting", "split lighting"],
|
|
362
|
+
"negative_prompt": ["full body", "wide shot", "multiple people", "crowd"]
|
|
363
|
+
}
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
# === QUALITY MODIFIERS ===
|
|
367
|
+
visual.add_concept(
|
|
368
|
+
name="high quality",
|
|
369
|
+
description="Quality boosting prompt modifiers",
|
|
370
|
+
technical_specs={
|
|
371
|
+
"sd_prompt_tags": ["masterpiece", "best quality", "highly detailed",
|
|
372
|
+
"sharp focus", "intricate details", "professional"],
|
|
373
|
+
"negative_prompt": ["worst quality", "low quality", "blurry", "jpeg artifacts",
|
|
374
|
+
"watermark", "signature", "text", "error", "cropped"]
|
|
375
|
+
}
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
# === LIGHTING TECHNIQUES ===
|
|
379
|
+
visual.add_technique("dramatic lighting", [
|
|
380
|
+
"Add 'dramatic lighting' or 'cinematic lighting' to prompt",
|
|
381
|
+
"Include specific light source (neon, golden hour, etc.)",
|
|
382
|
+
"Add 'volumetric lighting' or 'god rays' for atmosphere",
|
|
383
|
+
"Use high contrast in composition"
|
|
384
|
+
])
|
|
385
|
+
|
|
386
|
+
visual.add_technique("neon lighting", [
|
|
387
|
+
"Specify neon colors: 'pink neon', 'cyan neon', 'purple neon'",
|
|
388
|
+
"Add 'neon glow', 'neon signs', 'neon reflections'",
|
|
389
|
+
"Include 'wet streets' or 'rain' for reflections",
|
|
390
|
+
"Set scene at night for contrast"
|
|
391
|
+
])
|
|
392
|
+
|
|
393
|
+
# === COMFYUI WORKFLOW KNOWLEDGE ===
|
|
394
|
+
visual.add_concept(
|
|
395
|
+
name="txt2img",
|
|
396
|
+
description="Text to image generation workflow",
|
|
397
|
+
technical_specs={
|
|
398
|
+
"nodes": ["CheckpointLoaderSimple", "CLIPTextEncode", "KSampler",
|
|
399
|
+
"VAEDecode", "SaveImage"],
|
|
400
|
+
"optimal_settings": {
|
|
401
|
+
"steps": "20-30 for quality, 10-15 for speed",
|
|
402
|
+
"cfg": "7-8 for balanced, 5-6 for creative, 10+ for strict",
|
|
403
|
+
"sampler": "euler_ancestral for variety, dpm++ for quality"
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
visual.add_concept(
|
|
409
|
+
name="img2img",
|
|
410
|
+
description="Image to image transformation workflow",
|
|
411
|
+
technical_specs={
|
|
412
|
+
"nodes": ["LoadImage", "VAEEncode", "KSampler", "VAEDecode", "SaveImage"],
|
|
413
|
+
"optimal_settings": {
|
|
414
|
+
"denoise": "0.4-0.6 for subtle changes, 0.7-0.9 for major changes",
|
|
415
|
+
"steps": "20-30",
|
|
416
|
+
"cfg": "7-10"
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
visual.add_concept(
|
|
422
|
+
name="character consistency",
|
|
423
|
+
description="Maintaining character appearance across generations",
|
|
424
|
+
technical_specs={
|
|
425
|
+
"techniques": ["IPAdapter", "reference image", "trained LoRA"],
|
|
426
|
+
"nodes": ["IPAdapter", "IPAdapterFaceID", "ControlNet"],
|
|
427
|
+
"tips": ["Use same seed for similar poses", "Train character LoRA for best results"]
|
|
428
|
+
}
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
# === CONSTRAINTS ===
|
|
432
|
+
visual.add_constraint("8gb vram", [
|
|
433
|
+
"Use SD 1.5 or SDXL with optimizations",
|
|
434
|
+
"Enable --lowvram or --medvram flags",
|
|
435
|
+
"Max resolution ~768x768 for SD1.5, ~1024x1024 for SDXL",
|
|
436
|
+
"Use fp8 for Flux models",
|
|
437
|
+
"Avoid large batch sizes"
|
|
438
|
+
])
|
|
439
|
+
|
|
440
|
+
visual.add_constraint("video generation", [
|
|
441
|
+
"AnimateDiff for short clips (16-32 frames)",
|
|
442
|
+
"CogVideoX for longer content (requires more VRAM)",
|
|
443
|
+
"Keep consistent seed and prompt for coherence",
|
|
444
|
+
"Use motion LoRA for specific movements"
|
|
445
|
+
])
|
|
446
|
+
|
|
447
|
+
self._domains["visual"] = visual
|
|
448
|
+
|
|
272
449
|
def register_domain(self, knowledge: DomainKnowledge) -> None:
|
|
273
450
|
"""Register a custom domain knowledge module."""
|
|
274
451
|
self._domains[knowledge.domain.lower()] = knowledge
|
vibe_aigc/llm.py
CHANGED
|
@@ -16,6 +16,7 @@ class LLMConfig(BaseModel):
|
|
|
16
16
|
temperature: float = 0.7
|
|
17
17
|
max_tokens: int = 2000
|
|
18
18
|
api_key: Optional[str] = None
|
|
19
|
+
base_url: Optional[str] = None # Custom endpoint (e.g., z.ai, local models)
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
class LLMClient:
|
|
@@ -24,7 +25,10 @@ class LLMClient:
|
|
|
24
25
|
def __init__(self, config: Optional[LLMConfig] = None):
|
|
25
26
|
self.config = config or LLMConfig()
|
|
26
27
|
try:
|
|
27
|
-
|
|
28
|
+
client_kwargs = {"api_key": self.config.api_key}
|
|
29
|
+
if self.config.base_url:
|
|
30
|
+
client_kwargs["base_url"] = self.config.base_url
|
|
31
|
+
self.client = AsyncOpenAI(**client_kwargs)
|
|
28
32
|
except Exception as e:
|
|
29
33
|
if "api_key" in str(e).lower():
|
|
30
34
|
raise RuntimeError(
|
|
@@ -123,6 +127,17 @@ Focus on logical decomposition and clear dependencies. Keep tasks atomic and exe
|
|
|
123
127
|
"the request was filtered. Please try again or adjust your vibe."
|
|
124
128
|
)
|
|
125
129
|
|
|
130
|
+
# Strip markdown code blocks if present (common with some LLMs like z.ai/GLM)
|
|
131
|
+
content = content.strip()
|
|
132
|
+
if content.startswith("```"):
|
|
133
|
+
# Remove opening ```json or ```
|
|
134
|
+
first_newline = content.find("\n")
|
|
135
|
+
if first_newline != -1:
|
|
136
|
+
content = content[first_newline + 1:]
|
|
137
|
+
# Remove closing ```
|
|
138
|
+
if content.endswith("```"):
|
|
139
|
+
content = content[:-3].strip()
|
|
140
|
+
|
|
126
141
|
return json.loads(content)
|
|
127
142
|
|
|
128
143
|
except json.JSONDecodeError as e:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vibe-aigc
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A New Paradigm for Content Generation via Agentic Orchestration
|
|
5
5
|
Author: Vibe AIGC Contributors
|
|
6
6
|
License-Expression: MIT
|
|
@@ -19,6 +19,7 @@ Classifier: Typing :: Typed
|
|
|
19
19
|
Requires-Python: >=3.12
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
21
|
License-File: LICENSE
|
|
22
|
+
Requires-Dist: aiohttp>=3.9.0
|
|
22
23
|
Requires-Dist: pydantic>=2.0.0
|
|
23
24
|
Requires-Dist: openai>=1.0.0
|
|
24
25
|
Provides-Extra: dev
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
vibe_aigc/__init__.py,sha256=
|
|
1
|
+
vibe_aigc/__init__.py,sha256=xBc4OHS4gG7mRYLFI3M6AkJGviqNj-xzY11QhW6kFp8,3122
|
|
2
2
|
vibe_aigc/agents.py,sha256=MAUQgJ4eygv3Uvri1xzF-WdExxyyPQvMWfXrW0AF9jE,21844
|
|
3
3
|
vibe_aigc/assets.py,sha256=6CemoYHlssMpPn6lY13lw-98ZyUoKFqdNbqRagqZgUo,12462
|
|
4
4
|
vibe_aigc/cli.py,sha256=7J62Bsa0FbbXrw5o5Cy7BpidAO99MOmKN_hlVjoSkhQ,7481
|
|
5
|
+
vibe_aigc/comfyui.py,sha256=Do7kOaeiuJ0SVYsib2I5prOn5O0M_u0gPbK32cFHYg4,15663
|
|
5
6
|
vibe_aigc/executor.py,sha256=AWTqa7hoDljhXjp6gCekzbTGROtD8kptb0IU3mi03lw,38977
|
|
6
|
-
vibe_aigc/knowledge.py,sha256=
|
|
7
|
-
vibe_aigc/llm.py,sha256=
|
|
7
|
+
vibe_aigc/knowledge.py,sha256=UCh4AchG64T9mlXQcowYR2ywebic_ph3nvA7E-TdLPY,23820
|
|
8
|
+
vibe_aigc/llm.py,sha256=ovRVKWGmcTmBTI5FI_MERK9ouxbEaO26-ArOaKTWqMY,6850
|
|
8
9
|
vibe_aigc/models.py,sha256=wIwlN_NDL_72YXRq3Igi5X5fx1ZFGG7ER6pO5MdL4-I,2408
|
|
9
10
|
vibe_aigc/persistence.py,sha256=inrJQjmCK4LighxQSmJorR6c7OvRzx-cmEb5HCQS9PY,10614
|
|
10
11
|
vibe_aigc/planner.py,sha256=DRGIY7N7o4fl3ioa3azqSQf4eJwXCKgSoAg-lNaRq6U,26364
|
|
11
12
|
vibe_aigc/tools.py,sha256=Tm_NA53yJjjvCrUuZ7YVtdLAdfUgxOLm5zZzIcJYvHI,15572
|
|
12
13
|
vibe_aigc/tools_multimodal.py,sha256=asSJJqF0hrD9uNiYpuieVY-lbgEXjbK3UjT20nX2Lig,20405
|
|
13
14
|
vibe_aigc/visualization.py,sha256=jDs2f1vj4k8ZnJTA_niKLBH2NMahTgWneiADlNmW24s,7143
|
|
14
|
-
vibe_aigc-0.
|
|
15
|
-
vibe_aigc-0.
|
|
16
|
-
vibe_aigc-0.
|
|
17
|
-
vibe_aigc-0.
|
|
18
|
-
vibe_aigc-0.
|
|
19
|
-
vibe_aigc-0.
|
|
15
|
+
vibe_aigc-0.3.0.dist-info/licenses/LICENSE,sha256=Ir4dCTvOsbfoiOh9vYbhIKDH59S7J6qhJYZmHHICoKY,1079
|
|
16
|
+
vibe_aigc-0.3.0.dist-info/METADATA,sha256=Gjoxon64TochZCxr82gVUo4U294LyzZZ3HLd31ew5_M,6604
|
|
17
|
+
vibe_aigc-0.3.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
18
|
+
vibe_aigc-0.3.0.dist-info/entry_points.txt,sha256=2htp4yXJMvCAQXTB39XWWwbBPP3MYUYXsqlwMeQsd7o,49
|
|
19
|
+
vibe_aigc-0.3.0.dist-info/top_level.txt,sha256=Cpjz8X0WEhnhaigqxmsZSl9VxduaDspj7WuVUGGLeao,10
|
|
20
|
+
vibe_aigc-0.3.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|