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.
Files changed (45) hide show
  1. {vibe_aigc-0.1.1/vibe_aigc.egg-info → vibe_aigc-0.2.0}/PKG-INFO +27 -1
  2. {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/README.md +26 -0
  3. {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/pyproject.toml +1 -1
  4. {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/tests/test_adaptive_replanning.py +1 -1
  5. vibe_aigc-0.2.0/tests/test_agents.py +231 -0
  6. vibe_aigc-0.2.0/tests/test_assets.py +291 -0
  7. vibe_aigc-0.2.0/tests/test_knowledge_base.py +161 -0
  8. vibe_aigc-0.2.0/tests/test_tools.py +274 -0
  9. vibe_aigc-0.2.0/vibe_aigc/__init__.py +101 -0
  10. vibe_aigc-0.2.0/vibe_aigc/agents.py +581 -0
  11. vibe_aigc-0.2.0/vibe_aigc/assets.py +386 -0
  12. {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/vibe_aigc/executor.py +138 -8
  13. vibe_aigc-0.2.0/vibe_aigc/knowledge.py +398 -0
  14. {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/vibe_aigc/llm.py +53 -11
  15. {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/vibe_aigc/planner.py +38 -5
  16. vibe_aigc-0.2.0/vibe_aigc/tools.py +508 -0
  17. vibe_aigc-0.2.0/vibe_aigc/tools_multimodal.py +613 -0
  18. {vibe_aigc-0.1.1 → vibe_aigc-0.2.0/vibe_aigc.egg-info}/PKG-INFO +27 -1
  19. {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/vibe_aigc.egg-info/SOURCES.txt +9 -0
  20. vibe_aigc-0.1.1/vibe_aigc/__init__.py +0 -13
  21. {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/LICENSE +0 -0
  22. {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/setup.cfg +0 -0
  23. {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/tests/test_auto_checkpoint.py +0 -0
  24. {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/tests/test_automatic_checkpoints.py +0 -0
  25. {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/tests/test_checkpoint_serialization.py +0 -0
  26. {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/tests/test_error_handling.py +0 -0
  27. {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/tests/test_executor.py +0 -0
  28. {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/tests/test_feedback_system.py +0 -0
  29. {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/tests/test_integration.py +0 -0
  30. {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/tests/test_metaplanner_resume.py +0 -0
  31. {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/tests/test_metaplanner_visualization.py +0 -0
  32. {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/tests/test_models.py +0 -0
  33. {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/tests/test_parallel_execution.py +0 -0
  34. {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/tests/test_planner.py +0 -0
  35. {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/tests/test_progress_callbacks.py +0 -0
  36. {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/tests/test_visualization.py +0 -0
  37. {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/tests/test_workflow_resume.py +0 -0
  38. {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/vibe_aigc/cli.py +0 -0
  39. {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/vibe_aigc/models.py +0 -0
  40. {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/vibe_aigc/persistence.py +0 -0
  41. {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/vibe_aigc/visualization.py +0 -0
  42. {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/vibe_aigc.egg-info/dependency_links.txt +0 -0
  43. {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/vibe_aigc.egg-info/entry_points.txt +0 -0
  44. {vibe_aigc-0.1.1 → vibe_aigc-0.2.0}/vibe_aigc.egg-info/requires.txt +0 -0
  45. {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.1.1
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.1.1"
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)