vibe-aigc 0.1.1__tar.gz → 0.2.0__tar.gz
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-0.1.1/vibe_aigc.egg-info → vibe_aigc-0.2.0}/PKG-INFO +27 -1
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/README.md +26 -0
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/pyproject.toml +1 -1
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/tests/test_adaptive_replanning.py +1 -1
- vibe_aigc-0.2.0/tests/test_agents.py +231 -0
- vibe_aigc-0.2.0/tests/test_assets.py +291 -0
- vibe_aigc-0.2.0/tests/test_knowledge_base.py +161 -0
- vibe_aigc-0.2.0/tests/test_tools.py +274 -0
- vibe_aigc-0.2.0/vibe_aigc/__init__.py +101 -0
- vibe_aigc-0.2.0/vibe_aigc/agents.py +581 -0
- vibe_aigc-0.2.0/vibe_aigc/assets.py +386 -0
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/vibe_aigc/executor.py +138 -8
- vibe_aigc-0.2.0/vibe_aigc/knowledge.py +398 -0
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/vibe_aigc/llm.py +53 -11
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/vibe_aigc/planner.py +38 -5
- vibe_aigc-0.2.0/vibe_aigc/tools.py +508 -0
- vibe_aigc-0.2.0/vibe_aigc/tools_multimodal.py +613 -0
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0/vibe_aigc.egg-info}/PKG-INFO +27 -1
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/vibe_aigc.egg-info/SOURCES.txt +9 -0
- vibe_aigc-0.1.1/vibe_aigc/__init__.py +0 -13
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/LICENSE +0 -0
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/setup.cfg +0 -0
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/tests/test_auto_checkpoint.py +0 -0
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/tests/test_automatic_checkpoints.py +0 -0
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/tests/test_checkpoint_serialization.py +0 -0
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/tests/test_error_handling.py +0 -0
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/tests/test_executor.py +0 -0
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/tests/test_feedback_system.py +0 -0
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/tests/test_integration.py +0 -0
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/tests/test_metaplanner_resume.py +0 -0
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/tests/test_metaplanner_visualization.py +0 -0
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/tests/test_models.py +0 -0
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/tests/test_parallel_execution.py +0 -0
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/tests/test_planner.py +0 -0
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/tests/test_progress_callbacks.py +0 -0
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/tests/test_visualization.py +0 -0
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/tests/test_workflow_resume.py +0 -0
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/vibe_aigc/cli.py +0 -0
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/vibe_aigc/models.py +0 -0
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/vibe_aigc/persistence.py +0 -0
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/vibe_aigc/visualization.py +0 -0
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/vibe_aigc.egg-info/dependency_links.txt +0 -0
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/vibe_aigc.egg-info/entry_points.txt +0 -0
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/vibe_aigc.egg-info/requires.txt +0 -0
- {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/vibe_aigc.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vibe-aigc
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: A New Paradigm for Content Generation via Agentic Orchestration
|
|
5
5
|
Author: Vibe AIGC Contributors
|
|
6
6
|
License-Expression: MIT
|
|
@@ -69,9 +69,35 @@ planner = MetaPlanner()
|
|
|
69
69
|
result = await planner.execute(vibe)
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
+
## Architecture (Paper Section 5)
|
|
73
|
+
|
|
74
|
+
The implementation follows the paper's three-part architecture:
|
|
75
|
+
|
|
76
|
+
| Component | Purpose | Module |
|
|
77
|
+
|-----------|---------|--------|
|
|
78
|
+
| **MetaPlanner** | Decomposes Vibes into workflows | `vibe_aigc.planner` |
|
|
79
|
+
| **KnowledgeBase** | Domain expertise for intent understanding | `vibe_aigc.knowledge` |
|
|
80
|
+
| **ToolRegistry** | Atomic tools for content generation | `vibe_aigc.tools` |
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from vibe_aigc import MetaPlanner, Vibe, create_knowledge_base, create_default_registry
|
|
84
|
+
|
|
85
|
+
# The full architecture
|
|
86
|
+
kb = create_knowledge_base() # Film, writing, design, music knowledge
|
|
87
|
+
tools = create_default_registry() # LLM, templates, combine tools
|
|
88
|
+
|
|
89
|
+
planner = MetaPlanner(knowledge_base=kb, tool_registry=tools)
|
|
90
|
+
|
|
91
|
+
# Query knowledge for "Hitchcockian suspense" → technical specs
|
|
92
|
+
result = kb.query("Hitchcockian suspense")
|
|
93
|
+
# Returns: camera techniques, lighting specs, editing patterns
|
|
94
|
+
```
|
|
95
|
+
|
|
72
96
|
## Features
|
|
73
97
|
|
|
74
98
|
- 🎯 **Vibe-based Planning** — High-level intent → executable workflows
|
|
99
|
+
- 🧠 **Domain Knowledge** — Built-in expertise for film, writing, design, music
|
|
100
|
+
- 🔧 **Tool Library** — Pluggable tools for actual content generation
|
|
75
101
|
- ⚡ **Parallel Execution** — Independent nodes run concurrently
|
|
76
102
|
- 🔄 **Adaptive Replanning** — Automatic recovery from failures
|
|
77
103
|
- 💾 **Checkpoint/Resume** — Save and restore workflow state
|
|
@@ -34,9 +34,35 @@ planner = MetaPlanner()
|
|
|
34
34
|
result = await planner.execute(vibe)
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
+
## Architecture (Paper Section 5)
|
|
38
|
+
|
|
39
|
+
The implementation follows the paper's three-part architecture:
|
|
40
|
+
|
|
41
|
+
| Component | Purpose | Module |
|
|
42
|
+
|-----------|---------|--------|
|
|
43
|
+
| **MetaPlanner** | Decomposes Vibes into workflows | `vibe_aigc.planner` |
|
|
44
|
+
| **KnowledgeBase** | Domain expertise for intent understanding | `vibe_aigc.knowledge` |
|
|
45
|
+
| **ToolRegistry** | Atomic tools for content generation | `vibe_aigc.tools` |
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from vibe_aigc import MetaPlanner, Vibe, create_knowledge_base, create_default_registry
|
|
49
|
+
|
|
50
|
+
# The full architecture
|
|
51
|
+
kb = create_knowledge_base() # Film, writing, design, music knowledge
|
|
52
|
+
tools = create_default_registry() # LLM, templates, combine tools
|
|
53
|
+
|
|
54
|
+
planner = MetaPlanner(knowledge_base=kb, tool_registry=tools)
|
|
55
|
+
|
|
56
|
+
# Query knowledge for "Hitchcockian suspense" → technical specs
|
|
57
|
+
result = kb.query("Hitchcockian suspense")
|
|
58
|
+
# Returns: camera techniques, lighting specs, editing patterns
|
|
59
|
+
```
|
|
60
|
+
|
|
37
61
|
## Features
|
|
38
62
|
|
|
39
63
|
- 🎯 **Vibe-based Planning** — High-level intent → executable workflows
|
|
64
|
+
- 🧠 **Domain Knowledge** — Built-in expertise for film, writing, design, music
|
|
65
|
+
- 🔧 **Tool Library** — Pluggable tools for actual content generation
|
|
40
66
|
- ⚡ **Parallel Execution** — Independent nodes run concurrently
|
|
41
67
|
- 🔄 **Adaptive Replanning** — Automatic recovery from failures
|
|
42
68
|
- 💾 **Checkpoint/Resume** — Save and restore workflow state
|
|
@@ -8,7 +8,7 @@ exclude = ["tests*", "docs*", "examples*", "landing*"]
|
|
|
8
8
|
|
|
9
9
|
[project]
|
|
10
10
|
name = "vibe-aigc"
|
|
11
|
-
version = "0.
|
|
11
|
+
version = "0.2.0"
|
|
12
12
|
description = "A New Paradigm for Content Generation via Agentic Orchestration"
|
|
13
13
|
authors = [{name = "Vibe AIGC Contributors"}]
|
|
14
14
|
license = "MIT"
|
|
@@ -282,7 +282,7 @@ class TestAdaptiveReplanning:
|
|
|
282
282
|
]
|
|
283
283
|
|
|
284
284
|
plan_call_count = 0
|
|
285
|
-
async def plan_side_effect(vibe):
|
|
285
|
+
async def plan_side_effect(vibe, knowledge_context=None, tools_context=None):
|
|
286
286
|
nonlocal plan_call_count
|
|
287
287
|
result = plans[min(plan_call_count, len(plans) - 1)]
|
|
288
288
|
plan_call_count += 1
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""Tests for Specialized Agent Framework."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from unittest.mock import AsyncMock, MagicMock
|
|
5
|
+
|
|
6
|
+
from vibe_aigc.agents import (
|
|
7
|
+
BaseAgent,
|
|
8
|
+
AgentRole,
|
|
9
|
+
AgentContext,
|
|
10
|
+
AgentResult,
|
|
11
|
+
AgentRegistry,
|
|
12
|
+
WriterAgent,
|
|
13
|
+
ResearcherAgent,
|
|
14
|
+
EditorAgent,
|
|
15
|
+
DirectorAgent,
|
|
16
|
+
DesignerAgent,
|
|
17
|
+
ScreenwriterAgent,
|
|
18
|
+
ComposerAgent,
|
|
19
|
+
create_default_agents
|
|
20
|
+
)
|
|
21
|
+
from vibe_aigc.tools import ToolRegistry, ToolResult
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class TestAgentContext:
|
|
25
|
+
"""Test AgentContext class."""
|
|
26
|
+
|
|
27
|
+
def test_create_context(self):
|
|
28
|
+
ctx = AgentContext(
|
|
29
|
+
task="Write a blog post",
|
|
30
|
+
vibe_description="Technical but accessible",
|
|
31
|
+
style="informative",
|
|
32
|
+
constraints=["under 1000 words"]
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
assert ctx.task == "Write a blog post"
|
|
36
|
+
assert ctx.style == "informative"
|
|
37
|
+
assert len(ctx.constraints) == 1
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class TestAgentResult:
|
|
41
|
+
"""Test AgentResult class."""
|
|
42
|
+
|
|
43
|
+
def test_success_result(self):
|
|
44
|
+
result = AgentResult(
|
|
45
|
+
success=True,
|
|
46
|
+
output="Generated content",
|
|
47
|
+
artifacts={"image": "url"},
|
|
48
|
+
messages=["Completed"]
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
assert result.success
|
|
52
|
+
assert result.output == "Generated content"
|
|
53
|
+
assert "image" in result.artifacts
|
|
54
|
+
|
|
55
|
+
def test_to_dict(self):
|
|
56
|
+
result = AgentResult(success=True, output="test")
|
|
57
|
+
d = result.to_dict()
|
|
58
|
+
|
|
59
|
+
assert d["success"] is True
|
|
60
|
+
assert d["output"] == "test"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class TestWriterAgent:
|
|
64
|
+
"""Test WriterAgent."""
|
|
65
|
+
|
|
66
|
+
def test_creation(self):
|
|
67
|
+
agent = WriterAgent()
|
|
68
|
+
|
|
69
|
+
assert agent.name == "Writer"
|
|
70
|
+
assert agent.role == AgentRole.WRITER
|
|
71
|
+
assert "llm_generate" in agent._capabilities
|
|
72
|
+
|
|
73
|
+
@pytest.mark.asyncio
|
|
74
|
+
async def test_execute_without_registry(self):
|
|
75
|
+
agent = WriterAgent()
|
|
76
|
+
ctx = AgentContext(task="Write something", vibe_description="Test")
|
|
77
|
+
|
|
78
|
+
result = await agent.execute(ctx)
|
|
79
|
+
|
|
80
|
+
assert not result.success
|
|
81
|
+
assert "No tool registry" in result.messages[0]
|
|
82
|
+
|
|
83
|
+
@pytest.mark.asyncio
|
|
84
|
+
async def test_execute_with_mock_registry(self):
|
|
85
|
+
# Create mock registry
|
|
86
|
+
mock_registry = MagicMock(spec=ToolRegistry)
|
|
87
|
+
mock_tool = AsyncMock()
|
|
88
|
+
mock_tool.execute.return_value = ToolResult(
|
|
89
|
+
success=True,
|
|
90
|
+
output={"text": "Generated text", "tokens_used": 100}
|
|
91
|
+
)
|
|
92
|
+
mock_registry.get.return_value = mock_tool
|
|
93
|
+
|
|
94
|
+
agent = WriterAgent(tool_registry=mock_registry)
|
|
95
|
+
ctx = AgentContext(task="Write a haiku", vibe_description="Peaceful")
|
|
96
|
+
|
|
97
|
+
result = await agent.execute(ctx)
|
|
98
|
+
|
|
99
|
+
assert result.success
|
|
100
|
+
assert "Generated text" in result.output
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class TestResearcherAgent:
|
|
104
|
+
"""Test ResearcherAgent."""
|
|
105
|
+
|
|
106
|
+
def test_creation(self):
|
|
107
|
+
agent = ResearcherAgent()
|
|
108
|
+
|
|
109
|
+
assert agent.role == AgentRole.RESEARCHER
|
|
110
|
+
assert "search" in agent._capabilities
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class TestEditorAgent:
|
|
114
|
+
"""Test EditorAgent."""
|
|
115
|
+
|
|
116
|
+
def test_creation(self):
|
|
117
|
+
agent = EditorAgent()
|
|
118
|
+
|
|
119
|
+
assert agent.role == AgentRole.EDITOR
|
|
120
|
+
|
|
121
|
+
@pytest.mark.asyncio
|
|
122
|
+
async def test_execute_without_content(self):
|
|
123
|
+
mock_registry = MagicMock(spec=ToolRegistry)
|
|
124
|
+
mock_registry.get.return_value = None
|
|
125
|
+
|
|
126
|
+
agent = EditorAgent(tool_registry=mock_registry)
|
|
127
|
+
ctx = AgentContext(task="Edit", vibe_description="Test")
|
|
128
|
+
|
|
129
|
+
result = await agent.execute(ctx)
|
|
130
|
+
|
|
131
|
+
assert not result.success
|
|
132
|
+
assert "No content" in result.messages[0]
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class TestDirectorAgent:
|
|
136
|
+
"""Test DirectorAgent."""
|
|
137
|
+
|
|
138
|
+
def test_add_managed_agent(self):
|
|
139
|
+
director = DirectorAgent()
|
|
140
|
+
writer = WriterAgent()
|
|
141
|
+
|
|
142
|
+
director.add_agent(writer)
|
|
143
|
+
|
|
144
|
+
assert "Writer" in director._managed_agents
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class TestDesignerAgent:
|
|
148
|
+
"""Test DesignerAgent."""
|
|
149
|
+
|
|
150
|
+
def test_creation(self):
|
|
151
|
+
agent = DesignerAgent()
|
|
152
|
+
|
|
153
|
+
assert agent.role == AgentRole.DESIGNER
|
|
154
|
+
assert "image_generate" in agent._capabilities
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class TestScreenwriterAgent:
|
|
158
|
+
"""Test ScreenwriterAgent."""
|
|
159
|
+
|
|
160
|
+
def test_creation(self):
|
|
161
|
+
agent = ScreenwriterAgent()
|
|
162
|
+
|
|
163
|
+
assert agent.role == AgentRole.SCREENWRITER
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class TestComposerAgent:
|
|
167
|
+
"""Test ComposerAgent."""
|
|
168
|
+
|
|
169
|
+
def test_creation(self):
|
|
170
|
+
agent = ComposerAgent()
|
|
171
|
+
|
|
172
|
+
assert agent.role == AgentRole.COMPOSER
|
|
173
|
+
assert "audio_generate" in agent._capabilities
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class TestAgentRegistry:
|
|
177
|
+
"""Test AgentRegistry."""
|
|
178
|
+
|
|
179
|
+
def test_register_and_get(self):
|
|
180
|
+
registry = AgentRegistry()
|
|
181
|
+
agent = WriterAgent()
|
|
182
|
+
|
|
183
|
+
registry.register(agent)
|
|
184
|
+
|
|
185
|
+
assert registry.get("Writer") == agent
|
|
186
|
+
|
|
187
|
+
def test_find_by_role(self):
|
|
188
|
+
registry = AgentRegistry()
|
|
189
|
+
registry.register(WriterAgent())
|
|
190
|
+
registry.register(EditorAgent())
|
|
191
|
+
|
|
192
|
+
writers = registry.find_by_role(AgentRole.WRITER)
|
|
193
|
+
|
|
194
|
+
assert len(writers) == 1
|
|
195
|
+
assert writers[0].role == AgentRole.WRITER
|
|
196
|
+
|
|
197
|
+
def test_list_agents(self):
|
|
198
|
+
registry = AgentRegistry()
|
|
199
|
+
registry.register(WriterAgent())
|
|
200
|
+
registry.register(EditorAgent())
|
|
201
|
+
|
|
202
|
+
names = registry.list_agents()
|
|
203
|
+
|
|
204
|
+
assert "Writer" in names
|
|
205
|
+
assert "Editor" in names
|
|
206
|
+
|
|
207
|
+
def test_create_team(self):
|
|
208
|
+
registry = AgentRegistry()
|
|
209
|
+
registry.register(WriterAgent())
|
|
210
|
+
registry.register(EditorAgent())
|
|
211
|
+
registry.register(DirectorAgent())
|
|
212
|
+
|
|
213
|
+
team = registry.create_team([AgentRole.WRITER, AgentRole.EDITOR])
|
|
214
|
+
|
|
215
|
+
assert AgentRole.WRITER in team
|
|
216
|
+
assert AgentRole.EDITOR in team
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class TestCreateDefaultAgents:
|
|
220
|
+
"""Test create_default_agents factory."""
|
|
221
|
+
|
|
222
|
+
def test_creates_all_agents(self):
|
|
223
|
+
registry = create_default_agents()
|
|
224
|
+
|
|
225
|
+
assert registry.get("Writer") is not None
|
|
226
|
+
assert registry.get("Researcher") is not None
|
|
227
|
+
assert registry.get("Editor") is not None
|
|
228
|
+
assert registry.get("Director") is not None
|
|
229
|
+
assert registry.get("Designer") is not None
|
|
230
|
+
assert registry.get("Screenwriter") is not None
|
|
231
|
+
assert registry.get("Composer") is not None
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""Tests for Asset Bank."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
import tempfile
|
|
5
|
+
import shutil
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from vibe_aigc.assets import (
|
|
9
|
+
AssetBank,
|
|
10
|
+
Character,
|
|
11
|
+
StyleGuide,
|
|
12
|
+
Artifact,
|
|
13
|
+
create_asset_bank
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TestCharacter:
|
|
18
|
+
"""Test Character class."""
|
|
19
|
+
|
|
20
|
+
def test_create_character(self):
|
|
21
|
+
char = Character(
|
|
22
|
+
id="char-001",
|
|
23
|
+
name="Alice",
|
|
24
|
+
description="A curious adventurer",
|
|
25
|
+
visual_description="Young woman, brown hair, blue eyes",
|
|
26
|
+
personality="Curious, brave, kind"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
assert char.name == "Alice"
|
|
30
|
+
assert char.id == "char-001"
|
|
31
|
+
|
|
32
|
+
def test_to_prompt_context(self):
|
|
33
|
+
char = Character(
|
|
34
|
+
id="char-001",
|
|
35
|
+
name="Bob",
|
|
36
|
+
description="A wise mentor",
|
|
37
|
+
visual_description="Elderly man with white beard",
|
|
38
|
+
color_palette=["#333", "#gold"]
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
context = char.to_prompt_context()
|
|
42
|
+
|
|
43
|
+
assert "Bob" in context
|
|
44
|
+
assert "wise mentor" in context
|
|
45
|
+
assert "white beard" in context
|
|
46
|
+
assert "#333" in context
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class TestStyleGuide:
|
|
50
|
+
"""Test StyleGuide class."""
|
|
51
|
+
|
|
52
|
+
def test_create_style_guide(self):
|
|
53
|
+
style = StyleGuide(
|
|
54
|
+
id="style-001",
|
|
55
|
+
name="Cyberpunk",
|
|
56
|
+
description="Neon-lit dystopian future",
|
|
57
|
+
mood="dark, gritty",
|
|
58
|
+
aesthetic="high-tech low-life",
|
|
59
|
+
color_palette=["#ff00ff", "#00ffff", "#000"]
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
assert style.name == "Cyberpunk"
|
|
63
|
+
assert style.mood == "dark, gritty"
|
|
64
|
+
|
|
65
|
+
def test_to_prompt_context(self):
|
|
66
|
+
style = StyleGuide(
|
|
67
|
+
id="style-001",
|
|
68
|
+
name="Minimalist",
|
|
69
|
+
description="Clean and simple",
|
|
70
|
+
mood="calm",
|
|
71
|
+
aesthetic="modern",
|
|
72
|
+
aspect_ratio="16:9"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
context = style.to_prompt_context()
|
|
76
|
+
|
|
77
|
+
assert "Minimalist" in context
|
|
78
|
+
assert "calm" in context
|
|
79
|
+
assert "16:9" in context
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class TestArtifact:
|
|
83
|
+
"""Test Artifact class."""
|
|
84
|
+
|
|
85
|
+
def test_create_artifact(self):
|
|
86
|
+
artifact = Artifact(
|
|
87
|
+
id="art-001",
|
|
88
|
+
type="image",
|
|
89
|
+
name="Hero Shot",
|
|
90
|
+
description="Main character portrait",
|
|
91
|
+
url="https://example.com/image.png"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
assert artifact.type == "image"
|
|
95
|
+
assert artifact.url is not None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class TestAssetBank:
|
|
99
|
+
"""Test AssetBank class."""
|
|
100
|
+
|
|
101
|
+
@pytest.fixture
|
|
102
|
+
def temp_dir(self):
|
|
103
|
+
"""Create temporary directory for tests."""
|
|
104
|
+
dir_path = tempfile.mkdtemp()
|
|
105
|
+
yield dir_path
|
|
106
|
+
shutil.rmtree(dir_path)
|
|
107
|
+
|
|
108
|
+
def test_create_asset_bank(self, temp_dir):
|
|
109
|
+
bank = AssetBank(storage_dir=temp_dir)
|
|
110
|
+
|
|
111
|
+
assert bank.storage_dir.exists()
|
|
112
|
+
|
|
113
|
+
def test_add_and_get_character(self, temp_dir):
|
|
114
|
+
bank = AssetBank(storage_dir=temp_dir)
|
|
115
|
+
|
|
116
|
+
char = Character(
|
|
117
|
+
id="char-001",
|
|
118
|
+
name="Test Character",
|
|
119
|
+
description="A test"
|
|
120
|
+
)
|
|
121
|
+
bank.add_character(char)
|
|
122
|
+
|
|
123
|
+
retrieved = bank.get_character("char-001")
|
|
124
|
+
|
|
125
|
+
assert retrieved is not None
|
|
126
|
+
assert retrieved.name == "Test Character"
|
|
127
|
+
|
|
128
|
+
def test_find_character_by_name(self, temp_dir):
|
|
129
|
+
bank = AssetBank(storage_dir=temp_dir)
|
|
130
|
+
|
|
131
|
+
char = Character(id="c1", name="Alice", description="Test")
|
|
132
|
+
bank.add_character(char)
|
|
133
|
+
|
|
134
|
+
found = bank.find_character("alice") # Case insensitive
|
|
135
|
+
|
|
136
|
+
assert found is not None
|
|
137
|
+
assert found.id == "c1"
|
|
138
|
+
|
|
139
|
+
def test_list_characters(self, temp_dir):
|
|
140
|
+
bank = AssetBank(storage_dir=temp_dir)
|
|
141
|
+
|
|
142
|
+
bank.add_character(Character(id="c1", name="A", description=""))
|
|
143
|
+
bank.add_character(Character(id="c2", name="B", description=""))
|
|
144
|
+
|
|
145
|
+
chars = bank.list_characters()
|
|
146
|
+
|
|
147
|
+
assert len(chars) == 2
|
|
148
|
+
|
|
149
|
+
def test_update_character(self, temp_dir):
|
|
150
|
+
bank = AssetBank(storage_dir=temp_dir)
|
|
151
|
+
|
|
152
|
+
char = Character(id="c1", name="Old Name", description="")
|
|
153
|
+
bank.add_character(char)
|
|
154
|
+
|
|
155
|
+
bank.update_character("c1", {"name": "New Name"})
|
|
156
|
+
|
|
157
|
+
updated = bank.get_character("c1")
|
|
158
|
+
assert updated.name == "New Name"
|
|
159
|
+
|
|
160
|
+
def test_add_character_reference(self, temp_dir):
|
|
161
|
+
bank = AssetBank(storage_dir=temp_dir)
|
|
162
|
+
|
|
163
|
+
char = Character(id="c1", name="Test", description="")
|
|
164
|
+
bank.add_character(char)
|
|
165
|
+
|
|
166
|
+
bank.add_character_reference("c1", "https://example.com/ref.png")
|
|
167
|
+
|
|
168
|
+
updated = bank.get_character("c1")
|
|
169
|
+
assert len(updated.reference_images) == 1
|
|
170
|
+
|
|
171
|
+
def test_add_and_get_style_guide(self, temp_dir):
|
|
172
|
+
bank = AssetBank(storage_dir=temp_dir)
|
|
173
|
+
|
|
174
|
+
style = StyleGuide(id="s1", name="Test Style", description="")
|
|
175
|
+
bank.add_style_guide(style)
|
|
176
|
+
|
|
177
|
+
retrieved = bank.get_style_guide("s1")
|
|
178
|
+
|
|
179
|
+
assert retrieved is not None
|
|
180
|
+
assert retrieved.name == "Test Style"
|
|
181
|
+
|
|
182
|
+
def test_store_and_get_artifact(self, temp_dir):
|
|
183
|
+
bank = AssetBank(storage_dir=temp_dir)
|
|
184
|
+
|
|
185
|
+
artifact = Artifact(
|
|
186
|
+
id="a1",
|
|
187
|
+
type="image",
|
|
188
|
+
name="Test Image",
|
|
189
|
+
description="",
|
|
190
|
+
url="https://example.com/img.png"
|
|
191
|
+
)
|
|
192
|
+
bank.store_artifact(artifact)
|
|
193
|
+
|
|
194
|
+
retrieved = bank.get_artifact("a1")
|
|
195
|
+
|
|
196
|
+
assert retrieved is not None
|
|
197
|
+
assert retrieved.type == "image"
|
|
198
|
+
|
|
199
|
+
def test_find_artifacts(self, temp_dir):
|
|
200
|
+
bank = AssetBank(storage_dir=temp_dir)
|
|
201
|
+
|
|
202
|
+
bank.store_artifact(Artifact(id="a1", type="image", name="Img1", description="", related_character_id="c1"))
|
|
203
|
+
bank.store_artifact(Artifact(id="a2", type="image", name="Img2", description="", related_character_id="c1"))
|
|
204
|
+
bank.store_artifact(Artifact(id="a3", type="audio", name="Audio1", description=""))
|
|
205
|
+
|
|
206
|
+
images = bank.find_artifacts(type="image")
|
|
207
|
+
char_images = bank.find_artifacts(character_id="c1")
|
|
208
|
+
|
|
209
|
+
assert len(images) == 2
|
|
210
|
+
assert len(char_images) == 2
|
|
211
|
+
|
|
212
|
+
def test_get_project_context(self, temp_dir):
|
|
213
|
+
bank = AssetBank(storage_dir=temp_dir)
|
|
214
|
+
|
|
215
|
+
bank.add_character(Character(id="c1", name="Hero", description="Main character"))
|
|
216
|
+
bank.add_style_guide(StyleGuide(id="s1", name="Dark", description="Dark aesthetic"))
|
|
217
|
+
|
|
218
|
+
context = bank.get_project_context()
|
|
219
|
+
|
|
220
|
+
assert "Hero" in context
|
|
221
|
+
assert "Dark" in context
|
|
222
|
+
|
|
223
|
+
def test_persistence(self, temp_dir):
|
|
224
|
+
# Create and populate bank
|
|
225
|
+
bank1 = AssetBank(storage_dir=temp_dir)
|
|
226
|
+
bank1.add_character(Character(id="c1", name="Persistent", description=""))
|
|
227
|
+
|
|
228
|
+
# Create new bank from same directory
|
|
229
|
+
bank2 = AssetBank(storage_dir=temp_dir)
|
|
230
|
+
|
|
231
|
+
# Should have loaded the character
|
|
232
|
+
assert bank2.get_character("c1") is not None
|
|
233
|
+
assert bank2.get_character("c1").name == "Persistent"
|
|
234
|
+
|
|
235
|
+
def test_clear(self, temp_dir):
|
|
236
|
+
bank = AssetBank(storage_dir=temp_dir)
|
|
237
|
+
|
|
238
|
+
bank.add_character(Character(id="c1", name="Test", description=""))
|
|
239
|
+
bank.clear()
|
|
240
|
+
|
|
241
|
+
assert len(bank.list_characters()) == 0
|
|
242
|
+
|
|
243
|
+
def test_create_character_convenience(self, temp_dir):
|
|
244
|
+
bank = AssetBank(storage_dir=temp_dir)
|
|
245
|
+
|
|
246
|
+
char = bank.create_character(
|
|
247
|
+
name="Quick Character",
|
|
248
|
+
description="Created quickly",
|
|
249
|
+
visual_description="Generic look"
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
assert char.id is not None
|
|
253
|
+
assert char.name == "Quick Character"
|
|
254
|
+
assert bank.get_character(char.id) is not None
|
|
255
|
+
|
|
256
|
+
def test_create_style_guide_convenience(self, temp_dir):
|
|
257
|
+
bank = AssetBank(storage_dir=temp_dir)
|
|
258
|
+
|
|
259
|
+
style = bank.create_style_guide(
|
|
260
|
+
name="Quick Style",
|
|
261
|
+
description="Created quickly",
|
|
262
|
+
mood="happy",
|
|
263
|
+
color_palette=["#fff", "#000"]
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
assert style.id is not None
|
|
267
|
+
assert style.mood == "happy"
|
|
268
|
+
|
|
269
|
+
def test_create_artifact_convenience(self, temp_dir):
|
|
270
|
+
bank = AssetBank(storage_dir=temp_dir)
|
|
271
|
+
|
|
272
|
+
artifact = bank.create_artifact(
|
|
273
|
+
type="image",
|
|
274
|
+
name="Generated Image",
|
|
275
|
+
description="AI generated",
|
|
276
|
+
url="https://example.com/gen.png",
|
|
277
|
+
prompt_used="A beautiful sunset"
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
assert artifact.id is not None
|
|
281
|
+
assert artifact.prompt_used == "A beautiful sunset"
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class TestCreateAssetBank:
|
|
285
|
+
"""Test create_asset_bank factory."""
|
|
286
|
+
|
|
287
|
+
def test_creates_bank(self):
|
|
288
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
289
|
+
bank = create_asset_bank(storage_dir=temp_dir)
|
|
290
|
+
|
|
291
|
+
assert isinstance(bank, AssetBank)
|