aniruddh1907 commited on
Commit
6efe9ab
ยท
0 Parent(s):

[feat] add image gen and graph gen

Browse files
.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ### Python template
2
+ # Byte-compiled / optimized / DLL files
3
+ __pycache__/
4
+ *.py[cod]
5
+ *$py.class
6
+
7
+ # C extensions
8
+ *.so
9
+
10
+ # Distribution / packaging
11
+ .Python
12
+ build/
13
+ develop-eggs/
14
+ dist/
15
+ downloads/
16
+ eggs/
17
+ .eggs/
18
+ lib/
19
+ lib64/
20
+ parts/
21
+ sdist/
22
+ var/
23
+ wheels/
24
+ share/python-wheels/
25
+ *.egg-info/
26
+ .installed.cfg
27
+ *.egg
28
+ MANIFEST
29
+
30
+ # PyInstaller
31
+ # Usually these files are written by a python script from a template
32
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
33
+ *.manifest
34
+ *.spec
35
+
36
+ # Installer logs
37
+ pip-log.txt
38
+ pip-delete-this-directory.txt
39
+
40
+ # Unit test / coverage reports
41
+ htmlcov/
42
+ .tox/
43
+ .nox/
44
+ .coverage
45
+ .coverage.*
46
+ .cache
47
+ nosetests.xml
48
+ coverage.xml
49
+ *.cover
50
+ *.py,cover
51
+ .hypothesis/
52
+ .pytest_cache/
53
+ cover/
54
+
55
+ # Translations
56
+ *.mo
57
+ *.pot
58
+
59
+ # Django stuff:
60
+ *.log
61
+ local_settings.py
62
+ db.sqlite3
63
+ db.sqlite3-journal
64
+
65
+ # Flask stuff:
66
+ instance/
67
+ .webassets-cache
68
+
69
+ # Scrapy stuff:
70
+ .scrapy
71
+
72
+ # Sphinx documentation
73
+ docs/_build/
74
+
75
+ # PyBuilder
76
+ .pybuilder/
77
+ target/
78
+
79
+ # Jupyter Notebook
80
+ .ipynb_checkpoints
81
+
82
+ # IPython
83
+ profile_default/
84
+ ipython_config.py
85
+
86
+ # pyenv
87
+ # For a library or package, you might want to ignore these files since the code is
88
+ # intended to run in multiple environments; otherwise, check them in:
89
+ # .python-version
90
+
91
+ # pipenv
92
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
93
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
94
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
95
+ # install all needed dependencies.
96
+ #Pipfile.lock
97
+
98
+ # poetry
99
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
100
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
101
+ # commonly ignored for libraries.
102
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
103
+ #poetry.lock
104
+
105
+ # pdm
106
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
107
+ #pdm.lock
108
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
109
+ # in version control.
110
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
111
+ .pdm.toml
112
+ .pdm-python
113
+ .pdm-build/
114
+
115
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
116
+ __pypackages__/
117
+
118
+ # Celery stuff
119
+ celerybeat-schedule
120
+ celerybeat.pid
121
+
122
+ # SageMath parsed files
123
+ *.sage.py
124
+
125
+ # Environments
126
+ .env
127
+ .venv
128
+ env/
129
+ venv/
130
+ ENV/
131
+ env.bak/
132
+ venv.bak/
133
+
134
+ # Spyder project settings
135
+ .spyderproject
136
+ .spyproject
137
+
138
+ # Rope project settings
139
+ .ropeproject
140
+
141
+ # mkdocs documentation
142
+ /site
143
+
144
+ # mypy
145
+ .mypy_cache/
146
+ .dmypy.json
147
+ dmypy.json
148
+
149
+ # Pyre type checker
150
+ .pyre/
151
+
152
+ # pytype static type analyzer
153
+ .pytype/
154
+
155
+ # Cython debug symbols
156
+ cython_debug/
157
+
158
+ # PyCharm
159
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
160
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
161
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
162
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
163
+ #.idea/
164
+
.idea/.gitignore ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ # Default ignored files
2
+ /shelf/
3
+ /workspace.xml
4
+ # Editor-based HTTP Client requests
5
+ /httpRequests/
6
+ # Datasource local storage ignored files
7
+ /dataSources/
8
+ /dataSources.local.xml
.idea/MajorProject.iml ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="PYTHON_MODULE" version="4">
3
+ <component name="NewModuleRootManager">
4
+ <content url="file://$MODULE_DIR$" />
5
+ <orderEntry type="inheritedJdk" />
6
+ <orderEntry type="sourceFolder" forTests="false" />
7
+ </component>
8
+ </module>
.idea/inspectionProfiles/Project_Default.xml ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <component name="InspectionProjectProfileManager">
2
+ <profile version="1.0">
3
+ <option name="myName" value="Project Default" />
4
+ <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
5
+ <inspection_tool class="IncorrectHttpHeaderInspection" enabled="true" level="WARNING" enabled_by_default="true">
6
+ <option name="customHeaders">
7
+ <set>
8
+ <option value="X-API-Key" />
9
+ </set>
10
+ </option>
11
+ </inspection_tool>
12
+ <inspection_tool class="PyCompatibilityInspection" enabled="true" level="WARNING" enabled_by_default="true">
13
+ <option name="ourVersions">
14
+ <value>
15
+ <list size="2">
16
+ <item index="0" class="java.lang.String" itemvalue="3.13" />
17
+ <item index="1" class="java.lang.String" itemvalue="3.5" />
18
+ </list>
19
+ </value>
20
+ </option>
21
+ </inspection_tool>
22
+ <inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
23
+ <option name="ignoredPackages">
24
+ <value>
25
+ <list size="10">
26
+ <item index="0" class="java.lang.String" itemvalue="shiboken6" />
27
+ <item index="1" class="java.lang.String" itemvalue="asyncio" />
28
+ <item index="2" class="java.lang.String" itemvalue="gunicorn" />
29
+ <item index="3" class="java.lang.String" itemvalue="asyncpg" />
30
+ <item index="4" class="java.lang.String" itemvalue="SQLAlchemy" />
31
+ <item index="5" class="java.lang.String" itemvalue="python-dotenv" />
32
+ <item index="6" class="java.lang.String" itemvalue="fastapi" />
33
+ <item index="7" class="java.lang.String" itemvalue="pydantic" />
34
+ <item index="8" class="java.lang.String" itemvalue="streamlit" />
35
+ <item index="9" class="java.lang.String" itemvalue="requests" />
36
+ </list>
37
+ </value>
38
+ </option>
39
+ </inspection_tool>
40
+ <inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
41
+ <option name="ignoredErrors">
42
+ <list>
43
+ <option value="N802" />
44
+ </list>
45
+ </option>
46
+ </inspection_tool>
47
+ </profile>
48
+ </component>
.idea/inspectionProfiles/profiles_settings.xml ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ <component name="InspectionProjectProfileManager">
2
+ <settings>
3
+ <option name="USE_PROJECT_PROFILE" value="false" />
4
+ <version value="1.0" />
5
+ </settings>
6
+ </component>
.idea/misc.xml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="Black">
4
+ <option name="sdkName" value="env" />
5
+ </component>
6
+ <component name="ProjectRootManager" version="2" project-jdk-name="env" project-jdk-type="Python SDK" />
7
+ </project>
.idea/modules.xml ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/MajorProject.iml" filepath="$PROJECT_DIR$/.idea/MajorProject.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
.idea/vcs.xml ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings" defaultProject="true" />
4
+ </project>
app.py ADDED
@@ -0,0 +1,274 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import base64
2
+ import json
3
+ import os
4
+ import random
5
+ import re
6
+ import shutil
7
+ import sys
8
+ import tempfile
9
+ import uuid
10
+ from datetime import datetime
11
+ from io import BytesIO
12
+ from pathlib import Path
13
+
14
+ import gradio as gr
15
+ from PIL import Image
16
+ from dotenv import load_dotenv
17
+ from graphviz import Digraph
18
+ from huggingface_hub import InferenceClient
19
+ from together import Together
20
+
21
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
22
+ # ENV / API
23
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
24
+ load_dotenv()
25
+ HF_TOKEN = os.getenv("HF_TOKEN") # <-- add your HF token to .env
26
+ TOGETHER_TOKEN = os.getenv("TOGETHER_API_KEY", "")
27
+ together_client = Together(api_key=TOGETHER_TOKEN)
28
+ image_client = InferenceClient(token=HF_TOKEN) # default model set later
29
+
30
+ # Optional Graphviz path helper (Windows ONLY (RIP Gotham))
31
+ if shutil.which("dot") is None:
32
+ gv_path = r"C:\Program Files\Graphviz\bin"
33
+ if os.path.exists(gv_path):
34
+ os.environ["PATH"] = gv_path + os.pathsep + os.environ["PATH"]
35
+ else:
36
+ sys.exit("Graphviz not found. Please install Graphviz or remove the check.")
37
+
38
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
39
+ # LLM templates
40
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
41
+ LLAMA_JSON_PROMPT = """
42
+ Extract every character and any explicit relationship between them.
43
+ Return pure JSON ONLY in this schema:
44
+ {
45
+ "characters": ["Alice", "Bob"],
46
+ "relations": [
47
+ {"from":"Alice","to":"Bob","type":"friend"}
48
+ ]
49
+ }
50
+ TEXT:
51
+ \"\"\"%s\"\"\"
52
+ """
53
+
54
+ IMAGE_PROMPT_TEMPLATE = """
55
+ Based on the following story, craft ONE vivid scene description suitable as a prompt for a textโ€‘toโ€‘image AI.
56
+ Do NOT restate the entire storyโ€”write only the visual description.
57
+ Include setting, mood, key characters, and distinctive details.
58
+ Return a single sentence, nothing else.
59
+
60
+ Story:
61
+ \"\"\"%s\"\"\"
62
+ """
63
+
64
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
65
+ # Entity extraction
66
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
67
+ def extract_entities(text: str):
68
+ try:
69
+ prompt = LLAMA_JSON_PROMPT % text
70
+ resp = together_client.chat.completions.create(
71
+ model="meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8",
72
+ messages=[{"role": "user", "content": prompt}],
73
+ max_tokens=1024,
74
+ )
75
+ raw = resp.choices[0].message.content.strip()
76
+ m = re.search(r"\{[\s\S]*\}", raw)
77
+ if not m:
78
+ return None, f"โš ๏ธย No JSON block found.\n\n{raw}"
79
+ data = json.loads(m.group(0))
80
+ return data, None
81
+ except Exception as e:
82
+ return None, f"โš ๏ธย extractor error: {e}"
83
+
84
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
85
+ # Build visual prompt
86
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
87
+ def generate_image_prompt(story_text: str):
88
+ try:
89
+ prompt_msg = IMAGE_PROMPT_TEMPLATE % story_text
90
+ resp = together_client.chat.completions.create(
91
+ model="meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8",
92
+ messages=[{"role": "user", "content": prompt_msg}],
93
+ max_tokens=120,
94
+ )
95
+ return resp.choices[0].message.content.strip()
96
+ except Exception as e:
97
+ return e
98
+
99
+
100
+
101
+ def generate_images_with_together(story, style, quality, count=1):
102
+ base_prompt = generate_image_prompt(story)
103
+ images = []
104
+
105
+ for i in range(count):
106
+ full_prompt = f"{style} style, cinematic lighting, quality {quality}, {base_prompt} [Scene {i + 1}]"
107
+ seed = random.randint(1, 10_000_000)
108
+
109
+ response = together_client.images.generate(
110
+ prompt=full_prompt,
111
+ model="black-forest-labs/FLUX.1-schnell-Free",
112
+ seed=seed,
113
+ width=768,
114
+ height=512,
115
+ steps=4
116
+ )
117
+
118
+ if response.data and response.data[0].b64_json:
119
+ image_b64 = response.data[0].b64_json
120
+ image_data = base64.b64decode(image_b64)
121
+ image = Image.open(BytesIO(image_data))
122
+ images.append(image)
123
+ else:
124
+ print("โš ๏ธ No image returned for scene", i + 1)
125
+
126
+ return images
127
+
128
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
129
+ # Graph โ†’ PNG (Graphviz)
130
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
131
+ def build_graph_png(data: dict) -> str:
132
+ dot = Digraph(format="png")
133
+ dot.attr(rankdir="LR", bgcolor="white", fontsize="11")
134
+ for c in data["characters"]:
135
+ dot.node(c, shape="ellipse", style="filled", fillcolor="#8ecae6")
136
+ for r in data["relations"]:
137
+ dot.edge(r["from"], r["to"], label=r["type"], fontsize="10")
138
+
139
+ tmpdir = Path(tempfile.mkdtemp())
140
+ path = tmpdir / f"graph_{uuid.uuid4().hex}.png"
141
+ dot.render(path.stem, directory=tmpdir, cleanup=True)
142
+ return str(path)
143
+
144
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
145
+ # Core generation
146
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
147
+ def generate_assets(prompt, style, quality, num_images, state):
148
+ data, err = extract_entities(prompt)
149
+ if not data:
150
+ return [], None, err or "No data.", state
151
+
152
+ graph_path = build_graph_png(data)
153
+
154
+ images = []
155
+ if num_images > 0:
156
+ try:
157
+ images = generate_images_with_together(prompt, style, quality, int(num_images))
158
+ except Exception as e:
159
+ print("โš ๏ธ Together.ai image generation failed:", e)
160
+ images = []
161
+
162
+ status = "โœ… All assets generated." if images else "โœ… Graph generated (no images)."
163
+ return images, graph_path, status, data
164
+
165
+ # Helper to rebuild graph after manual edits
166
+ def _regen_graph(state): return gr.update(value=build_graph_png(state))
167
+
168
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
169
+ # Manual tweak callbacks
170
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
171
+ def add_character(name, state):
172
+ if not name:
173
+ return gr.update(), "Enter a character name.", state
174
+ if name in state["characters"]:
175
+ return gr.update(), f"{name} already exists.", state
176
+ state["characters"].append(name)
177
+ return _regen_graph(state), "โœ…ย Character added.", state
178
+
179
+ def add_relation(frm, to, typ, state):
180
+ if frm not in state["characters"] or to not in state["characters"]:
181
+ return gr.update(), "Both characters must exist first.", state
182
+ state["relations"].append({"from": frm, "to": to, "type": typ or "relation"})
183
+ return _regen_graph(state), "โœ…ย Relation added.", state
184
+
185
+ def delete_character(name, state):
186
+ if name not in state["characters"]:
187
+ return gr.update(), "Character not found.", state
188
+ state["characters"].remove(name)
189
+ state["relations"] = [r for r in state["relations"] if r["from"] != name and r["to"] != name]
190
+ return _regen_graph(state), f"๐Ÿšฎย {name} deleted.", state
191
+
192
+ # Save / Load
193
+ def save_json(state):
194
+ fp = Path(tempfile.gettempdir()) / f"story_{datetime.now().isoformat()}.json"
195
+ fp.write_text(json.dumps(state, indent=2))
196
+ return str(fp)
197
+
198
+ def load_json(file_obj, state):
199
+ if not file_obj or not Path(file_obj).exists():
200
+ return gr.update(), "No file uploaded.", state
201
+ try:
202
+ data = json.loads(Path(file_obj).read_text())
203
+ assert "characters" in data and "relations" in data
204
+ return _regen_graph(data), "โœ…ย File loaded.", data
205
+ except Exception as e:
206
+ return gr.update(), f"Load error: {e}", state
207
+
208
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
209
+ # UI (same tabs you designed)
210
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
211
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="cyan")) as demo:
212
+ gr.Markdown("## โœจ EpicFrame โ€“ Narrative Workbench")
213
+
214
+ state = gr.State({"characters": [], "relations": []})
215
+
216
+ # Input tab
217
+ with gr.Tab("Input"):
218
+ text_input = gr.Textbox(label="Story prompt", lines=6)
219
+ style_dropdown = gr.Dropdown(["Realistic", "Anime", "Sketch"], value="Realistic", label="Style")
220
+ quality_slider = gr.Slider(1, 10, value=7, step=1, label="Image Quality")
221
+ num_images_sl = gr.Slider(0, 4, value=0, step=1, label="Images to generate (0 = skip)")
222
+ generate_btn = gr.Button("โ–ถ๏ธ Generate Assets")
223
+ status_box = gr.Textbox(label="Status", lines=2)
224
+
225
+ # Images tab
226
+ with gr.Tab("Images"):
227
+ gallery = gr.Gallery(label="๐Ÿ–ผ๏ธ Images", columns=4)
228
+
229
+ # Graph/Edit tab
230
+ with gr.Tab("Graph / Edit"):
231
+ graph_img = gr.Image(label="๐Ÿ“Œ Character Map", interactive=False, height=500)
232
+ with gr.Row():
233
+ add_char_name = gr.Textbox(label="Add Character โ†’ Name")
234
+ add_char_btn = gr.Button("Add")
235
+ with gr.Row():
236
+ rel_from = gr.Textbox(label="Relation From")
237
+ rel_to = gr.Textbox(label="To")
238
+ rel_type = gr.Textbox(label="Type")
239
+ add_rel_btn = gr.Button("Add Relation")
240
+ with gr.Row():
241
+ del_char_name = gr.Textbox(label="Delete Character โ†’ Name")
242
+ del_char_btn = gr.Button("Delete")
243
+ tweak_msg = gr.Textbox(label="โžฐ Status", max_lines=2)
244
+
245
+ # Save/Load tab
246
+ with gr.Tab("Save / Load"):
247
+ save_btn = gr.Button("๐Ÿ’พ Download JSON")
248
+ load_file = gr.File(label="Load JSON")
249
+ load_btn = gr.Button("โคต๏ธ Load into workspace")
250
+ save_msg = gr.Textbox(label="Status", max_lines=2)
251
+
252
+ # callbacks
253
+ generate_btn.click(
254
+ generate_assets,
255
+ inputs=[text_input, style_dropdown, quality_slider, num_images_sl, state],
256
+ outputs=[gallery, graph_img, status_box, state]
257
+ )
258
+
259
+ add_char_btn.click(add_character,
260
+ inputs=[add_char_name, state],
261
+ outputs=[graph_img, tweak_msg, state])
262
+ add_rel_btn.click(add_relation,
263
+ inputs=[rel_from, rel_to, rel_type, state],
264
+ outputs=[graph_img, tweak_msg, state])
265
+ del_char_btn.click(delete_character,
266
+ inputs=[del_char_name, state],
267
+ outputs=[graph_img, tweak_msg, state])
268
+
269
+ save_btn.click(save_json, inputs=state, outputs=save_btn, api_name="download") \
270
+ .then(lambda p: "โœ… JSON ready.", outputs=save_msg)
271
+ load_btn.click(load_json, inputs=[load_file, state],
272
+ outputs=[graph_img, save_msg, state])
273
+
274
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ gradio
2
+ graphviz
3
+ python-dotenv
4
+ together