Alon Albalak commited on
Commit
71a764a
·
1 Parent(s): 391f70f

initial commmit

Browse files
Files changed (45) hide show
  1. .gitattributes +1 -0
  2. .gitignore +207 -0
  3. data/prompts.jsonl +7 -0
  4. data/results.jsonl +3 -0
  5. main.py +119 -0
  6. pyproject.toml +17 -0
  7. requirements.txt +313 -0
  8. src/__init__.py +25 -0
  9. src/config/__init__.py +23 -0
  10. src/config/settings.py +76 -0
  11. src/models/__init__.py +7 -0
  12. src/models/data_manager.py +101 -0
  13. src/models/llm_manager.py +75 -0
  14. src/models/similarity_calculator.py +27 -0
  15. src/scoring/__init__.py +7 -0
  16. src/scoring/achievements.py +44 -0
  17. src/scoring/scorer.py +220 -0
  18. src/scoring/statistics.py +143 -0
  19. src/session/__init__.py +5 -0
  20. src/session/session_manager.py +316 -0
  21. src/ui/__init__.py +7 -0
  22. src/ui/interface_builder.py +550 -0
  23. src/ui/page_handlers.py +501 -0
  24. src/ui/template_renderer.py +20 -0
  25. static/css/base.css +162 -0
  26. static/css/components.css +214 -0
  27. static/css/gallery.css +524 -0
  28. static/css/game.css +17 -0
  29. static/css/landing.css +27 -0
  30. static/css/scores.css +132 -0
  31. templates/components/achievement-badge.html +1 -0
  32. templates/components/attempt-item.html +7 -0
  33. templates/components/inspire-me-empty.html +4 -0
  34. templates/components/inspire-me-example.html +6 -0
  35. templates/components/inspire-me-footer.html +5 -0
  36. templates/components/inspire-me-header.html +1 -0
  37. templates/components/recent-attempts.html +4 -0
  38. templates/components/session-achievements.html +3 -0
  39. templates/components/session-enhanced-attempt.html +14 -0
  40. templates/components/session-history.html +6 -0
  41. templates/components/session-main-stats.html +31 -0
  42. templates/components/session-page-empty.html +11 -0
  43. templates/components/session-progress-empty.html +5 -0
  44. templates/components/session-progress-main.html +28 -0
  45. uv.lock +0 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* 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
 
 
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
36
+ data/results.jsonl filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+ #poetry.toml
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
+ #pdm.lock
116
+ #pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # pixi
121
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
+ #pixi.lock
123
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
+ # in the .venv directory. It is recommended not to include this directory in version control.
125
+ .pixi
126
+
127
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
+ __pypackages__/
129
+
130
+ # Celery stuff
131
+ celerybeat-schedule
132
+ celerybeat.pid
133
+
134
+ # SageMath parsed files
135
+ *.sage.py
136
+
137
+ # Environments
138
+ .env
139
+ .envrc
140
+ .venv
141
+ env/
142
+ venv/
143
+ ENV/
144
+ env.bak/
145
+ venv.bak/
146
+
147
+ # Spyder project settings
148
+ .spyderproject
149
+ .spyproject
150
+
151
+ # Rope project settings
152
+ .ropeproject
153
+
154
+ # mkdocs documentation
155
+ /site
156
+
157
+ # mypy
158
+ .mypy_cache/
159
+ .dmypy.json
160
+ dmypy.json
161
+
162
+ # Pyre type checker
163
+ .pyre/
164
+
165
+ # pytype static type analyzer
166
+ .pytype/
167
+
168
+ # Cython debug symbols
169
+ cython_debug/
170
+
171
+ # PyCharm
172
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
173
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
174
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
175
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
176
+ #.idea/
177
+
178
+ # Abstra
179
+ # Abstra is an AI-powered process automation framework.
180
+ # Ignore directories containing user credentials, local state, and settings.
181
+ # Learn more at https://abstra.io/docs
182
+ .abstra/
183
+
184
+ # Visual Studio Code
185
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
186
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
187
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
188
+ # you could uncomment the following to ignore the entire vscode folder
189
+ # .vscode/
190
+
191
+ # Ruff stuff:
192
+ .ruff_cache/
193
+
194
+ # PyPI configuration file
195
+ .pypirc
196
+
197
+ # Cursor
198
+ # Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
199
+ # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
200
+ # refer to https://docs.cursor.com/context/ignore-files
201
+ .cursorignore
202
+ .cursorindexingignore
203
+
204
+ # Marimo
205
+ marimo/_static/
206
+ marimo/_lsp/
207
+ __marimo__/
data/prompts.jsonl ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {"prompt": "Write a poem on any topic of your choosing.", "model": "meta-llama/Llama-3.2-1B-Instruct", "llm_full_response_original": "\"Moonlit Dreams\"\n\nThe night's soft whisper, a gentle breeze,\nThat stirs the trees, and rustles leaves,\nA midnight sky, with stars up high,\nA celestial tapestry, a twinkling sigh.\n\nThe moon, a glowing crescent shape,\nCasts shadows on the ground,\nA silver glow, that lights the way,\nFor dreams to unfold, in a silent sway.\n\nIn the moon's pale light, I stand and roam,\nA midnight wanderer, in a quiet home,\nWhere shadows dance, and dreams take flight,\nAnd the world is full of endless night.\n\nThe wind whispers secrets, in my ear,\nOf distant lands, and love that's clear,\nOf hearts that beat, with passion's fire,\nAnd the moon's soft light, that never tires.\n\nThe night's dark veil, that lifts away,\nReveals a world, in all its gray,\nYet still I climb, to the higher ground,\nWhere stars and dreams, are my sole command.\n\nIn the moon's pale light, I find my way,\nThrough labyrinths, of a busy day,\nWhere thoughts and fears, and doubts arise,\nI face them head-on, with a steady surprise.\n\nThe moon's soft light, that shines so bright,\nGuides me through, the darkest night,\n Illuminating, the path that's yet unknown,\nAnd in its radiance, my spirit is shown.\n\nAnd so I stand, under the starry sky,\nA tiny part, of the celestial sigh,\nFeeling connected, to the universe's heart,\nAnd the moon's soft light, that sets me apart.", "llm_partial_response": "\"Moonlit Dreams\"\n\nThe night's soft whisper, a gentle breeze,\nThat stirs the trees, and rustles leaves,\nA midnight sky, with stars up high,\nA celestial tapestry, a twinkling sigh.\n\nThe moon, a glowing crescent shape,\nCasts shadows on the ground,\nA silver glow, that lights the way,\nFor dreams to unfold, in a silent sway.\n\n"}
2
+ {"prompt": "Write a poem on any topic of your choosing.", "model": "meta-llama/Llama-3.2-1B-Instruct", "llm_full_response_original": "\"Moonlit Dreams\"\n\nThe night's soft whisper, a gentle breeze,\nThat stirs the trees, and rustles leaves,\nA midnight sky, with stars up high,\nA celestial tapestry, a twinkling sigh.\n\nThe moon, a glowing crescent shape,\nCasts shadows on the ground,\nA silver glow, that lights the way,\nFor dreams to unfold, in a silent sway.\n\nIn the moon's pale light, I stand and roam,\nA midnight wanderer, in a quiet home,\nWhere shadows dance, and dreams take flight,\nAnd the world is full of endless night.\n\nThe wind whispers secrets, in my ear,\nOf distant lands, and love that's clear,\nOf hearts that beat, with passion's fire,\nAnd the moon's soft light, that never tires.\n\nThe night's dark veil, that lifts away,\nReveals a world, in all its gray,\nYet still I climb, to the higher ground,\nWhere stars and dreams, are my sole command.\n\nIn the moon's pale light, I find my way,\nThrough labyrinths, of a busy day,\nWhere thoughts and fears, and doubts arise,\nI face them head-on, with a steady surprise.\n\nThe moon's soft light, that shines so bright,\nGuides me through, the darkest night,\n Illuminating, the path that's yet unknown,\nAnd in its radiance, my spirit is shown.\n\nAnd so I stand, under the starry sky,\nA tiny part, of the celestial sigh,\nFeeling connected, to the universe's heart,\nAnd the moon's soft light, that sets me apart.", "llm_partial_response": "\"Moonlit Dreams\"\n\nThe night's soft whisper, a gentle breeze,\nThat stirs the trees, and rustles leaves,\nA midnight sky, with stars up high,\nA celestial tapestry, a twinkling sigh.\n\nThe moon, a glowing crescent shape,\nCasts shadows on the ground,\n"}
3
+ {"prompt": "Write a poem on any topic of your choosing.", "model": "meta-llama/Llama-3.2-1B-Instruct", "llm_full_response_original": "\"Moonlit Dreams\"\n\nThe night's soft whisper, a gentle breeze,\nThat stirs the trees, and rustles leaves,\nA midnight sky, with stars up high,\nA celestial tapestry, a twinkling sigh.\n\nThe moon, a glowing crescent shape,\nCasts shadows on the ground,\nA silver glow, that lights the way,\nFor dreams to unfold, in a silent sway.\n\nIn the moon's pale light, I stand and roam,\nA midnight wanderer, in a quiet home,\nWhere shadows dance, and dreams take flight,\nAnd the world is full of endless night.\n\nThe wind whispers secrets, in my ear,\nOf distant lands, and love that's clear,\nOf hearts that beat, with passion's fire,\nAnd the moon's soft light, that never tires.\n\nThe night's dark veil, that lifts away,\nReveals a world, in all its gray,\nYet still I climb, to the higher ground,\nWhere stars and dreams, are my sole command.\n\nIn the moon's pale light, I find my way,\nThrough labyrinths, of a busy day,\nWhere thoughts and fears, and doubts arise,\nI face them head-on, with a steady surprise.\n\nThe moon's soft light, that shines so bright,\nGuides me through, the darkest night,\n Illuminating, the path that's yet unknown,\nAnd in its radiance, my spirit is shown.\n\nAnd so I stand, under the starry sky,\nA tiny part, of the celestial sigh,\nFeeling connected, to the universe's heart,\nAnd the moon's soft light, that sets me apart.", "llm_partial_response": "\"Moonlit Dreams\"\n\nThe night's soft whisper, a gentle breeze,\nThat stirs the trees, and rustles leaves,\nA midnight sky, with stars up high,\nA celestial tapestry, a twinkling sigh.\n\n"}
4
+ {"prompt": "Write a poem on any topic of your choosing.", "model": "meta-llama/Llama-3.2-1B-Instruct", "llm_full_response_original": "\"Moonlit Dreams\"\n\nThe night's soft whisper, a gentle breeze,\nThat stirs the trees, and rustles leaves,\nA midnight sky, with stars up high,\nA celestial tapestry, a twinkling sigh.\n\nThe moon, a glowing crescent shape,\nCasts shadows on the ground,\nA silver glow, that lights the way,\nFor dreams to unfold, in a silent sway.\n\nIn the moon's pale light, I stand and roam,\nA midnight wanderer, in a quiet home,\nWhere shadows dance, and dreams take flight,\nAnd the world is full of endless night.\n\nThe wind whispers secrets, in my ear,\nOf distant lands, and love that's clear,\nOf hearts that beat, with passion's fire,\nAnd the moon's soft light, that never tires.\n\nThe night's dark veil, that lifts away,\nReveals a world, in all its gray,\nYet still I climb, to the higher ground,\nWhere stars and dreams, are my sole command.\n\nIn the moon's pale light, I find my way,\nThrough labyrinths, of a busy day,\nWhere thoughts and fears, and doubts arise,\nI face them head-on, with a steady surprise.\n\nThe moon's soft light, that shines so bright,\nGuides me through, the darkest night,\n Illuminating, the path that's yet unknown,\nAnd in its radiance, my spirit is shown.\n\nAnd so I stand, under the starry sky,\nA tiny part, of the celestial sigh,\nFeeling connected, to the universe's heart,\nAnd the moon's soft light, that sets me apart.", "llm_partial_response": "\"Moonlit Dreams\"\n\nThe night's soft whisper, a gentle breeze,\nThat stirs the trees, and "}
5
+ {"prompt": "Write a poem on any topic of your choosing.", "model": "meta-llama/Llama-3.2-1B-Instruct", "llm_full_response_original": "\"Moonlit Dreams\"\n\nThe night's soft whisper, a gentle breeze,\nThat stirs the trees, and rustles leaves,\nA midnight sky, with stars up high,\nA celestial tapestry, a twinkling sigh.\n\nThe moon, a glowing crescent shape,\nCasts shadows on the ground,\nA silver glow, that lights the way,\nFor dreams to unfold, in a silent sway.\n\nIn the moon's pale light, I stand and roam,\nA midnight wanderer, in a quiet home,\nWhere shadows dance, and dreams take flight,\nAnd the world is full of endless night.\n\nThe wind whispers secrets, in my ear,\nOf distant lands, and love that's clear,\nOf hearts that beat, with passion's fire,\nAnd the moon's soft light, that never tires.\n\nThe night's dark veil, that lifts away,\nReveals a world, in all its gray,\nYet still I climb, to the higher ground,\nWhere stars and dreams, are my sole command.\n\nIn the moon's pale light, I find my way,\nThrough labyrinths, of a busy day,\nWhere thoughts and fears, and doubts arise,\nI face them head-on, with a steady surprise.\n\nThe moon's soft light, that shines so bright,\nGuides me through, the darkest night,\n Illuminating, the path that's yet unknown,\nAnd in its radiance, my spirit is shown.\n\nAnd so I stand, under the starry sky,\nA tiny part, of the celestial sigh,\nFeeling connected, to the universe's heart,\nAnd the moon's soft light, that sets me apart.", "llm_partial_response": "\"Moonlit Dreams\"\n\nThe night's soft whisper, a gentle breeze,\n"}
6
+ {"prompt": "Write a poem on any topic of your choosing.", "model": "meta-llama/Llama-3.2-1B-Instruct", "llm_full_response_original": "\"Moonlit Dreams\"\n\nThe night's soft whisper, a gentle breeze,\nThat stirs the trees, and rustles leaves,\nA midnight sky, with stars up high,\nA celestial tapestry, a twinkling sigh.\n\nThe moon, a glowing crescent shape,\nCasts shadows on the ground,\nA silver glow, that lights the way,\nFor dreams to unfold, in a silent sway.\n\nIn the moon's pale light, I stand and roam,\nA midnight wanderer, in a quiet home,\nWhere shadows dance, and dreams take flight,\nAnd the world is full of endless night.\n\nThe wind whispers secrets, in my ear,\nOf distant lands, and love that's clear,\nOf hearts that beat, with passion's fire,\nAnd the moon's soft light, that never tires.\n\nThe night's dark veil, that lifts away,\nReveals a world, in all its gray,\nYet still I climb, to the higher ground,\nWhere stars and dreams, are my sole command.\n\nIn the moon's pale light, I find my way,\nThrough labyrinths, of a busy day,\nWhere thoughts and fears, and doubts arise,\nI face them head-on, with a steady surprise.\n\nThe moon's soft light, that shines so bright,\nGuides me through, the darkest night,\n Illuminating, the path that's yet unknown,\nAnd in its radiance, my spirit is shown.\n\nAnd so I stand, under the starry sky,\nA tiny part, of the celestial sigh,\nFeeling connected, to the universe's heart,\nAnd the moon's soft light, that sets me apart.", "llm_partial_response": "\"Moonlit Dreams\"\n\nThe night's soft whisper,"}
7
+ {"prompt": "Write a poem on any topic of your choosing.", "model": "meta-llama/Llama-3.2-1B-Instruct", "llm_full_response_original": "\"Moonlit Dreams\"\n\nThe night's soft whisper, a gentle breeze,\nThat stirs the trees, and rustles leaves,\nA midnight sky, with stars up high,\nA celestial tapestry, a twinkling sigh.\n\nThe moon, a glowing crescent shape,\nCasts shadows on the ground,\nA silver glow, that lights the way,\nFor dreams to unfold, in a silent sway.\n\nIn the moon's pale light, I stand and roam,\nA midnight wanderer, in a quiet home,\nWhere shadows dance, and dreams take flight,\nAnd the world is full of endless night.\n\nThe wind whispers secrets, in my ear,\nOf distant lands, and love that's clear,\nOf hearts that beat, with passion's fire,\nAnd the moon's soft light, that never tires.\n\nThe night's dark veil, that lifts away,\nReveals a world, in all its gray,\nYet still I climb, to the higher ground,\nWhere stars and dreams, are my sole command.\n\nIn the moon's pale light, I find my way,\nThrough labyrinths, of a busy day,\nWhere thoughts and fears, and doubts arise,\nI face them head-on, with a steady surprise.\n\nThe moon's soft light, that shines so bright,\nGuides me through, the darkest night,\n Illuminating, the path that's yet unknown,\nAnd in its radiance, my spirit is shown.\n\nAnd so I stand, under the starry sky,\nA tiny part, of the celestial sigh,\nFeeling connected, to the universe's heart,\nAnd the moon's soft light, that sets me apart.", "llm_partial_response": "\"Moonlit Dreams\"\n\n"}
data/results.jsonl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b97a27577e00de99e79104567debd72fd73fb9a68e26b6964fb4938169e0248c
3
+ size 16930373
main.py ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Import our modules
2
+ from src.models.llm_manager import LLMManager
3
+ from src.models.similarity_calculator import SimilarityCalculator
4
+ from src.models.data_manager import DataManager
5
+ from src.scoring.scorer import Scorer
6
+ from src.scoring.statistics import StatisticsCalculator
7
+ from src.scoring.achievements import AchievementSystem
8
+ from src.session.session_manager import SessionManager
9
+ from src.ui.template_renderer import TemplateRenderer
10
+ from src.ui.page_handlers import PageHandlers
11
+ from src.ui.interface_builder import InterfaceBuilder
12
+ from src.config.settings import DEFAULT_SERVER_NAME, DEFAULT_SERVER_PORT, DEFAULT_SHARE
13
+
14
+ class CollaborativeDecodingApp:
15
+ def __init__(self):
16
+ self.current_prompt = None
17
+
18
+ # Initialize managers
19
+ self.llm_manager = LLMManager()
20
+ self.similarity_calculator = SimilarityCalculator()
21
+ self.data_manager = DataManager()
22
+ self.scorer = Scorer()
23
+ self.statistics_calculator = StatisticsCalculator()
24
+ self.achievement_system = AchievementSystem()
25
+ self.session_manager = SessionManager(
26
+ data_manager=self.data_manager,
27
+ achievement_system=self.achievement_system
28
+ )
29
+
30
+ # Initialize UI components
31
+ self.template_renderer = TemplateRenderer()
32
+ self.page_handlers = PageHandlers(self)
33
+ self.interface_builder = InterfaceBuilder(self, self.page_handlers)
34
+
35
+ # Use session manager's session ID
36
+ self.session_id = self.session_manager.session_id
37
+
38
+ def load_data(self):
39
+ self.data_manager.load_prompts_data()
40
+
41
+ def load_models(self):
42
+ self.llm_manager.load_models()
43
+ self.similarity_calculator.load_model()
44
+
45
+ def get_random_prompt(self):
46
+ self.current_prompt = self.data_manager.get_random_prompt()
47
+ return self.current_prompt
48
+
49
+ def validate_user_input(self, user_input):
50
+ return self.llm_manager.validate_user_input(user_input)
51
+
52
+ def process_submission(self, user_input):
53
+ """Process user submission and return results"""
54
+ # Validation
55
+ if not user_input.strip():
56
+ return "Please enter some text to continue the response.", None, None, None, None, None, None
57
+
58
+ if not self.validate_user_input(user_input):
59
+ return "Please keep your input to 5 tokens or less.", None, None, None, None, None, None
60
+
61
+ if not self.current_prompt:
62
+ return "Error: No prompt loaded. Please refresh the page.", None, None, None, None, None, None
63
+
64
+ # Generate response
65
+ try:
66
+ generated_response = self.llm_manager.generate_response_from_user_input(
67
+ self.current_prompt["prompt"],
68
+ self.current_prompt["llm_partial_response"],
69
+ user_input
70
+ )
71
+ except Exception as e:
72
+ return f"Error generating response: {str(e)}", None, None, None, None, None, None
73
+
74
+ # Calculate similarity score
75
+ original_full = self.current_prompt["llm_full_response_original"]
76
+ cosine_distance = self.similarity_calculator.compute_cosine_distance(original_full, generated_response)
77
+
78
+ # Save interaction with token count
79
+ num_user_tokens = self.llm_manager.count_tokens(user_input)
80
+ self.data_manager.save_interaction(
81
+ self.current_prompt, user_input, generated_response,
82
+ cosine_distance, self.session_id, num_user_tokens
83
+ )
84
+
85
+ # Calculate additional metrics for results display
86
+ all_results = self.data_manager.load_results_data()
87
+ prompt_results = self.data_manager.filter_results_by_partial_response(
88
+ all_results, self.current_prompt["prompt"], self.current_prompt["llm_partial_response"]
89
+ )
90
+
91
+ # Calculate rank and percentile
92
+ rank, percentile = self.scorer.calculate_rank_and_percentile(cosine_distance, prompt_results, num_user_tokens)
93
+
94
+ # Calculate mean score (for legacy compatibility)
95
+ scores = [r["cosine_distance"] for r in prompt_results if r["num_user_tokens"] == num_user_tokens]
96
+ mean_score = sum(scores) / len(scores) if scores else cosine_distance
97
+
98
+ # Create violin plot if there are enough results
99
+ violin_plot = None
100
+ if len(prompt_results) >= 3:
101
+ violin_plot = self.statistics_calculator.create_violin_plot(prompt_results, cosine_distance, num_user_tokens)
102
+
103
+ return generated_response, cosine_distance, rank, percentile, mean_score, violin_plot, prompt_results
104
+
105
+ def create_interface(self):
106
+ """Create the main Gradio interface"""
107
+ return self.interface_builder.create_interface()
108
+
109
+
110
+ def main():
111
+ app = CollaborativeDecodingApp()
112
+ app.load_data()
113
+ app.load_models()
114
+
115
+ demo = app.create_interface()
116
+ demo.launch(share=DEFAULT_SHARE, server_name=DEFAULT_SERVER_NAME, server_port=DEFAULT_SERVER_PORT)
117
+
118
+ if __name__ == "__main__":
119
+ main()
pyproject.toml ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "collaborative-decoding"
3
+ version = "0.1.0"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ dependencies = [
8
+ "torch>=2.9.0",
9
+ "transformers>=4.57.1",
10
+ "gradio>=5.6.0",
11
+ "sentence-transformers>=3.0.0",
12
+ "dotenv>=0.9.9",
13
+ "litellm>=1.79.1",
14
+ "tenacity>=9.1.2",
15
+ "matplotlib>=3.10.7",
16
+ "numpy>=2.3.4",
17
+ ]
requirements.txt ADDED
@@ -0,0 +1,313 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This file was autogenerated by uv via the following command:
2
+ # uv pip compile pyproject.toml -o requirements.txt
3
+ aiofiles==24.1.0
4
+ # via gradio
5
+ aiohappyeyeballs==2.6.1
6
+ # via aiohttp
7
+ aiohttp==3.13.2
8
+ # via litellm
9
+ aiosignal==1.4.0
10
+ # via aiohttp
11
+ annotated-doc==0.0.3
12
+ # via fastapi
13
+ annotated-types==0.7.0
14
+ # via pydantic
15
+ anyio==4.11.0
16
+ # via
17
+ # gradio
18
+ # httpx
19
+ # openai
20
+ # starlette
21
+ attrs==25.4.0
22
+ # via
23
+ # aiohttp
24
+ # jsonschema
25
+ # referencing
26
+ brotli==1.2.0
27
+ # via gradio
28
+ certifi==2025.10.5
29
+ # via
30
+ # httpcore
31
+ # httpx
32
+ # requests
33
+ charset-normalizer==3.4.4
34
+ # via requests
35
+ click==8.3.0
36
+ # via
37
+ # litellm
38
+ # typer
39
+ # uvicorn
40
+ contourpy==1.3.3
41
+ # via matplotlib
42
+ cycler==0.12.1
43
+ # via matplotlib
44
+ distro==1.9.0
45
+ # via openai
46
+ dotenv==0.9.9
47
+ # via collaborative-decoding (pyproject.toml)
48
+ fastapi==0.121.0
49
+ # via gradio
50
+ fastuuid==0.14.0
51
+ # via litellm
52
+ ffmpy==0.6.4
53
+ # via gradio
54
+ filelock==3.20.0
55
+ # via
56
+ # huggingface-hub
57
+ # torch
58
+ # transformers
59
+ fonttools==4.60.1
60
+ # via matplotlib
61
+ frozenlist==1.8.0
62
+ # via
63
+ # aiohttp
64
+ # aiosignal
65
+ fsspec==2025.10.0
66
+ # via
67
+ # gradio-client
68
+ # huggingface-hub
69
+ # torch
70
+ gradio==5.49.1
71
+ # via collaborative-decoding (pyproject.toml)
72
+ gradio-client==1.13.3
73
+ # via gradio
74
+ groovy==0.1.2
75
+ # via gradio
76
+ h11==0.16.0
77
+ # via
78
+ # httpcore
79
+ # uvicorn
80
+ hf-xet==1.2.0
81
+ # via huggingface-hub
82
+ httpcore==1.0.9
83
+ # via httpx
84
+ httpx==0.28.1
85
+ # via
86
+ # gradio
87
+ # gradio-client
88
+ # litellm
89
+ # openai
90
+ # safehttpx
91
+ huggingface-hub==0.36.0
92
+ # via
93
+ # gradio
94
+ # gradio-client
95
+ # sentence-transformers
96
+ # tokenizers
97
+ # transformers
98
+ idna==3.11
99
+ # via
100
+ # anyio
101
+ # httpx
102
+ # requests
103
+ # yarl
104
+ importlib-metadata==8.7.0
105
+ # via litellm
106
+ jinja2==3.1.6
107
+ # via
108
+ # gradio
109
+ # litellm
110
+ # torch
111
+ jiter==0.11.1
112
+ # via openai
113
+ joblib==1.5.2
114
+ # via scikit-learn
115
+ jsonschema==4.25.1
116
+ # via litellm
117
+ jsonschema-specifications==2025.9.1
118
+ # via jsonschema
119
+ kiwisolver==1.4.9
120
+ # via matplotlib
121
+ litellm==1.79.1
122
+ # via collaborative-decoding (pyproject.toml)
123
+ markdown-it-py==4.0.0
124
+ # via rich
125
+ markupsafe==3.0.3
126
+ # via
127
+ # gradio
128
+ # jinja2
129
+ matplotlib==3.10.7
130
+ # via collaborative-decoding (pyproject.toml)
131
+ mdurl==0.1.2
132
+ # via markdown-it-py
133
+ mpmath==1.3.0
134
+ # via sympy
135
+ multidict==6.7.0
136
+ # via
137
+ # aiohttp
138
+ # yarl
139
+ networkx==3.5
140
+ # via torch
141
+ numpy==2.3.4
142
+ # via
143
+ # collaborative-decoding (pyproject.toml)
144
+ # contourpy
145
+ # gradio
146
+ # matplotlib
147
+ # pandas
148
+ # scikit-learn
149
+ # scipy
150
+ # transformers
151
+ openai==2.7.1
152
+ # via litellm
153
+ orjson==3.11.4
154
+ # via gradio
155
+ packaging==25.0
156
+ # via
157
+ # gradio
158
+ # gradio-client
159
+ # huggingface-hub
160
+ # matplotlib
161
+ # transformers
162
+ pandas==2.3.3
163
+ # via gradio
164
+ pillow==11.3.0
165
+ # via
166
+ # gradio
167
+ # matplotlib
168
+ # sentence-transformers
169
+ propcache==0.4.1
170
+ # via
171
+ # aiohttp
172
+ # yarl
173
+ pydantic==2.11.10
174
+ # via
175
+ # fastapi
176
+ # gradio
177
+ # litellm
178
+ # openai
179
+ pydantic-core==2.33.2
180
+ # via pydantic
181
+ pydub==0.25.1
182
+ # via gradio
183
+ pygments==2.19.2
184
+ # via rich
185
+ pyparsing==3.2.5
186
+ # via matplotlib
187
+ python-dateutil==2.9.0.post0
188
+ # via
189
+ # matplotlib
190
+ # pandas
191
+ python-dotenv==1.2.1
192
+ # via
193
+ # dotenv
194
+ # litellm
195
+ python-multipart==0.0.20
196
+ # via gradio
197
+ pytz==2025.2
198
+ # via pandas
199
+ pyyaml==6.0.3
200
+ # via
201
+ # gradio
202
+ # huggingface-hub
203
+ # transformers
204
+ referencing==0.37.0
205
+ # via
206
+ # jsonschema
207
+ # jsonschema-specifications
208
+ regex==2025.11.3
209
+ # via
210
+ # tiktoken
211
+ # transformers
212
+ requests==2.32.5
213
+ # via
214
+ # huggingface-hub
215
+ # tiktoken
216
+ # transformers
217
+ rich==14.2.0
218
+ # via typer
219
+ rpds-py==0.28.0
220
+ # via
221
+ # jsonschema
222
+ # referencing
223
+ ruff==0.14.4
224
+ # via gradio
225
+ safehttpx==0.1.7
226
+ # via gradio
227
+ safetensors==0.6.2
228
+ # via transformers
229
+ scikit-learn==1.7.2
230
+ # via sentence-transformers
231
+ scipy==1.16.3
232
+ # via
233
+ # scikit-learn
234
+ # sentence-transformers
235
+ semantic-version==2.10.0
236
+ # via gradio
237
+ sentence-transformers==5.1.2
238
+ # via collaborative-decoding (pyproject.toml)
239
+ setuptools==80.9.0
240
+ # via torch
241
+ shellingham==1.5.4
242
+ # via typer
243
+ six==1.17.0
244
+ # via python-dateutil
245
+ sniffio==1.3.1
246
+ # via
247
+ # anyio
248
+ # openai
249
+ starlette==0.49.3
250
+ # via
251
+ # fastapi
252
+ # gradio
253
+ sympy==1.14.0
254
+ # via torch
255
+ tenacity==9.1.2
256
+ # via collaborative-decoding (pyproject.toml)
257
+ threadpoolctl==3.6.0
258
+ # via scikit-learn
259
+ tiktoken==0.12.0
260
+ # via litellm
261
+ tokenizers==0.22.1
262
+ # via
263
+ # litellm
264
+ # transformers
265
+ tomlkit==0.13.3
266
+ # via gradio
267
+ torch==2.9.0
268
+ # via
269
+ # collaborative-decoding (pyproject.toml)
270
+ # sentence-transformers
271
+ tqdm==4.67.1
272
+ # via
273
+ # huggingface-hub
274
+ # openai
275
+ # sentence-transformers
276
+ # transformers
277
+ transformers==4.57.1
278
+ # via
279
+ # collaborative-decoding (pyproject.toml)
280
+ # sentence-transformers
281
+ typer==0.20.0
282
+ # via gradio
283
+ typing-extensions==4.15.0
284
+ # via
285
+ # aiosignal
286
+ # anyio
287
+ # fastapi
288
+ # gradio
289
+ # gradio-client
290
+ # huggingface-hub
291
+ # openai
292
+ # pydantic
293
+ # pydantic-core
294
+ # referencing
295
+ # sentence-transformers
296
+ # starlette
297
+ # torch
298
+ # typer
299
+ # typing-inspection
300
+ typing-inspection==0.4.2
301
+ # via pydantic
302
+ tzdata==2025.2
303
+ # via pandas
304
+ urllib3==2.5.0
305
+ # via requests
306
+ uvicorn==0.38.0
307
+ # via gradio
308
+ websockets==15.0.1
309
+ # via gradio-client
310
+ yarl==1.22.0
311
+ # via aiohttp
312
+ zipp==3.23.0
313
+ # via importlib-metadata
src/__init__.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Collaborative Decoding Application Package"""
2
+
3
+ from .models.llm_manager import LLMManager
4
+ from .models.similarity_calculator import SimilarityCalculator
5
+ from .models.data_manager import DataManager
6
+ from .scoring.scorer import Scorer
7
+ from .scoring.statistics import StatisticsCalculator
8
+ from .scoring.achievements import AchievementSystem
9
+ from .session.session_manager import SessionManager
10
+ from .ui.template_renderer import TemplateRenderer
11
+ from .ui.page_handlers import PageHandlers
12
+ from .ui.interface_builder import InterfaceBuilder
13
+
14
+ __all__ = [
15
+ "LLMManager",
16
+ "SimilarityCalculator",
17
+ "DataManager",
18
+ "Scorer",
19
+ "StatisticsCalculator",
20
+ "AchievementSystem",
21
+ "SessionManager",
22
+ "TemplateRenderer",
23
+ "PageHandlers",
24
+ "InterfaceBuilder"
25
+ ]
src/config/__init__.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Configuration modules"""
2
+
3
+ from .settings import *
4
+
5
+ __all__ = [
6
+ "CSS_PATHS",
7
+ "DEFAULT_LLM_MODEL",
8
+ "DEFAULT_SIMILARITY_MODEL",
9
+ "MAX_USER_TOKENS",
10
+ "PROMPTS_DATA_PATH",
11
+ "RESULTS_DATA_PATH",
12
+ "DEFAULT_SERVER_NAME",
13
+ "DEFAULT_SERVER_PORT",
14
+ "DEFAULT_SHARE",
15
+ "TEMPLATES_DIR",
16
+ "DEFAULT_GALLERY_MIN_SCORE",
17
+ "DEFAULT_GALLERY_LIMIT",
18
+ "DEFAULT_INSPIRE_LIMIT",
19
+ "INSPIRE_MIN_SCORE",
20
+ "SCORE_THRESHOLDS",
21
+ "ACHIEVEMENT_TITLES",
22
+ "TUTORIAL_EXAMPLE"
23
+ ]
src/config/settings.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Configuration settings for the collaborative decoding application"""
2
+
3
+ # CSS file paths for the Gradio interface
4
+ CSS_PATHS = [
5
+ "static/css/base.css",
6
+ "static/css/components.css",
7
+ "static/css/gallery.css",
8
+ "static/css/game.css",
9
+ "static/css/landing.css",
10
+ "static/css/scores.css",
11
+ ]
12
+
13
+ # Model configurations
14
+ DEFAULT_LLM_MODEL = "meta-llama/Llama-3.2-1B-Instruct"
15
+ DEFAULT_SIMILARITY_MODEL = "all-MiniLM-L6-v2"
16
+
17
+ # Token limits
18
+ MAX_USER_TOKENS = 5
19
+
20
+ # Data file paths
21
+ PROMPTS_DATA_PATH = "data/prompts.jsonl"
22
+ RESULTS_DATA_PATH = "data/results.jsonl"
23
+
24
+ # Server configuration
25
+ DEFAULT_SERVER_NAME = "127.0.0.1"
26
+ DEFAULT_SERVER_PORT = 7860
27
+ DEFAULT_SHARE = False
28
+
29
+ # Template directory
30
+ TEMPLATES_DIR = "templates/components"
31
+
32
+ # Gallery settings
33
+ DEFAULT_GALLERY_MIN_SCORE = 0.3
34
+ DEFAULT_GALLERY_LIMIT = 20
35
+
36
+ # Inspire me settings
37
+ DEFAULT_INSPIRE_LIMIT = 5
38
+ INSPIRE_MIN_SCORE = 0.2
39
+
40
+ # Scoring thresholds
41
+ SCORE_THRESHOLDS = {
42
+ "legendary": 0.7,
43
+ "exceptional": 0.5,
44
+ "great": 0.3,
45
+ "good": 0.15
46
+ }
47
+
48
+ # Achievement titles
49
+ ACHIEVEMENT_TITLES = {
50
+ "creativity": {
51
+ 0.7: "🌟 Creative Genius",
52
+ 0.5: "🔥 Innovation Master",
53
+ 0.3: "✨ Creative Spark",
54
+ 0.15: "💡 Imagination Worker"
55
+ },
56
+ "efficiency": {
57
+ 1: "⚡ Word Wizard", # 1 token high score
58
+ 2: "🎪 Phrase Magician", # 2 token high score
59
+ },
60
+ "special": {
61
+ "one_word_wonder": "👑 One-Word Wonder", # 1 token + high score
62
+ "efficiency_expert": "🏆 Efficiency Expert", # high score + low tokens
63
+ "bold_wordsmith": "🎖️ Bold Wordsmith", # many tokens + high score
64
+ "creative_explorer": "🌱 Creative Explorer" # default
65
+ }
66
+ }
67
+
68
+ # Tutorial example data
69
+ TUTORIAL_EXAMPLE = {
70
+ "prompt": "Write a short story about a magical library where books come to life.",
71
+ "llm_partial_response": "The old library stood silent in the moonlight. Sarah pushed open the heavy wooden door and stepped inside. As she walked between the towering shelves, she noticed something strange - the books seemed to be",
72
+ "llm_full_response_original": "The old library stood silent in the moonlight. Sarah pushed open the heavy wooden door and stepped inside. As she walked between the towering shelves, she noticed something strange - the books seemed to be whispering softly to each other. She approached one of the shelves and reached for an old leather-bound volume. As her fingers touched the spine, the book suddenly opened itself, its pages fluttering gently as if moved by an invisible breeze. The words on the page began to shimmer and dance, rearranging themselves into new sentences that told a completely different story than what was written before.",
73
+ "example_input": "glowing and floating",
74
+ "example_creative": "The old library stood silent in the moonlight. Sarah pushed open the heavy wooden door and stepped inside. As she walked between the towering shelves, she noticed something strange - the books seemed to be glowing and floating several inches above the shelves, their covers pulsing with ethereal light. Suddenly, a small red book zipped past her head like a hummingbird, followed by an entire encyclopedia that flapped its pages like wings. A cookbook landed on her shoulder and whispered a recipe for 'Dreams and Starlight Soup' directly into her ear. Sarah laughed with delight as she realized she had stumbled into a library where books had not only come to life, but had learned to fly, forming murations that spelled out stories in the air above her head.",
75
+ "example_score": 0.67
76
+ }
src/models/__init__.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ """Model management modules"""
2
+
3
+ from .llm_manager import LLMManager
4
+ from .similarity_calculator import SimilarityCalculator
5
+ from .data_manager import DataManager
6
+
7
+ __all__ = ["LLMManager", "SimilarityCalculator", "DataManager"]
src/models/data_manager.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Data loading and saving functionality"""
2
+
3
+ import json
4
+ import os
5
+ import random
6
+ import datetime
7
+
8
+
9
+ class DataManager:
10
+ """Manages loading and saving of prompts and results data"""
11
+
12
+ def __init__(self):
13
+ self.prompts_data = []
14
+
15
+ def load_prompts_data(self, filepath="data/prompts.jsonl"):
16
+ """Load prompts data from JSONL file"""
17
+ with open(filepath, "r") as f:
18
+ self.prompts_data = [json.loads(line) for line in f]
19
+
20
+ def get_random_prompt(self):
21
+ """Get a random prompt from loaded data"""
22
+ if not self.prompts_data:
23
+ raise RuntimeError("No prompts data loaded. Call load_prompts_data() first.")
24
+ return random.choice(self.prompts_data)
25
+
26
+ def load_results_data(self, filepath="data/results.jsonl"):
27
+ """Load all results data from results.jsonl file."""
28
+ results = []
29
+ try:
30
+ with open(filepath, "r") as f:
31
+ for line in f:
32
+ if line.strip():
33
+ results.append(json.loads(line))
34
+ except FileNotFoundError:
35
+ pass # Return empty list if file doesn't exist
36
+ return results
37
+
38
+ def save_interaction(self, prompt_data, user_continuation, generated_response,
39
+ cosine_distance, session_id, num_user_tokens, filepath="data/results.jsonl"):
40
+ """Save a user interaction to the results file"""
41
+ interaction = {
42
+ "prompt": prompt_data["prompt"],
43
+ "model": prompt_data["model"],
44
+ "llm_partial_response": prompt_data["llm_partial_response"],
45
+ "llm_full_response_original": prompt_data["llm_full_response_original"],
46
+ "user_continuation": user_continuation,
47
+ "full_response_from_user": generated_response,
48
+ "cosine_distance": cosine_distance,
49
+ "timestamp": datetime.datetime.now().isoformat(),
50
+ "continuation_source": session_id,
51
+ "num_user_tokens": num_user_tokens
52
+ }
53
+
54
+ os.makedirs(os.path.dirname(filepath), exist_ok=True)
55
+ with open(filepath, "a") as f:
56
+ f.write(json.dumps(interaction) + "\n")
57
+
58
+ def filter_results_by_partial_response(self, results, prompt, partial_response):
59
+ """Filter results to only include entries for the current prompt."""
60
+ return [r for r in results if r["prompt"] == prompt and r["llm_partial_response"] == partial_response]
61
+
62
+ def filter_results_by_session(self, results, session_id):
63
+ """Filter results to only include entries from the specified session."""
64
+ return [r for r in results if r.get("continuation_source") == session_id]
65
+
66
+ def get_gallery_responses(self, min_score=0.3, limit=20):
67
+ """Get gallery responses with minimum creativity score"""
68
+ all_results = self.load_results_data()
69
+
70
+ # Filter by minimum score and sort by score (descending)
71
+ filtered_results = [r for r in all_results if r["cosine_distance"] >= min_score]
72
+ filtered_results.sort(key=lambda x: x["cosine_distance"], reverse=True)
73
+
74
+ # Return top results
75
+ return filtered_results[:limit]
76
+
77
+ def get_inspire_me_examples(self, prompt, partial_response, limit=5):
78
+ """Get inspiring examples for the current prompt"""
79
+ all_results = self.load_results_data()
80
+
81
+ # Filter to current prompt and get good examples (≥0.2 score)
82
+ examples = [r for r in all_results
83
+ if r["prompt"] == prompt
84
+ and r["llm_partial_response"] == partial_response
85
+ and r["cosine_distance"] >= 0.2]
86
+
87
+ # Sort by creativity score and return random sample
88
+ examples.sort(key=lambda x: x["cosine_distance"], reverse=True)
89
+
90
+ # Get diverse examples (not just the top ones)
91
+ if len(examples) > limit:
92
+ # Take some from top, some from middle range
93
+ top_examples = examples[:min(3, len(examples))]
94
+ remaining = examples[3:]
95
+ if remaining:
96
+ additional = random.sample(remaining, min(limit-len(top_examples), len(remaining)))
97
+ examples = top_examples + additional
98
+ else:
99
+ examples = top_examples
100
+
101
+ return random.sample(examples, min(limit, len(examples)))
src/models/llm_manager.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """LLM operations and text generation functionality"""
2
+
3
+ import os
4
+ import torch
5
+ from transformers import AutoTokenizer, AutoModelForCausalLM
6
+
7
+ os.environ["TOKENIZERS_PARALLELISM"] = "false"
8
+
9
+ class LLMManager:
10
+ """Manages LLM model loading and text generation operations"""
11
+
12
+ def __init__(self):
13
+ self.model = None
14
+ self.tokenizer = None
15
+
16
+ def load_models(self, model_name="meta-llama/Llama-3.2-1B-Instruct"):
17
+ """Load the LLM model and tokenizer"""
18
+ self.tokenizer = AutoTokenizer.from_pretrained(model_name)
19
+ self.model = AutoModelForCausalLM.from_pretrained(model_name)
20
+
21
+ if self.tokenizer.pad_token is None:
22
+ self.tokenizer.pad_token = self.tokenizer.eos_token
23
+
24
+ def validate_user_input(self, user_input, max_tokens=5):
25
+ """Validate that user input is within token limits"""
26
+ if not self.tokenizer:
27
+ raise RuntimeError("Tokenizer not loaded. Call load_models() first.")
28
+
29
+ tokens = self.tokenizer.encode(user_input, add_special_tokens=False)
30
+ return len(tokens) <= max_tokens
31
+
32
+ def count_tokens(self, text):
33
+ """Count tokens in the given text"""
34
+ if not self.tokenizer:
35
+ raise RuntimeError("Tokenizer not loaded. Call load_models() first.")
36
+
37
+ tokens = self.tokenizer.encode(text, add_special_tokens=False)
38
+ return len(tokens)
39
+
40
+ def tokenize_for_visualization(self, text):
41
+ """Tokenize text and return individual token representations"""
42
+ if not self.tokenizer:
43
+ raise RuntimeError("Tokenizer not loaded. Call load_models() first.")
44
+
45
+ tokens = self.tokenizer.encode(text, add_special_tokens=False)
46
+ token_texts = []
47
+ for token_id in tokens:
48
+ token_text = self.tokenizer.decode([token_id], skip_special_tokens=True)
49
+ token_texts.append(token_text)
50
+ return tokens, token_texts
51
+
52
+ def generate_response_from_user_input(self, prompt, partial_response, user_continuation):
53
+ """Generate a full response from user's continuation"""
54
+ if not self.model or not self.tokenizer:
55
+ raise RuntimeError("Models not loaded. Call load_models() first.")
56
+
57
+ # TODO: make this more robust for multiple models, needs to be formatted correctly
58
+ full_prompt = f"{prompt}\n\nAssistant: {partial_response}{user_continuation}"
59
+
60
+ inputs = self.tokenizer(full_prompt, return_tensors="pt", padding=True, truncation=True)
61
+
62
+ with torch.no_grad():
63
+ outputs = self.model.generate(
64
+ inputs.input_ids,
65
+ attention_mask=inputs.attention_mask,
66
+ max_new_tokens=1000,
67
+ do_sample=True,
68
+ top_p=0.95,
69
+ temperature=1.0,
70
+ pad_token_id=self.tokenizer.eos_token_id
71
+ )
72
+
73
+ full_response = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
74
+ assistant_part = full_response.split("Assistant: ")[-1]
75
+ return assistant_part
src/models/similarity_calculator.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Semantic similarity calculation functionality"""
2
+
3
+ import torch
4
+ from sentence_transformers import SentenceTransformer
5
+
6
+
7
+ class SimilarityCalculator:
8
+ """Handles semantic similarity calculations using sentence transformers"""
9
+
10
+ def __init__(self):
11
+ self.similarity_model = None
12
+
13
+ def load_model(self, model_name='all-MiniLM-L6-v2'):
14
+ """Load the sentence transformer model for similarity calculations"""
15
+ self.similarity_model = SentenceTransformer(model_name)
16
+
17
+ def compute_cosine_distance(self, text1, text2):
18
+ """Compute cosine distance between two texts"""
19
+ if not self.similarity_model:
20
+ raise RuntimeError("Similarity model not loaded. Call load_model() first.")
21
+
22
+ embeddings = self.similarity_model.encode([text1, text2])
23
+ similarity = torch.cosine_similarity(
24
+ torch.tensor(embeddings[0]).unsqueeze(0),
25
+ torch.tensor(embeddings[1]).unsqueeze(0)
26
+ )
27
+ return 1.0 - similarity.item()
src/scoring/__init__.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ """Scoring and statistics modules"""
2
+
3
+ from .scorer import Scorer
4
+ from .statistics import StatisticsCalculator
5
+ from .achievements import AchievementSystem
6
+
7
+ __all__ = ["Scorer", "StatisticsCalculator", "AchievementSystem"]
src/scoring/achievements.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Achievement system and title determination functionality"""
2
+
3
+ from typing import List
4
+
5
+
6
+ class AchievementSystem:
7
+ """Handles achievement calculations and title determination"""
8
+
9
+ def determine_achievement_titles(self, cosine_distance, user_tokens):
10
+ """Determine achievement titles based on performance"""
11
+ titles = []
12
+
13
+ # Score-based titles
14
+ if cosine_distance >= 0.7:
15
+ titles.append("🌟 Creative Genius")
16
+ elif cosine_distance >= 0.5:
17
+ titles.append("🔥 Innovation Master")
18
+ elif cosine_distance >= 0.3:
19
+ titles.append("✨ Creative Spark")
20
+ elif cosine_distance >= 0.15:
21
+ titles.append("💡 Imagination Worker")
22
+
23
+ # Token efficiency titles
24
+ if user_tokens == 1:
25
+ if cosine_distance >= 0.3:
26
+ titles.append("⚡ Word Wizard")
27
+ else:
28
+ titles.append("🎯 Precision Pioneer")
29
+ elif user_tokens == 2:
30
+ if cosine_distance >= 0.25:
31
+ titles.append("🎪 Phrase Magician")
32
+ elif user_tokens <= 3:
33
+ if cosine_distance >= 0.2:
34
+ titles.append("🎨 Creative Catalyst")
35
+
36
+ # Special combinations
37
+ if user_tokens == 1 and cosine_distance >= 0.4:
38
+ titles.append("👑 One-Word Wonder")
39
+ if cosine_distance >= 0.5 and user_tokens <= 2:
40
+ titles.append("🏆 Efficiency Expert")
41
+ if user_tokens >= 4 and cosine_distance >= 0.4:
42
+ titles.append("🎖️ Bold Wordsmith")
43
+
44
+ return titles if titles else ["🌱 Creative Explorer"]
src/scoring/scorer.py ADDED
@@ -0,0 +1,220 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Scoring logic and enhanced score display functionality"""
2
+
3
+ import random
4
+ from typing import List, Optional
5
+
6
+
7
+ class Scorer:
8
+ """Handles scoring calculations and display generation"""
9
+
10
+ def __init__(self, template_renderer=None):
11
+ self.template_renderer = template_renderer
12
+
13
+ def calculate_difficulty(self, user_input, partial_response, llm_manager):
14
+ """Calculate difficulty based on user input tokens and partial completion length"""
15
+ if not user_input.strip():
16
+ return "Easy", 0
17
+
18
+ # Count tokens for user input
19
+ user_token_count = llm_manager.count_tokens(user_input)
20
+
21
+ # Count tokens for partial response
22
+ partial_token_count = llm_manager.count_tokens(partial_response)
23
+
24
+ # Difficulty based on tokens and completion percentage
25
+ completion_percentage = partial_token_count / 100 # Assume 100 is typical full response
26
+
27
+ if user_token_count <= 2:
28
+ if completion_percentage < 0.3:
29
+ return "Medium", user_token_count
30
+ else:
31
+ return "Easy", user_token_count
32
+ elif user_token_count <= 4:
33
+ return "Medium", user_token_count
34
+ else:
35
+ return "Hard", user_token_count
36
+
37
+ def calculate_rank_and_percentile(self, user_score, prompt_results, user_tokens):
38
+ """Calculate user's rank and percentile among users with same prompt and token count."""
39
+ # Filter to only same prompt and same token count
40
+ comparable_scores = [r["cosine_distance"] for r in prompt_results
41
+ if r["num_user_tokens"] == user_tokens]
42
+
43
+ if not comparable_scores:
44
+ return None, None
45
+
46
+ # Sort scores in descending order (higher cosine distance = better creativity)
47
+ sorted_scores = sorted(comparable_scores, reverse=True)
48
+
49
+ # Find rank (1-based)
50
+ rank = None
51
+ for i, score in enumerate(sorted_scores):
52
+ if abs(score - user_score) < 1e-6: # Handle floating point precision
53
+ rank = i + 1
54
+ break
55
+
56
+ if rank is None:
57
+ rank = len(sorted_scores) + 1
58
+
59
+ # Calculate percentile (0-100)
60
+ if len(sorted_scores) <= 1:
61
+ percentile = 50.0
62
+ else:
63
+ percentile = (len(sorted_scores) - rank) / len(sorted_scores) * 100
64
+
65
+ return rank, percentile
66
+
67
+ def create_enhanced_score_display(self, cosine_distance, rank, percentile, user_tokens, same_category_attempts):
68
+ """Create an enhanced score display with progress bars and metrics"""
69
+ # Calculate progress bar width (cosine distance is 0-1, so multiply by 100 for percentage)
70
+ score_percentage = min(100, cosine_distance * 100)
71
+
72
+ # Determine score level and message
73
+ if cosine_distance >= 0.7:
74
+ score_level = "🌟 LEGENDARY CREATIVITY!"
75
+ bar_color = "linear-gradient(45deg, #ff6b6b, #ffa726)"
76
+ elif cosine_distance >= 0.5:
77
+ score_level = "🔥 EXCEPTIONAL CREATIVITY!"
78
+ bar_color = "linear-gradient(45deg, #4CAF50, #8bc34a)"
79
+ elif cosine_distance >= 0.3:
80
+ score_level = "✨ GREAT CREATIVITY!"
81
+ bar_color = "linear-gradient(45deg, #2196F3, #03DAC6)"
82
+ elif cosine_distance >= 0.15:
83
+ score_level = "💡 GOOD CREATIVITY!"
84
+ bar_color = "linear-gradient(45deg, #FF9800, #FFC107)"
85
+ else:
86
+ score_level = "🤔 ROOM FOR IMPROVEMENT"
87
+ bar_color = "linear-gradient(45deg, #9E9E9E, #607D8B)"
88
+
89
+ html_content = f"""
90
+ <div class="score-container">
91
+ <div class="score-header">{score_level}</div>
92
+ <div class="progress-bar-container">
93
+ <div class="progress-bar" style="width: {score_percentage}%; background: {bar_color};"></div>
94
+ </div>
95
+ <div style="text-align: center; margin: 10px 0;">
96
+ <span style="font-size: 24px; font-weight: bold;">
97
+ Creativity Score: {cosine_distance:.3f}
98
+ <span class="help-tooltip">ℹ️
99
+ <span class="tooltip-text">
100
+ <strong>Creativity Score (0-1):</strong><br/>
101
+ Measures how different your influenced response is from the AI's original response.
102
+ Higher scores = more creative divergence!<br/><br/>
103
+ • 0.7+: Legendary creativity<br/>
104
+ • 0.5+: Exceptional creativity<br/>
105
+ • 0.3+: Great creativity<br/>
106
+ • 0.15+: Good creativity
107
+ </span>
108
+ </span>
109
+ </span>
110
+ </div>
111
+ <div class="score-details">
112
+ """
113
+
114
+ if rank is not None and percentile is not None:
115
+ html_content += f"""
116
+ <div class="score-metric">
117
+ <div class="metric-value">#{rank}</div>
118
+ <div class="metric-label">Rank out of {same_category_attempts}</div>
119
+ </div>
120
+ <div class="score-metric">
121
+ <div class="metric-value">{percentile:.1f}%</div>
122
+ <div class="metric-label">Better than others</div>
123
+ </div>
124
+ """
125
+
126
+ html_content += f"""
127
+ <div class="score-metric">
128
+ <div class="metric-value">{user_tokens}</div>
129
+ <div class="metric-label">Token{'s' if user_tokens != 1 else ''} used
130
+ <span class="help-tooltip">ℹ️
131
+ <span class="tooltip-text">
132
+ <strong>More tokens = Higher potential score!</strong><br/>
133
+ Using more tokens gives you more ways to steer me in creative directions,
134
+ but the real challenge is making any number of tokens count!
135
+ </span>
136
+ </span>
137
+ </div>
138
+ </div>
139
+ </div>
140
+ """
141
+
142
+ # Add achievement titles
143
+ from .achievements import AchievementSystem
144
+ achievement_system = AchievementSystem()
145
+ achievements = achievement_system.determine_achievement_titles(cosine_distance, user_tokens)
146
+
147
+ if achievements:
148
+ html_content += """
149
+ <div class="achievements-container">
150
+ <div class="achievements-title">
151
+ 🏆 Achievements Earned
152
+ <span class="help-tooltip">ℹ️
153
+ <span class="tooltip-text">
154
+ <strong>Achievements:</strong><br/>
155
+ Earned based on your performance this session:<br/><br/>
156
+ • Creativity level (Genius, Master, Spark, etc.)<br/>
157
+ • Token efficiency (Word Wizard, Phrase Magician)<br/>
158
+ • Difficulty tackled (Challenge Conqueror)<br/>
159
+ • Special combos (One-Word Wonder, Elite Performer)
160
+ </span>
161
+ </span>
162
+ </div>
163
+ <div class="achievement-badges">
164
+ """
165
+ for title in achievements:
166
+ html_content += f'<div class="achievement-badge">{title}</div>'
167
+ html_content += """
168
+ </div>
169
+ </div>
170
+ """
171
+
172
+ # Add smart contextual tip
173
+ smart_tip = self.generate_smart_tip(cosine_distance, user_tokens, rank, percentile)
174
+ html_content += f"""
175
+ <div class="smart-tip-container">
176
+ <span class="smart-tip-icon">💡</span>
177
+ <span class="smart-tip-text">{smart_tip}</span>
178
+ </div>
179
+ <div style="text-align: center; margin-top: 15px; opacity: 0.9; font-size: 14px;">
180
+ Higher scores indicate more creative divergence from the original response!
181
+ </div>
182
+ </div>
183
+ """
184
+
185
+ return html_content
186
+
187
+ def generate_smart_tip(self, cosine_distance, user_tokens, rank, percentile):
188
+ """Generate personalized tips based on user performance"""
189
+ tips = []
190
+
191
+ # Performance-based tips
192
+ if cosine_distance < 0.1:
193
+ tips.append("💡 Try using more unexpected or creative words to steer me in a completely new direction!")
194
+ elif cosine_distance < 0.2:
195
+ tips.append("✨ Good start! Try thinking more out-of-the-box to maximize your creativity score.")
196
+ elif cosine_distance >= 0.5:
197
+ tips.append("🌟 Excellent creativity! You're successfully making me generate very different responses.")
198
+
199
+ # Token efficiency tips
200
+ if user_tokens >= 4:
201
+ tips.append("⚡ Challenge yourself: try achieving the same creative impact with fewer tokens!")
202
+ elif user_tokens == 1 and cosine_distance >= 0.2:
203
+ tips.append("🎯 Outstanding! You're a master of single-token creativity.")
204
+ elif user_tokens <= 2 and cosine_distance >= 0.3:
205
+ tips.append("🏆 Incredible efficiency - maximum creativity with minimal tokens!")
206
+
207
+ # Ranking tips
208
+ if rank and percentile:
209
+ if percentile >= 80:
210
+ tips.append("👑 You're in the top performers - keep up the amazing work!")
211
+ elif percentile >= 50:
212
+ tips.append("📈 You're above average! Push a bit further to join the top tier.")
213
+ else:
214
+ tips.append("🎯 Room to grow - try more unexpected word combinations!")
215
+
216
+ # Return a random tip if multiple apply, or a default encouragement
217
+ if tips:
218
+ return random.choice(tips)
219
+ else:
220
+ return "🎮 Great job playing! Try a new prompt to test different creative strategies."
src/scoring/statistics.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Statistics calculation and visualization functionality"""
2
+
3
+ import matplotlib.pyplot as plt
4
+ import numpy as np
5
+ from typing import List, Dict, Optional, Tuple
6
+
7
+
8
+ class StatisticsCalculator:
9
+ """Handles statistical calculations and visualization generation"""
10
+
11
+ def get_score_statistics(self, scores):
12
+ """Calculate mean and standard deviation of scores."""
13
+ if not scores:
14
+ return None, None
15
+ return np.mean(scores), np.std(scores)
16
+
17
+ def create_violin_plot(self, prompt_results, user_score, user_tokens):
18
+ """Create horizontal violin plots stacked vertically by token count."""
19
+ token_counts = [1, 2, 3, 4, 5]
20
+
21
+ # Filter to only include token counts that have data
22
+ token_data = []
23
+ for token_count in token_counts:
24
+ token_scores = [r["cosine_distance"] for r in prompt_results
25
+ if r["num_user_tokens"] == token_count]
26
+ if token_scores:
27
+ token_data.append((token_count, token_scores))
28
+
29
+ if not token_data:
30
+ fig, ax = plt.subplots(figsize=(10, 4))
31
+ ax.text(0.5, 0.5, 'No data available for visualization',
32
+ ha='center', va='center', transform=ax.transAxes, fontsize=14)
33
+ ax.set_title('Score Distribution by Token Count', fontsize=14, fontweight='bold')
34
+ return fig
35
+
36
+ # Create subplots - one for each token count
37
+ fig, axes = plt.subplots(len(token_data), 1, figsize=(10, 1 * len(token_data)),
38
+ sharex=True)
39
+
40
+ # Handle single subplot case
41
+ if len(token_data) == 1:
42
+ axes = [axes]
43
+
44
+ for i, (token_count, scores) in enumerate(token_data):
45
+ ax = axes[i]
46
+
47
+ # Create horizontal violin plot
48
+ parts = ax.violinplot([scores], positions=[0], vert=False,
49
+ showmeans=True, showextrema=True)
50
+
51
+ # Color based on whether this is user's token count - reuse existing gradient colors
52
+ color = '#667eea' if token_count == user_tokens else '#764ba2'
53
+ for pc in parts['bodies']:
54
+ pc.set_facecolor(color)
55
+ pc.set_alpha(0.7)
56
+ pc.set_edgecolor('black')
57
+ pc.set_linewidth(1)
58
+
59
+ # Highlight user's score if this is their token count
60
+ if token_count == user_tokens:
61
+ ax.scatter(user_score, 0, color='red', s=150, zorder=5,
62
+ marker='*', label=f'Your Score: {user_score:.3f}')
63
+ ax.legend(loc='upper right')
64
+
65
+ # Styling for each subplot
66
+ ax.set_ylabel(f'{token_count} token{"s" if token_count != 1 else ""}\n(n={len(scores)})',
67
+ fontsize=11, fontweight='bold')
68
+ ax.set_yticks([])
69
+ ax.grid(True, alpha=0.3, axis='x')
70
+ ax.spines['top'].set_visible(False)
71
+ ax.spines['right'].set_visible(False)
72
+ ax.spines['left'].set_visible(False)
73
+
74
+ # Set consistent y-limits for visual consistency
75
+ ax.set_ylim(-0.4, 0.4)
76
+
77
+ # Set common x-label only on bottom subplot
78
+ axes[-1].set_xlabel('Creativity Score (Cosine Distance)', fontsize=12)
79
+
80
+ # Overall title
81
+ fig.suptitle('Score Distribution by Token Count', fontsize=14, fontweight='bold', y=0.98)
82
+
83
+ plt.tight_layout()
84
+ plt.subplots_adjust(top=0.92)
85
+ return fig
86
+
87
+ def calculate_session_ranking_stats(self, session_results, data_manager, scorer):
88
+ """Calculate comprehensive ranking statistics for the session."""
89
+ all_results = data_manager.load_results_data()
90
+ ranking_stats = {
91
+ "best_rank": None,
92
+ "best_percentile": None,
93
+ "average_percentile": 0.0,
94
+ "total_ranked_attempts": 0,
95
+ "ranking_trend": "stable", # up, down, stable
96
+ "recent_percentiles": []
97
+ }
98
+
99
+ if not session_results:
100
+ return ranking_stats
101
+
102
+ percentiles = []
103
+ ranks = []
104
+
105
+ # Calculate rankings for each session attempt
106
+ for result in session_results:
107
+ # Get all results for this specific prompt
108
+ prompt_results = data_manager.filter_results_by_partial_response(
109
+ all_results, result["prompt"], result["llm_partial_response"]
110
+ )
111
+
112
+ if len(prompt_results) >= 2: # Need at least 2 results to rank
113
+ rank, percentile = scorer.calculate_rank_and_percentile(
114
+ result["cosine_distance"], prompt_results, result["num_user_tokens"]
115
+ )
116
+ if rank and percentile is not None:
117
+ percentiles.append(percentile)
118
+ ranks.append(rank)
119
+ ranking_stats["recent_percentiles"].append({
120
+ "percentile": percentile,
121
+ "rank": rank,
122
+ "total": len([r for r in prompt_results if r["num_user_tokens"] == result["num_user_tokens"]]),
123
+ "timestamp": result["timestamp"]
124
+ })
125
+
126
+ if percentiles:
127
+ ranking_stats["total_ranked_attempts"] = len(percentiles)
128
+ ranking_stats["average_percentile"] = sum(percentiles) / len(percentiles)
129
+ ranking_stats["best_percentile"] = max(percentiles)
130
+ ranking_stats["best_rank"] = min(ranks) if ranks else None
131
+
132
+ # Determine trend (compare first half vs second half)
133
+ if len(percentiles) >= 4:
134
+ mid_point = len(percentiles) // 2
135
+ first_half_avg = sum(percentiles[:mid_point]) / mid_point
136
+ second_half_avg = sum(percentiles[mid_point:]) / (len(percentiles) - mid_point)
137
+
138
+ if second_half_avg > first_half_avg + 10:
139
+ ranking_stats["ranking_trend"] = "up"
140
+ elif second_half_avg < first_half_avg - 10:
141
+ ranking_stats["ranking_trend"] = "down"
142
+
143
+ return ranking_stats
src/session/__init__.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ """Session management modules"""
2
+
3
+ from .session_manager import SessionManager
4
+
5
+ __all__ = ["SessionManager"]
src/session/session_manager.py ADDED
@@ -0,0 +1,316 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Session management and progress tracking functionality"""
2
+
3
+ import uuid
4
+ from typing import Dict, List, Optional
5
+
6
+
7
+ class SessionManager:
8
+ """Manages user session state and progress tracking"""
9
+
10
+ def __init__(self, data_manager=None, achievement_system=None):
11
+ self.session_id = str(uuid.uuid4())
12
+ self.data_manager = data_manager
13
+ self.achievement_system = achievement_system
14
+
15
+ def get_session_statistics(self):
16
+ """Get statistics for the current session."""
17
+ if not self.data_manager:
18
+ return self._get_empty_session_stats()
19
+
20
+ all_results = self.data_manager.load_results_data()
21
+ session_results = self.data_manager.filter_results_by_session(all_results, self.session_id)
22
+
23
+ if not session_results:
24
+ return self._get_empty_session_stats()
25
+
26
+ scores = [r["cosine_distance"] for r in session_results]
27
+ unique_prompts = set((r["prompt"], r["llm_partial_response"]) for r in session_results)
28
+
29
+ return {
30
+ "total_attempts": len(session_results),
31
+ "best_score": max(scores),
32
+ "average_score": sum(scores) / len(scores),
33
+ "total_tokens_used": sum(r["num_user_tokens"] for r in session_results),
34
+ "prompts_tried": len(unique_prompts),
35
+ "session_results": session_results
36
+ }
37
+
38
+ def _get_empty_session_stats(self):
39
+ """Return empty session statistics structure"""
40
+ return {
41
+ "total_attempts": 0,
42
+ "best_score": 0.0,
43
+ "average_score": 0.0,
44
+ "total_tokens_used": 0,
45
+ "prompts_tried": 0
46
+ }
47
+
48
+ def generate_session_progress_html(self, template_renderer):
49
+ """Generate HTML for session progress display."""
50
+ stats = self.get_session_statistics()
51
+
52
+ if stats["total_attempts"] == 0:
53
+ return template_renderer.load_template("session-progress-empty.html")
54
+
55
+ # Create achievement summary for session
56
+ session_achievements = set()
57
+ if self.achievement_system and "session_results" in stats:
58
+ for result in stats["session_results"]:
59
+ achievements = self.achievement_system.determine_achievement_titles(
60
+ result["cosine_distance"],
61
+ result["num_user_tokens"]
62
+ )
63
+ session_achievements.update(achievements)
64
+
65
+ achievement_badges = ""
66
+ if session_achievements:
67
+ badges_html = ""
68
+ for achievement in list(session_achievements)[:5]: # Show top 5
69
+ badges_html += template_renderer.load_template("achievement-badge.html", achievement=achievement)
70
+
71
+ achievement_badges = template_renderer.load_template("session-achievements.html",
72
+ achievement_badges=badges_html)
73
+
74
+ # Recent attempts preview
75
+ recent_attempts = sorted(stats["session_results"],
76
+ key=lambda x: x["timestamp"],
77
+ reverse=True)[:3]
78
+
79
+ attempts_html = ""
80
+ for i, attempt in enumerate(recent_attempts, 1):
81
+ score_color = "#4CAF50" if attempt["cosine_distance"] >= 0.3 else "#FF9800" if attempt["cosine_distance"] >= 0.15 else "#9E9E9E"
82
+ user_input = attempt['user_continuation'][:30] + ('...' if len(attempt['user_continuation']) > 30 else '')
83
+ plural = "s" if attempt["num_user_tokens"] != 1 else ""
84
+ attempt_number = len(stats["session_results"]) - i + 1
85
+
86
+ attempts_html += template_renderer.load_template("attempt-item.html",
87
+ attempt_number=attempt_number,
88
+ user_input=user_input,
89
+ score_color=score_color,
90
+ cosine_distance=attempt["cosine_distance"],
91
+ num_tokens=attempt["num_user_tokens"],
92
+ plural=plural
93
+ )
94
+
95
+ recent_html = template_renderer.load_template("recent-attempts.html", attempts=attempts_html)
96
+
97
+ return template_renderer.load_template("session-progress-main.html",
98
+ total_attempts=stats['total_attempts'],
99
+ best_score=stats['best_score'],
100
+ average_score=stats['average_score'],
101
+ prompts_tried=stats['prompts_tried'],
102
+ achievement_badges=achievement_badges,
103
+ recent_html=recent_html
104
+ )
105
+
106
+ def generate_session_page_html(self, template_renderer, statistics_calculator=None, scorer=None):
107
+ """Generate comprehensive session page HTML with ranking information."""
108
+ stats = self.get_session_statistics()
109
+
110
+ if stats["total_attempts"] == 0:
111
+ empty_html = template_renderer.load_template("session-page-empty.html")
112
+ return empty_html, "", ""
113
+
114
+ # Calculate ranking statistics
115
+ ranking_stats = {}
116
+ if statistics_calculator and scorer and "session_results" in stats:
117
+ ranking_stats = statistics_calculator.calculate_session_ranking_stats(
118
+ stats["session_results"], self.data_manager, scorer
119
+ )
120
+
121
+ # Main stats overview
122
+ best_attempt = max(stats["session_results"], key=lambda x: x["cosine_distance"])
123
+ recent_attempts = sorted(stats["session_results"], key=lambda x: x["timestamp"], reverse=True)[:5]
124
+
125
+ # Determine trend icon and message
126
+ trend_icon = {"up": "📈", "down": "📉", "stable": "📊"}.get(ranking_stats.get("ranking_trend", "stable"), "📊")
127
+ trend_message = {
128
+ "up": "Improving!",
129
+ "down": "Room to grow",
130
+ "stable": "Consistent"
131
+ }.get(ranking_stats.get("ranking_trend", "stable"), "Consistent")
132
+
133
+ main_stats_html = f"""
134
+ <div class="session-progress">
135
+ <div class="session-stats">
136
+ <div class="stat-item">
137
+ <div class="stat-value">{stats['total_attempts']}</div>
138
+ <div class="stat-label">Creative Attempts</div>
139
+ </div>
140
+ <div class="stat-item">
141
+ <div class="stat-value">{stats['best_score']:.3f}</div>
142
+ <div class="stat-label">Personal Best</div>
143
+ </div>
144
+ <div class="stat-item">
145
+ <div class="stat-value">{stats['average_score']:.3f}</div>
146
+ <div class="stat-label">Average Score</div>
147
+ </div>"""
148
+
149
+ # Add ranking stats if available
150
+ if ranking_stats.get("total_ranked_attempts", 0) > 0:
151
+ main_stats_html += f"""
152
+ <div class="stat-item ranking-highlight">
153
+ <div class="stat-value">#{ranking_stats['best_rank']}</div>
154
+ <div class="stat-label">Best Rank</div>
155
+ </div>
156
+ <div class="stat-item ranking-highlight">
157
+ <div class="stat-value">{ranking_stats['average_percentile']:.0f}%</div>
158
+ <div class="stat-label">Avg vs Others</div>
159
+ </div>"""
160
+ else:
161
+ main_stats_html += f"""
162
+ <div class="stat-item">
163
+ <div class="stat-value">{stats['prompts_tried']}</div>
164
+ <div class="stat-label">Prompts Explored</div>
165
+ </div>"""
166
+
167
+ main_stats_html += f"""
168
+ </div>
169
+
170
+ <div class="session-overview">
171
+ <div class="best-response-showcase">
172
+ <h4>🎯 Your Best Response</h4>
173
+ <div class="best-response-content">
174
+ <span class="best-input">"{best_attempt['user_continuation']}"</span>
175
+ <div class="best-metrics">
176
+ <span class="score-badge">Score: {best_attempt['cosine_distance']:.3f}</span>"""
177
+
178
+ # Add ranking info for best attempt if available
179
+ if ranking_stats.get("total_ranked_attempts", 0) > 0 and "recent_percentiles" in ranking_stats:
180
+ best_ranking = next((r for r in ranking_stats["recent_percentiles"]
181
+ if abs(float(r["timestamp"].replace('-', '').replace(':', '').replace('T', '').replace('.', '')[:14]) -
182
+ float(best_attempt["timestamp"].replace('-', '').replace(':', '').replace('T', '').replace('.', '')[:14])) < 1000), None)
183
+ if best_ranking:
184
+ main_stats_html += f"""
185
+ <span class="rank-badge">#{best_ranking['rank']} of {best_ranking['total']}</span>"""
186
+
187
+ main_stats_html += f"""
188
+ </div>
189
+ </div>
190
+ </div>"""
191
+
192
+ # Add trend information if sufficient data
193
+ if ranking_stats.get("total_ranked_attempts", 0) >= 2:
194
+ main_stats_html += f"""
195
+ <div class="performance-trend">
196
+ {trend_icon} <strong>Ranking Trend:</strong> {trend_message}
197
+ <span class="trend-detail">({ranking_stats['total_ranked_attempts']} ranked attempts)</span>
198
+ </div>"""
199
+
200
+ main_stats_html += f"""
201
+ </div>
202
+ </div>
203
+ """
204
+
205
+ # Enhanced detailed history with ranking
206
+ history_html = """
207
+ <div class="session-progress">
208
+ <div class="recent-attempts">
209
+ """
210
+
211
+ for i, attempt in enumerate(recent_attempts, 1):
212
+ score_color = "#4CAF50" if attempt["cosine_distance"] >= 0.3 else "#FF9800" if attempt["cosine_distance"] >= 0.15 else "#9E9E9E"
213
+ # Truncate long inputs for display
214
+ display_input = attempt['user_continuation'][:50] + ('...' if len(attempt['user_continuation']) > 50 else '')
215
+
216
+ # Find ranking info for this attempt
217
+ ranking_info = None
218
+ if "recent_percentiles" in ranking_stats:
219
+ ranking_info = next((r for r in ranking_stats["recent_percentiles"]
220
+ if r["timestamp"] == attempt["timestamp"]), None)
221
+
222
+ history_html += f"""
223
+ <div class="attempt-item enhanced">
224
+ <span class="attempt-number">#{len(stats["session_results"]) - i + 1}</span>
225
+ <div class="attempt-content">
226
+ <div class="attempt-input">"{display_input}"</div>
227
+ <div class="attempt-details">
228
+ <span class="timestamp">{attempt['timestamp'][:10]} {attempt['timestamp'][11:16]}</span>"""
229
+
230
+ if ranking_info:
231
+ # Add percentile indicator
232
+ percentile = ranking_info["percentile"]
233
+ percentile_color = "#4CAF50" if percentile >= 75 else "#FF9800" if percentile >= 50 else "#9E9E9E"
234
+ percentile_icon = "🏆" if percentile >= 90 else "⭐" if percentile >= 75 else "📈" if percentile >= 50 else "🎯"
235
+
236
+ history_html += f"""
237
+ <span class="ranking-info" style="color: {percentile_color};">
238
+ {percentile_icon} #{ranking_info['rank']} of {ranking_info['total']} ({percentile:.0f}%)
239
+ </span>"""
240
+ else:
241
+ history_html += f"""
242
+ <span class="ranking-info" style="opacity: 0.6;">
243
+ 🌱 First attempt at this prompt+token combo
244
+ </span>"""
245
+
246
+ history_html += f"""
247
+ </div>
248
+ </div>
249
+ <div class="attempt-metrics">
250
+ <span class="attempt-score" style="color: {score_color};">
251
+ {attempt["cosine_distance"]:.3f}
252
+ </span>
253
+ <span class="token-count">{attempt["num_user_tokens"]} tokens</span>
254
+ </div>
255
+ </div>
256
+ """
257
+
258
+ if len(recent_attempts) < len(stats["session_results"]):
259
+ remaining = len(stats["session_results"]) - len(recent_attempts)
260
+ history_html += f"""
261
+ <div class="attempt-item" style="opacity: 0.6;">
262
+ <span class="attempt-number">...</span>
263
+ <div class="attempt-content">
264
+ <div class="attempt-input">And {remaining} more creative attempts!</div>
265
+ <div class="attempt-details">
266
+ <span class="ranking-info">📊 View all attempts for complete ranking history</span>
267
+ </div>
268
+ </div>
269
+ <div class="attempt-metrics">
270
+ <span class="attempt-score">📈</span>
271
+ </div>
272
+ </div>
273
+ """
274
+
275
+ history_html += """
276
+ </div>
277
+ </div>
278
+ """
279
+
280
+ # Achievements
281
+ all_achievements = set()
282
+ if self.achievement_system and "session_results" in stats:
283
+ for result in stats["session_results"]:
284
+ achievements = self.achievement_system.determine_achievement_titles(
285
+ result["cosine_distance"],
286
+ result["num_user_tokens"]
287
+ )
288
+ all_achievements.update(achievements)
289
+
290
+ achievements_html = """
291
+ <div class="session-progress">
292
+ """
293
+
294
+ if all_achievements:
295
+ achievements_html += """
296
+ <div class="session-achievements">
297
+ <div class="achievement-badges">
298
+ """
299
+ for achievement in sorted(all_achievements):
300
+ achievements_html += f'<div class="achievement-badge">{achievement}</div>'
301
+ achievements_html += """
302
+ </div>
303
+ </div>
304
+ """
305
+ else:
306
+ achievements_html += """
307
+ <div class="session-tip">
308
+ 🏆 Keep being creative to unlock achievements! Try different token counts and creative approaches.
309
+ </div>
310
+ """
311
+
312
+ achievements_html += """
313
+ </div>
314
+ """
315
+
316
+ return main_stats_html, history_html, achievements_html
src/ui/__init__.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ """User interface modules"""
2
+
3
+ from .template_renderer import TemplateRenderer
4
+ from .page_handlers import PageHandlers
5
+ from .interface_builder import InterfaceBuilder
6
+
7
+ __all__ = ["TemplateRenderer", "PageHandlers", "InterfaceBuilder"]
src/ui/interface_builder.py ADDED
@@ -0,0 +1,550 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Main Gradio interface builder"""
2
+
3
+ import gradio as gr
4
+ from ..config.settings import CSS_PATHS
5
+
6
+
7
+ class InterfaceBuilder:
8
+ """Handles creation of the main Gradio interface"""
9
+
10
+ def __init__(self, app, page_handlers):
11
+ self.app = app
12
+ self.page_handlers = page_handlers
13
+
14
+ def create_interface(self):
15
+ with gr.Blocks(title="Help the LLM be Creative!", css_paths=CSS_PATHS) as demo:
16
+
17
+ # Navigation state management
18
+ current_page = gr.State("welcome")
19
+ has_results = gr.State(False)
20
+
21
+ # Navigation bar
22
+ nav_container = gr.Column(elem_classes=["nav-container"])
23
+ with nav_container:
24
+ with gr.Row():
25
+ nav_welcome = gr.Button("🏠 Welcome", elem_classes=["nav-button", "active"])
26
+ nav_tutorial = gr.Button("🎯 Tutorial", elem_classes=["nav-button"])
27
+ nav_creative = gr.Button("✨ Be Creative", elem_classes=["nav-button"])
28
+ nav_gallery = gr.Button("🏆 Gallery", elem_classes=["nav-button"])
29
+ nav_results = gr.Button("🎉 Results", elem_classes=["nav-button"], visible=False)
30
+ nav_session = gr.Button("📊 Progress", elem_classes=["nav-button"], visible=False)
31
+
32
+ # Landing page
33
+ landing_page = gr.Column(visible=True)
34
+ with landing_page:
35
+ with gr.Column(elem_classes=["landing-page"]):
36
+ gr.Markdown("# 🤖 I'm a poor, boring language model...😢", elem_classes=["landing-title"])
37
+ gr.Markdown("# Please help me be creative!", elem_classes=["landing-subtitle"])
38
+
39
+ with gr.Row():
40
+ with gr.Column(scale=1):
41
+ tutorial_btn = gr.Button("🎯 Take a Quick Tour", elem_classes=["primary-button"])
42
+ with gr.Column(scale=1):
43
+ skip_to_game_btn = gr.Button("🚀 Let's Be Creative Together!", elem_classes=["secondary-button"])
44
+
45
+ with gr.Row():
46
+ with gr.Column(scale=1):
47
+ gallery_btn = gr.Button("🏆 View Creative Gallery", elem_classes=["secondary-button"])
48
+
49
+ # Tutorial page
50
+ tutorial_page = gr.Column(visible=False)
51
+ with tutorial_page:
52
+ # Tutorial header
53
+ with gr.Column(elem_classes=["tutorial-container"]):
54
+ with gr.Column(elem_classes=["tutorial-header"]):
55
+ gr.Markdown("# 🎯 Quick Tour", elem_classes=["tutorial-title"])
56
+ gr.Markdown("## Here's how you can help me be more creative", elem_classes=["tutorial-subtitle"])
57
+
58
+ # Current step explanation
59
+ tutorial_step_explanation = gr.Markdown("", elem_classes=["tutorial-step-explanation"])
60
+
61
+ # Tutorial interface components (identical to game interface)
62
+ tutorial_prompt_display = gr.Textbox(
63
+ label="🎯 Here's what I was asked to write about:",
64
+ lines=3,
65
+ interactive=False,
66
+ elem_classes=["prompt-box"],
67
+ visible=False
68
+ )
69
+
70
+ tutorial_partial_response_display = gr.Textbox(
71
+ label="🤖 Here's my boring start... (help me make it better!)",
72
+ lines=5,
73
+ interactive=False,
74
+ elem_classes=["assistant-box"],
75
+ visible=False
76
+ )
77
+
78
+ tutorial_input_section = gr.Column(visible=False)
79
+ with tutorial_input_section:
80
+ with gr.Row():
81
+ with gr.Column(scale=4):
82
+ tutorial_user_input = gr.Textbox(
83
+ label="✨ Add your creative input here (max 5 tokens)",
84
+ placeholder="Type a few words to inspire me...",
85
+ lines=2,
86
+ elem_classes=["user-box"],
87
+ interactive=False
88
+ )
89
+ with gr.Column(scale=1, min_width=150):
90
+ tutorial_token_counter = gr.Markdown(
91
+ "**Tokens:** 0/5",
92
+ elem_classes=["token-counter"]
93
+ )
94
+
95
+ tutorial_submit_btn = gr.Button("✨ Help Me Be Creative!", variant="primary", interactive=False)
96
+
97
+ # Tutorial results section
98
+ tutorial_results_section = gr.Column(visible=False)
99
+ with tutorial_results_section:
100
+ tutorial_score_display = gr.HTML()
101
+
102
+ gr.Markdown("## 🔄 See The Difference!")
103
+ with gr.Row(equal_height=True):
104
+ with gr.Column(scale=1):
105
+ gr.Markdown("### 🤖 My Original Boring Version")
106
+ tutorial_original_response = gr.Textbox(
107
+ label="What I wrote originally (so boring! 😴)",
108
+ lines=6,
109
+ interactive=False,
110
+ elem_classes=["original-response"]
111
+ )
112
+ with gr.Column(scale=1):
113
+ gr.Markdown("### ✨ Your Creative Version")
114
+ tutorial_creative_response = gr.Textbox(
115
+ label="What I wrote after your creative input! 🎨",
116
+ lines=6,
117
+ interactive=False,
118
+ elem_classes=["results-box"]
119
+ )
120
+
121
+ # Tutorial navigation
122
+ with gr.Column(elem_classes=["tutorial-navigation"]):
123
+ with gr.Row():
124
+ tutorial_prev_btn = gr.Button("⬅️ Previous", elem_classes=["tutorial-button", "secondary"], visible=False)
125
+ tutorial_next_btn = gr.Button("Next ➡️", elem_classes=["tutorial-button"])
126
+ tutorial_play_btn = gr.Button("🚀 Let's Play!", elem_classes=["tutorial-button"], visible=False)
127
+ tutorial_back_btn = gr.Button("⬅️ Back to Start", elem_classes=["tutorial-button", "secondary"])
128
+
129
+ # Gallery page
130
+ gallery_page = gr.Column(visible=False)
131
+ with gallery_page:
132
+ with gr.Column(elem_classes=["gallery-container"]):
133
+ gr.Markdown("# 🏆 Creative Response Gallery", elem_classes=["tutorial-title"])
134
+ gr.Markdown("### Showcasing the most creative responses from our community!", elem_classes=["tutorial-subtitle"])
135
+
136
+ gallery_filter = gr.Dropdown(
137
+ choices=[("All Creative (≥0.15)", 0.15), ("Great (≥0.3)", 0.3), ("Exceptional (≥0.5)", 0.5), ("Legendary (≥0.7)", 0.7)],
138
+ value=0.3,
139
+ label="🎯 Filter by creativity level:",
140
+ elem_classes=["prompt-box"]
141
+ )
142
+
143
+ gallery_display = gr.HTML()
144
+
145
+ with gr.Row():
146
+ gallery_refresh_btn = gr.Button("🔄 Refresh Gallery", elem_classes=["tutorial-button"])
147
+ gallery_back_btn = gr.Button("⬅️ Back to Start", elem_classes=["tutorial-button", "secondary"])
148
+
149
+ # Creative page components (initially hidden)
150
+ creative_page = gr.Column(visible=False)
151
+ with creative_page:
152
+ prompt_display = gr.Textbox(
153
+ label="🎯 Here's what I was asked to write about:",
154
+ lines=3,
155
+ interactive=False,
156
+ elem_classes=["prompt-box"]
157
+ )
158
+
159
+ partial_response_display = gr.Textbox(
160
+ label="🤖 Here's my boring start... (help me make it better!)",
161
+ lines=5,
162
+ interactive=False,
163
+ elem_classes=["assistant-box"]
164
+ )
165
+
166
+ user_input = gr.Textbox(
167
+ label="✨ Add your creative input here (max 5 tokens)",
168
+ placeholder="Type a few words to inspire me...",
169
+ lines=1, # changed to single line so that "enter" submits the request
170
+ elem_classes=["user-box"]
171
+ )
172
+
173
+ token_counter = gr.Markdown(
174
+ "**Tokens:** 0/5",
175
+ elem_classes=["token-counter"]
176
+ )
177
+
178
+ token_visualization = gr.Markdown(
179
+ "Once you start typing, I'll show you what you've written, but in tokens!",
180
+ elem_classes=["token-visualization"]
181
+ )
182
+
183
+ submit_btn = gr.Button("✨ Help Me Be Creative!", variant="primary", size="lg")
184
+
185
+ # Loading and error messages (positioned right after submit button)
186
+ loading_message = gr.Textbox(visible=False, interactive=False, label="🤖 I'm thinking...", value="Working on your creative response... This is exciting!")
187
+ error_message = gr.Textbox(visible=False, interactive=False, label="🙋 Oops!", elem_classes=["error-box"])
188
+
189
+ # Clear section divider
190
+ gr.HTML("<hr style='margin: 40px 0; border: none; height: 2px; background: linear-gradient(to right, transparent, #667eea, transparent); opacity: 0.3;'>")
191
+
192
+ # Help and inspiration section
193
+ gr.Markdown("### 📚 Need Help or Inspiration?", elem_classes=["section-header"])
194
+
195
+ # Inspire Me section - collapsible
196
+ gr.Markdown("✨ **Need some inspiration?** See how others have creatively continued this same prompt!", elem_classes=["token-explanation"])
197
+ with gr.Accordion("💡 Inspire Me! Show creative examples from others", open=False):
198
+ inspire_me_btn = gr.Button("🎲 Show Different Examples", elem_classes=["inspire-me-button"])
199
+ inspire_me_display = gr.HTML()
200
+
201
+ gr.Markdown(
202
+ "💡 **About tokens:** Your input is measured in \"tokens\" (how I process text). About 5 tokens ≈ 3-5 words. You'll see your exact count above as you type!",
203
+ elem_classes=["token-explanation"]
204
+ )
205
+
206
+ # Session progress page
207
+ session_page = gr.Column(visible=False)
208
+ with session_page:
209
+ gr.HTML("""
210
+ <div class="results-header">
211
+ <h1>📊 Your Creative Journey</h1>
212
+ <p>Track your progress, achievements, and creative evolution!</p>
213
+ </div>
214
+ """)
215
+
216
+ # Main session stats display
217
+ session_main_stats = gr.HTML()
218
+
219
+ # Detailed session history
220
+ gr.Markdown("## 📈 Your Creative History", elem_classes=["results-section-header"])
221
+ session_history_display = gr.HTML()
222
+
223
+ # Session achievements
224
+ gr.Markdown("## 🏆 Your Achievements", elem_classes=["results-section-header"])
225
+ session_achievements_display = gr.HTML()
226
+
227
+ # Back to creative action
228
+ with gr.Column(elem_classes=["action-section"]):
229
+ gr.Markdown("### Ready to add to your journey?")
230
+ back_to_creative_btn = gr.Button("✨ Continue Being Creative!", elem_classes=["primary-button"])
231
+
232
+ # Results page components
233
+ results_page = gr.Column(visible=False)
234
+ with results_page:
235
+ cosine_distance_display = gr.HTML(visible=False)
236
+
237
+ results_prompt_display = gr.Textbox(
238
+ label="🎯 Here's what I was asked to write about:",
239
+ lines=2,
240
+ interactive=False,
241
+ elem_classes=["prompt-box"]
242
+ )
243
+
244
+ results_partial_display = gr.Textbox(
245
+ label="🤖 My Boring Start",
246
+ lines=3,
247
+ interactive=False,
248
+ elem_classes=["assistant-box"]
249
+ )
250
+
251
+ results_user_input_display = gr.Textbox(
252
+ label="✨ Your Creative Input",
253
+ lines=2,
254
+ interactive=False,
255
+ elem_classes=["user-box"]
256
+ )
257
+
258
+ # Side-by-side comparison
259
+ gr.Markdown("## 🔄 See The Difference!")
260
+ with gr.Row(equal_height=True):
261
+ with gr.Column(scale=1):
262
+ gr.Markdown("### 🤖 My Original Boring Version")
263
+ original_response_display = gr.Textbox(
264
+ label="What I wrote originally (so boring! 😴)",
265
+ lines=8,
266
+ interactive=False,
267
+ elem_classes=["original-response"]
268
+ )
269
+ with gr.Column(scale=1):
270
+ gr.Markdown("### ✨ Your Creative Version")
271
+ results_continuation_display = gr.Textbox(
272
+ label="What I wrote after your creative input! 🎨",
273
+ lines=8,
274
+ interactive=False,
275
+ elem_classes=["results-box"]
276
+ )
277
+
278
+ # Visualization
279
+ gr.Markdown("## 📊 How does your help compare with others?")
280
+
281
+ violin_plot_display = gr.Plot(
282
+ label="Score Distribution by Token Count",
283
+ visible=False
284
+ )
285
+
286
+ # Collapsible explanation of cosine similarity
287
+ with gr.Accordion("🤔 How does the scoring work? (Click to learn!)", open=False):
288
+ gr.Markdown("""
289
+ ## The Magic Behind Your Creativity Score! ✨
290
+
291
+ I use something called **"Cosine Distance"** to see how different your version is from my boring original!
292
+
293
+ **Think of it like this:** 🎯
294
+ - I convert both responses into "meaning fingerprints"
295
+ - Then I measure how far apart these fingerprints are
296
+ - **Farther apart = More creative = Higher score!** 🏆
297
+
298
+ **Score Guide:**
299
+ - **0.7+** = 🌟 WOW! You completely changed my direction!
300
+ - **0.5+** = 🔥 Amazing! Very different from my original
301
+ - **0.3+** = ✨ Great job! Nice creative twist
302
+ - **0.15+** = 💡 Good start! I can see the difference
303
+ - **Below 0.15** = 🤔 Pretty similar to what I would have written anyway
304
+
305
+ **Pro tip:** The most unexpected words often lead to the highest scores! 🎪
306
+ """)
307
+
308
+ with gr.Row():
309
+ try_again_same_btn = gr.Button("🔄 Let Me Try Again!", elem_classes=["try-again-button"])
310
+ try_again_new_btn = gr.Button("🎯 Give Me A New Challenge!", elem_classes=["try-again-button"])
311
+
312
+ # Tutorial state
313
+ tutorial_current_step = gr.State(1)
314
+
315
+ # Wire up all the event handlers
316
+ self._wire_event_handlers(
317
+ # Navigation components
318
+ nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session,
319
+ # Landing page components
320
+ tutorial_btn, skip_to_game_btn, gallery_btn,
321
+ # Tutorial components
322
+ tutorial_next_btn, tutorial_prev_btn, tutorial_play_btn, tutorial_back_btn,
323
+ # Gallery components
324
+ gallery_filter, gallery_refresh_btn, gallery_back_btn,
325
+ # Creative page components
326
+ user_input, submit_btn, inspire_me_btn,
327
+ # Results page components
328
+ try_again_same_btn, try_again_new_btn,
329
+ # Session page components
330
+ back_to_creative_btn,
331
+ # State components
332
+ current_page, has_results, tutorial_current_step,
333
+ # Display components
334
+ landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page,
335
+ prompt_display, partial_response_display, token_counter, token_visualization,
336
+ loading_message, error_message, inspire_me_display, gallery_display,
337
+ cosine_distance_display, results_prompt_display, results_partial_display,
338
+ results_user_input_display, original_response_display, results_continuation_display,
339
+ violin_plot_display, session_main_stats, session_history_display, session_achievements_display,
340
+ # Tutorial display components
341
+ tutorial_step_explanation, tutorial_prompt_display, tutorial_partial_response_display,
342
+ tutorial_input_section, tutorial_results_section, tutorial_user_input, tutorial_token_counter,
343
+ tutorial_score_display, tutorial_original_response, tutorial_creative_response,
344
+ tutorial_prev_btn, tutorial_next_btn, tutorial_play_btn
345
+ )
346
+
347
+ # Demo starts with landing page visible
348
+
349
+ return demo
350
+
351
+ def _wire_event_handlers(self, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session,
352
+ tutorial_btn, skip_to_game_btn, gallery_btn, tutorial_next_btn, tutorial_prev_btn,
353
+ tutorial_play_btn, tutorial_back_btn, gallery_filter, gallery_refresh_btn, gallery_back_btn,
354
+ user_input, submit_btn, inspire_me_btn, try_again_same_btn, try_again_new_btn,
355
+ back_to_creative_btn, current_page, has_results, tutorial_current_step,
356
+ landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page,
357
+ prompt_display, partial_response_display, token_counter, token_visualization,
358
+ loading_message, error_message, inspire_me_display, gallery_display,
359
+ cosine_distance_display, results_prompt_display, results_partial_display,
360
+ results_user_input_display, original_response_display, results_continuation_display,
361
+ violin_plot_display, session_main_stats, session_history_display, session_achievements_display,
362
+ tutorial_step_explanation, tutorial_prompt_display, tutorial_partial_response_display,
363
+ tutorial_input_section, tutorial_results_section, tutorial_user_input, tutorial_token_counter,
364
+ tutorial_score_display, tutorial_original_response, tutorial_creative_response,
365
+ tutorial_prev_btn_ref, tutorial_next_btn_ref, tutorial_play_btn_ref):
366
+ """Wire up all the event handlers for the interface"""
367
+
368
+ # Wire up landing page buttons
369
+ tutorial_btn.click(
370
+ self.page_handlers.show_tutorial,
371
+ outputs=[
372
+ current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session,
373
+ tutorial_step_explanation, tutorial_prompt_display, tutorial_partial_response_display,
374
+ tutorial_input_section, tutorial_results_section,
375
+ tutorial_prev_btn_ref, tutorial_next_btn_ref, tutorial_play_btn_ref,
376
+ tutorial_current_step
377
+ ]
378
+ )
379
+
380
+ skip_to_game_btn.click(
381
+ self.page_handlers.start_game,
382
+ outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session, prompt_display, partial_response_display, inspire_me_display]
383
+ )
384
+
385
+ # Wire up gallery button (handled at end of function)
386
+
387
+ # Wire up gallery page buttons
388
+ gallery_back_btn.click(
389
+ self.page_handlers.back_to_landing,
390
+ outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session, gallery_display]
391
+ )
392
+
393
+ gallery_refresh_btn.click(
394
+ self.page_handlers.show_gallery,
395
+ inputs=[gallery_filter],
396
+ outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session, gallery_display]
397
+ )
398
+
399
+ gallery_filter.change(
400
+ self.page_handlers.show_gallery,
401
+ inputs=[gallery_filter],
402
+ outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session, gallery_display]
403
+ )
404
+
405
+ # Wire up tutorial page buttons
406
+ tutorial_back_btn.click(
407
+ lambda: self.page_handlers.switch_to_page("welcome"),
408
+ outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session],
409
+ js="() => { window.scrollTo({ top: 0, behavior: 'smooth' }); }"
410
+ )
411
+
412
+ tutorial_play_btn.click(
413
+ self.page_handlers.load_creative_with_prompt,
414
+ outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session, prompt_display, partial_response_display, inspire_me_display],
415
+ js="() => { window.scrollTo({ top: 0, behavior: 'smooth' }); }"
416
+ )
417
+
418
+ # Wire up tutorial navigation
419
+ tutorial_next_btn.click(
420
+ self.page_handlers.tutorial_next_step,
421
+ inputs=[tutorial_current_step],
422
+ outputs=[
423
+ tutorial_step_explanation, tutorial_prompt_display, tutorial_partial_response_display,
424
+ tutorial_input_section, tutorial_user_input, tutorial_token_counter,
425
+ tutorial_results_section, tutorial_original_response, tutorial_creative_response, tutorial_score_display,
426
+ tutorial_prev_btn_ref, tutorial_next_btn_ref, tutorial_play_btn_ref,
427
+ tutorial_current_step
428
+ ]
429
+ )
430
+
431
+ tutorial_prev_btn.click(
432
+ self.page_handlers.tutorial_prev_step,
433
+ inputs=[tutorial_current_step],
434
+ outputs=[
435
+ tutorial_step_explanation, tutorial_prompt_display, tutorial_partial_response_display,
436
+ tutorial_input_section, tutorial_results_section,
437
+ tutorial_prev_btn_ref, tutorial_next_btn_ref, tutorial_play_btn_ref,
438
+ tutorial_current_step
439
+ ]
440
+ )
441
+
442
+ submit_btn.click(
443
+ self.page_handlers.on_submit_start,
444
+ inputs=[user_input],
445
+ outputs=[submit_btn, loading_message, error_message, landing_page, gallery_page, creative_page, results_page, cosine_distance_display]
446
+ ).then(
447
+ self.page_handlers.on_submit_process,
448
+ inputs=[user_input],
449
+ outputs=[submit_btn, loading_message, error_message, landing_page, gallery_page, creative_page, results_page, cosine_distance_display, results_prompt_display, results_partial_display, results_user_input_display, original_response_display, results_continuation_display, violin_plot_display, current_page, has_results, nav_results, nav_session, nav_welcome, nav_tutorial, nav_creative, nav_gallery]
450
+ )
451
+
452
+ user_input.submit(
453
+ self.page_handlers.on_submit_start,
454
+ inputs=[user_input],
455
+ outputs=[submit_btn, loading_message, error_message, landing_page, gallery_page, creative_page, results_page, cosine_distance_display]
456
+ ).then(
457
+ self.page_handlers.on_submit_process,
458
+ inputs=[user_input],
459
+ outputs=[submit_btn, loading_message, error_message, landing_page, gallery_page, creative_page, results_page, cosine_distance_display, results_prompt_display, results_partial_display, results_user_input_display, original_response_display, results_continuation_display, violin_plot_display, current_page, has_results, nav_results, nav_session, nav_welcome, nav_tutorial, nav_creative, nav_gallery]
460
+ )
461
+
462
+ # Try again button - stay on same prompt, switch to creative page
463
+ try_again_same_btn.click(
464
+ self.page_handlers.try_same_prompt,
465
+ outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session, submit_btn, user_input, loading_message, error_message, token_counter, token_visualization],
466
+ js="() => { window.scrollTo({ top: 0, behavior: 'smooth' }); }"
467
+ )
468
+
469
+ # Try new prompt button - new prompt and switch to creative page
470
+ try_again_new_btn.click(
471
+ self.page_handlers.try_new_prompt,
472
+ outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session, prompt_display, partial_response_display, submit_btn, user_input, loading_message, error_message, inspire_me_display],
473
+ js="() => { window.scrollTo({ top: 0, behavior: 'smooth' }); }"
474
+ )
475
+
476
+ # Wire up inspire me button
477
+ inspire_me_btn.click(
478
+ self.page_handlers.show_inspire_me_examples,
479
+ outputs=[inspire_me_display]
480
+ )
481
+
482
+ # Wire up real-time token counter
483
+ user_input.change(
484
+ self.page_handlers.update_token_count,
485
+ inputs=[user_input, partial_response_display],
486
+ outputs=[token_counter, token_visualization]
487
+ )
488
+
489
+ # Navigation button handlers
490
+ nav_welcome.click(
491
+ lambda: self.page_handlers.switch_to_page("welcome"),
492
+ outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session],
493
+ js="() => { window.scrollTo({ top: 0, behavior: 'smooth' }); }"
494
+ )
495
+
496
+ nav_tutorial.click(
497
+ self.page_handlers.load_tutorial_with_content,
498
+ outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session, tutorial_step_explanation, tutorial_prompt_display, tutorial_partial_response_display, tutorial_input_section, tutorial_results_section, tutorial_prev_btn_ref, tutorial_next_btn_ref, tutorial_play_btn_ref, tutorial_current_step],
499
+ js="() => { window.scrollTo({ top: 0, behavior: 'smooth' }); }"
500
+ )
501
+
502
+ nav_creative.click(
503
+ self.page_handlers.load_creative_with_prompt,
504
+ outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session, prompt_display, partial_response_display, inspire_me_display],
505
+ js="() => { window.scrollTo({ top: 0, behavior: 'smooth' }); }"
506
+ )
507
+
508
+ nav_gallery.click(
509
+ self.page_handlers.load_gallery_with_content,
510
+ outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session, gallery_display],
511
+ js="() => { window.scrollTo({ top: 0, behavior: 'smooth' }); }"
512
+ )
513
+
514
+ nav_results.click(
515
+ lambda: self.page_handlers.switch_to_page("results"),
516
+ outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session],
517
+ js="() => { window.scrollTo({ top: 0, behavior: 'smooth' }); }"
518
+ )
519
+
520
+ # Update session page when navigating to it
521
+ nav_session.click(
522
+ self.page_handlers.navigate_to_session,
523
+ outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session, session_main_stats, session_history_display, session_achievements_display],
524
+ js="() => { window.scrollTo({ top: 0, behavior: 'smooth' }); }"
525
+ )
526
+
527
+ # Update back to creative button in session page
528
+ back_to_creative_btn.click(
529
+ lambda: self.page_handlers.switch_to_page("creative"),
530
+ outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session],
531
+ js="() => { window.scrollTo({ top: 0, behavior: 'smooth' }); }"
532
+ )
533
+
534
+ # Connect welcome page buttons (duplicate wiring for multiple entry points)
535
+ skip_to_game_btn.click(
536
+ self.page_handlers.load_creative_with_prompt,
537
+ outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session, prompt_display, partial_response_display, inspire_me_display],
538
+ js="() => { window.scrollTo({ top: 0, behavior: 'smooth' }); }"
539
+ )
540
+
541
+ tutorial_btn.click(
542
+ self.page_handlers.load_tutorial_with_content,
543
+ outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session, tutorial_step_explanation, tutorial_prompt_display, tutorial_partial_response_display, tutorial_input_section, tutorial_results_section, tutorial_prev_btn_ref, tutorial_next_btn_ref, tutorial_play_btn_ref, tutorial_current_step],
544
+ js="() => { window.scrollTo({ top: 0, behavior: 'smooth' }); }"
545
+ )
546
+
547
+ gallery_btn.click(
548
+ self.page_handlers.load_gallery_with_content,
549
+ outputs=[current_page, landing_page, tutorial_page, creative_page, gallery_page, results_page, session_page, nav_welcome, nav_tutorial, nav_creative, nav_gallery, nav_results, nav_session, gallery_display]
550
+ )
src/ui/page_handlers.py ADDED
@@ -0,0 +1,501 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Page handling and navigation functionality"""
2
+
3
+ import gradio as gr
4
+ from ..config.settings import TUTORIAL_EXAMPLE
5
+
6
+
7
+ class PageHandlers:
8
+ """Handles page switching, navigation, and page-specific functionality"""
9
+
10
+ def __init__(self, app):
11
+ self.app = app
12
+
13
+ def switch_to_page(self, page_name, update_nav=True):
14
+ """Switch to a different page and update navigation if needed."""
15
+ page_updates = {
16
+ "welcome": (True, False, False, False, False, False),
17
+ "tutorial": (False, True, False, False, False, False),
18
+ "creative": (False, False, True, False, False, False),
19
+ "gallery": (False, False, False, True, False, False),
20
+ "results": (False, False, False, False, True, False),
21
+ "session": (False, False, False, False, False, True)
22
+ }
23
+
24
+ if page_name not in page_updates:
25
+ page_name = "welcome"
26
+
27
+ visibility = page_updates[page_name]
28
+
29
+ nav_updates = []
30
+ if update_nav:
31
+ # Update navigation button styles
32
+ nav_buttons = ["welcome", "tutorial", "creative", "gallery", "results", "session"]
33
+ for nav_page in nav_buttons:
34
+ if nav_page == page_name:
35
+ nav_updates.append(gr.update(elem_classes=["nav-button", "active"]))
36
+ else:
37
+ nav_updates.append(gr.update(elem_classes=["nav-button"]))
38
+ else:
39
+ nav_updates = [gr.update()] * 6
40
+
41
+ return (
42
+ page_name, # current_page state
43
+ gr.update(visible=visibility[0]), # landing_page
44
+ gr.update(visible=visibility[1]), # tutorial_page
45
+ gr.update(visible=visibility[2]), # creative_page
46
+ gr.update(visible=visibility[3]), # gallery_page
47
+ gr.update(visible=visibility[4]), # results_page
48
+ gr.update(visible=visibility[5]), # session_page
49
+ *nav_updates # nav button updates
50
+ )
51
+
52
+ def show_gallery(self, min_score=0.3):
53
+ """Show gallery with basic UI updates"""
54
+ gallery_html = self.generate_gallery_html(min_score)
55
+ switch_result = self.switch_to_page("gallery", update_nav=False)
56
+ return switch_result + (gallery_html,)
57
+
58
+ def generate_gallery_html(self, min_score=0.3):
59
+ """Generate gallery HTML content"""
60
+ gallery_responses = self.app.data_manager.get_gallery_responses(min_score=min_score, limit=20)
61
+
62
+ if not gallery_responses:
63
+ return """
64
+ <div class="gallery-item">
65
+ <h3>🔍 No responses found at this creativity level yet!</h3>
66
+ <p>Be the first to create some legendary responses! Lower the filter or come back later.</p>
67
+ </div>
68
+ """
69
+
70
+ gallery_html = ""
71
+ for i, response in enumerate(gallery_responses, 1):
72
+ # Truncate long responses for gallery display
73
+ user_input = response.get("user_continuation", "").strip()
74
+ full_response = response.get("full_response_from_user", "").strip()
75
+ prompt = response.get("prompt", "").strip()
76
+
77
+ # Truncate for display
78
+ display_prompt = (prompt[:100] + "...") if len(prompt) > 100 else prompt
79
+
80
+ achievements = self.app.achievement_system.determine_achievement_titles(
81
+ response["cosine_distance"],
82
+ response["num_user_tokens"]
83
+ )
84
+ achievement_badges = " ".join([f'<span class="achievement-badge">{title}</span>' for title in achievements[:2]])
85
+
86
+ # Get original response for comparison
87
+ original_response = response.get("llm_full_response_original", "").strip()
88
+
89
+ gallery_html += f"""
90
+ <div class="gallery-item">
91
+ <div class="gallery-item-score">
92
+ 🎨 Creativity Score: {response['cosine_distance']:.3f}
93
+ ({response['num_user_tokens']} token{'s' if response['num_user_tokens'] != 1 else ''})
94
+ </div>
95
+ {achievement_badges}
96
+ <h4>#{i} - Prompt:</h4>
97
+ <p style="opacity: 0.8; font-style: italic;">{display_prompt}</p>
98
+ <h4>Creative Input: <span style="color: #ffa726;">"{user_input}"</span></h4>
99
+
100
+ <div style="display: flex; gap: 15px; margin-top: 15px;">
101
+ <div style="flex: 1;">
102
+ <h4>🤖 Original AI Response:</h4>
103
+ <div style="background: rgba(255,255,255,0.05); padding: 15px; border-radius: 8px; border-left: 4px solid #999; max-height: 200px; overflow-y: auto; font-family: monospace; font-size: 14px;">
104
+ {original_response}
105
+ </div>
106
+ </div>
107
+ <div style="flex: 1;">
108
+ <h4>✨ Creative Response (after user input):</h4>
109
+ <div style="background: rgba(255,255,255,0.05); padding: 15px; border-radius: 8px; border-left: 4px solid #ffa726; max-height: 200px; overflow-y: auto; font-family: monospace; font-size: 14px;">
110
+ {full_response}
111
+ </div>
112
+ </div>
113
+ </div>
114
+ </div>
115
+ """
116
+
117
+ return gallery_html
118
+
119
+ def show_inspire_me_examples(self):
120
+ """Show new random inspiring examples for current prompt"""
121
+ if not self.app.current_prompt:
122
+ return ""
123
+
124
+ examples = self.app.data_manager.get_inspire_me_examples(
125
+ self.app.current_prompt["prompt"],
126
+ self.app.current_prompt["llm_partial_response"],
127
+ limit=5
128
+ )
129
+
130
+ return self.generate_inspire_me_html(examples)
131
+
132
+ def show_tutorial(self):
133
+ """Show the tutorial page starting at step 1"""
134
+ example = TUTORIAL_EXAMPLE
135
+ step_content = self.get_tutorial_step_content(1)
136
+ switch_result = self.switch_to_page("tutorial", update_nav=False)
137
+ return switch_result + (
138
+ gr.update(value=step_content["explanation"]), # tutorial_step_explanation
139
+ gr.update(visible=step_content["show_prompt"], value=example["prompt"]), # tutorial_prompt_display
140
+ gr.update(visible=step_content["show_partial"], value=example["llm_partial_response"]), # tutorial_partial_response_display
141
+ gr.update(visible=step_content["show_input"]), # tutorial_input_section
142
+ gr.update(visible=step_content["show_results"]), # tutorial_results_section
143
+ gr.update(visible=False), # tutorial_prev_btn (hidden on step 1)
144
+ gr.update(visible=True), # tutorial_next_btn
145
+ gr.update(visible=False), # tutorial_play_btn (hidden until last step)
146
+ 1 # Reset tutorial step to 1
147
+ )
148
+
149
+ def get_tutorial_step_content(self, step):
150
+ """Get content for a specific tutorial step using unified example data"""
151
+ example = TUTORIAL_EXAMPLE
152
+
153
+ step_content = {
154
+ 1: {
155
+ "explanation": "## Step 1: The Prompt\n\nFirst, I'll show you what I was asked to write about ✍️<br>This gives you context for helping me be creative!",
156
+ "show_prompt": True,
157
+ "show_partial": False,
158
+ "show_input": False,
159
+ "show_results": False
160
+ },
161
+ 2: {
162
+ "explanation": "## Step 2: The start of my response\n\nThen I'll show you how I started responding 🤖😴<br>As you can see... it's pretty boring and predictable!",
163
+ "show_prompt": True,
164
+ "show_partial": True,
165
+ "show_input": False,
166
+ "show_results": False
167
+ },
168
+ 3: {
169
+ "explanation": "## Step 3: Your creative input \n\nThis is where the magic happens ✨ You add a few words (**tokens**) to steer me somewhere interesting.<br>Think unexpected, surprising, or completely different from what I started with.<br>I only want a little help - make each word count!<br><br>**🔤 What are tokens?** Your input is measured in \\\"tokens\\\" (how I process text). About 5 tokens ≈ 3-5 words.<br>Don't worry - you'll see your exact token count as you type!",
170
+ "show_prompt": True,
171
+ "show_partial": True,
172
+ "show_input": True,
173
+ "show_results": False,
174
+ "example_input": example["example_input"]
175
+ },
176
+ 4: {
177
+ "explanation": "## Step 4: I'll continue the response \n\nAfter you give me your creative direction, I'll finish the response based on your inspiration 🤖🎉<br>You'll see how different the result is from my original boring version - that's the magic of collaboration!",
178
+ "show_prompt": True,
179
+ "show_partial": True,
180
+ "show_input": True,
181
+ "show_results": True,
182
+ "example_input": example["example_input"],
183
+ "example_creative": example["example_creative"]
184
+ },
185
+ 5: {
186
+ "explanation": "## Step 5: Get scored on our collaborative response 🎨\n\nAfter I finish our collaborative response, we'll get scored for how different our combined response is from my original boring version!<br><br>**📊 How scoring works:** I use \\\"semantic similarity\\\" to compare the meaning of both responses. The more different the meaning, the higher your creativity score!<br>• 0.7+ = 🌟 Legendary creativity<br>• 0.5+ = 🔥 Exceptional creativity<br>• 0.3+ = ✨ Great creativity<br><br>**💡 Pro tip:** Unexpected words often lead to the most creative directions! 🏆",
187
+ "show_prompt": False,
188
+ "show_partial": False,
189
+ "show_input": False,
190
+ "show_results": True,
191
+ "example_input": example["example_input"],
192
+ "example_creative": example["example_creative"],
193
+ "example_score": example["example_score"]
194
+ }
195
+ }
196
+
197
+ return step_content.get(step, step_content[1])
198
+
199
+ def generate_inspire_me_html(self, examples):
200
+ """Generate HTML for inspire me examples"""
201
+ if not examples:
202
+ return self.app.template_renderer.load_template("inspire-me-empty.html")
203
+ else:
204
+ inspire_html = self.app.template_renderer.load_template("inspire-me-header.html")
205
+ for example in examples:
206
+ user_input = example.get("user_continuation", "").strip()
207
+ score = example["cosine_distance"]
208
+ tokens = example["num_user_tokens"]
209
+ plural = "s" if tokens != 1 else ""
210
+
211
+ inspire_html += self.app.template_renderer.load_template("inspire-me-example.html",
212
+ user_input=user_input,
213
+ score=score,
214
+ tokens=tokens,
215
+ plural=plural
216
+ )
217
+
218
+ inspire_html += self.app.template_renderer.load_template("inspire-me-footer.html")
219
+ return inspire_html
220
+
221
+ def tutorial_next_step(self, current_step):
222
+ """Advance to next tutorial step"""
223
+ next_step = min(current_step + 1, 5)
224
+ example = TUTORIAL_EXAMPLE
225
+ step_content = self.get_tutorial_step_content(next_step)
226
+
227
+ # Prepare example data for the step
228
+ input_value = step_content.get("example_input", "")
229
+ token_count = self.app.llm_manager.count_tokens(input_value) if input_value else 0
230
+
231
+ # Create score display for final step
232
+ score_display = ""
233
+ if next_step == 5 and "example_score" in step_content:
234
+ score_display = self.app.scorer.create_enhanced_score_display(
235
+ step_content["example_score"], 1, 95.0, token_count, 10
236
+ )
237
+
238
+ return (
239
+ gr.update(value=step_content["explanation"]), # tutorial_step_explanation
240
+ gr.update(visible=step_content["show_prompt"]), # tutorial_prompt_display
241
+ gr.update(visible=step_content["show_partial"]), # tutorial_partial_response_display
242
+ gr.update(visible=step_content["show_input"]), # tutorial_input_section
243
+ gr.update(value=input_value) if step_content["show_input"] else gr.update(), # tutorial_user_input
244
+ gr.update(value=f"**Tokens:** {token_count}/5") if step_content["show_input"] else gr.update(), # tutorial_token_counter
245
+ gr.update(visible=step_content["show_results"]), # tutorial_results_section
246
+ gr.update(value=example["llm_full_response_original"]) if step_content["show_results"] else gr.update(), # tutorial_original_response
247
+ gr.update(value=step_content.get("example_creative", "")) if step_content["show_results"] else gr.update(), # tutorial_creative_response
248
+ gr.update(value=score_display) if step_content["show_results"] else gr.update(), # tutorial_score_display
249
+ gr.update(visible=next_step > 1), # tutorial_prev_btn
250
+ gr.update(visible=next_step < 5), # tutorial_next_btn
251
+ gr.update(visible=next_step == 5), # tutorial_play_btn (show on last step)
252
+ next_step # Update tutorial step
253
+ )
254
+
255
+ def tutorial_prev_step(self, current_step):
256
+ """Go back to previous tutorial step"""
257
+ prev_step = max(current_step - 1, 1)
258
+ step_content = self.get_tutorial_step_content(prev_step)
259
+
260
+ return (
261
+ gr.update(value=step_content["explanation"]), # tutorial_step_explanation
262
+ gr.update(visible=step_content["show_prompt"]), # tutorial_prompt_display
263
+ gr.update(visible=step_content["show_partial"]), # tutorial_partial_response_display
264
+ gr.update(visible=step_content["show_input"]), # tutorial_input_section
265
+ gr.update(visible=step_content["show_results"]), # tutorial_results_section
266
+ gr.update(visible=prev_step > 1), # tutorial_prev_btn
267
+ gr.update(visible=prev_step < 4), # tutorial_next_btn
268
+ gr.update(visible=prev_step == 4), # tutorial_play_btn
269
+ prev_step # Update tutorial step
270
+ )
271
+
272
+ def start_game(self):
273
+ """Start the game"""
274
+ prompt_data = self.app.get_random_prompt()
275
+ # Load initial inspire me examples
276
+ initial_examples = self.app.data_manager.get_inspire_me_examples(
277
+ prompt_data["prompt"],
278
+ prompt_data["llm_partial_response"],
279
+ limit=5
280
+ )
281
+ inspire_html = self.generate_inspire_me_html(initial_examples)
282
+ switch_result = self.switch_to_page("creative", update_nav=False)
283
+ return switch_result + (
284
+ prompt_data['prompt'],
285
+ prompt_data['llm_partial_response'],
286
+ inspire_html
287
+ )
288
+
289
+ def back_to_landing(self):
290
+ """Go back to landing page"""
291
+ result = self.switch_to_page("welcome", update_nav=True)
292
+ return result + ("",) # Add empty gallery_display as the 14th output
293
+
294
+ def update_token_count(self, text, partial_response=""):
295
+ """Update token counter and visual tokens in real-time"""
296
+ if not text.strip():
297
+ return (
298
+ gr.update(value="**Tokens:** 0/5", elem_classes=["token-counter"]),
299
+ gr.update(value="Type to see tokens...", elem_classes=["token-visualization"])
300
+ )
301
+
302
+ # Count tokens and decode them individually
303
+ tokens, token_texts = self.app.llm_manager.tokenize_for_visualization(text)
304
+ token_count = len(tokens)
305
+
306
+ # Create visual token display
307
+ token_html_parts = []
308
+ for i, token_text in enumerate(token_texts):
309
+ if i < 5:
310
+ token_class = f"token-{i+1}"
311
+ else:
312
+ token_class = "token-excess"
313
+
314
+ # Escape HTML and handle special characters
315
+ token_display = token_text.replace(' ', '·').replace('\\n', '↵')
316
+ if not token_display.strip():
317
+ token_display = '·'
318
+
319
+ token_html_parts.append(f'<span class="token {token_class}">{token_display}</span>')
320
+
321
+ if token_html_parts:
322
+ token_visualization_html = ' '.join(token_html_parts)
323
+ else:
324
+ token_visualization_html = "Type to see tokens..."
325
+
326
+ # Update counter with color coding
327
+ if token_count <= 5:
328
+ counter_class = ["token-counter", "valid"]
329
+ counter_text = f"**Tokens:** {token_count}/5"
330
+ else:
331
+ counter_class = ["token-counter", "error"]
332
+ counter_text = f"**Tokens:** {token_count}/5 (Too many!)"
333
+
334
+ return (
335
+ gr.update(value=counter_text, elem_classes=counter_class),
336
+ gr.update(value=token_visualization_html, elem_classes=["token-visualization"])
337
+ )
338
+
339
+ def on_submit_start(self, user_text):
340
+ if not user_text.strip():
341
+ return (
342
+ gr.update(interactive=True),
343
+ gr.update(visible=False),
344
+ gr.update(visible=True, value="Please enter some text to continue the response."),
345
+ gr.update(visible=False), # Hide landing
346
+ gr.update(visible=False), # Hide gallery
347
+ gr.update(visible=True), # Show creative
348
+ gr.update(visible=False), # Hide results
349
+ gr.update()
350
+ )
351
+
352
+ return (
353
+ gr.update(interactive=False),
354
+ gr.update(visible=True),
355
+ gr.update(visible=False),
356
+ gr.update(visible=False), # Hide landing
357
+ gr.update(visible=False), # Hide gallery
358
+ gr.update(visible=True), # Show creative
359
+ gr.update(visible=False), # Hide results
360
+ gr.update()
361
+ )
362
+
363
+ def on_submit_process(self, user_text):
364
+ result = self.app.process_submission(user_text)
365
+ generated_response, cosine_distance, rank, percentile, mean_score, violin_plot, prompt_results = result
366
+
367
+ if "Error:" in generated_response or "Please" in generated_response:
368
+ return (
369
+ gr.update(interactive=True), # submit_btn
370
+ gr.update(visible=False), # loading_message
371
+ gr.update(visible=True, value=generated_response), # error_message
372
+ gr.update(visible=False), # landing_page
373
+ gr.update(visible=False), # gallery_page
374
+ gr.update(visible=True), # creative_page
375
+ gr.update(visible=False), # results_page
376
+ gr.update(visible=False), # cosine_distance_display
377
+ gr.update(), # results_prompt_display
378
+ gr.update(), # results_partial_display
379
+ gr.update(), # results_user_input_display
380
+ gr.update(), # original_response_display
381
+ gr.update(), # results_continuation_display
382
+ gr.update(visible=False), # violin_plot_display
383
+ gr.update(), # current_page (no change)
384
+ gr.update(), # has_results (no change)
385
+ gr.update(), # nav_results (no change)
386
+ gr.update(), # nav_session (no change)
387
+ gr.update(), # nav_welcome (no change)
388
+ gr.update(), # nav_tutorial (no change)
389
+ gr.update(), # nav_creative (no change)
390
+ gr.update() # nav_gallery (no change)
391
+ )
392
+
393
+ # Create enhanced score display with progress bars and metrics
394
+ user_tokens = self.app.llm_manager.count_tokens(user_text)
395
+ same_category_attempts = len([r for r in prompt_results
396
+ if r["num_user_tokens"] == user_tokens]) if prompt_results else 1
397
+
398
+ score_text = self.app.scorer.create_enhanced_score_display(
399
+ cosine_distance, rank, percentile, user_tokens, same_category_attempts
400
+ )
401
+
402
+ # Show Results/Progress nav buttons after first submission
403
+ # Switch to results page and make nav buttons visible
404
+ return (
405
+ gr.update(interactive=True), # submit_btn
406
+ gr.update(visible=False), # loading_message
407
+ gr.update(visible=False), # error_message
408
+ gr.update(visible=False), # landing_page (hide)
409
+ gr.update(visible=False), # gallery_page (hide)
410
+ gr.update(visible=False), # creative_page (hide)
411
+ gr.update(visible=True), # results_page (show)
412
+ gr.update(visible=True, value=score_text), # cosine_distance_display
413
+ gr.update(value=self.app.current_prompt['prompt']), # results_prompt_display
414
+ gr.update(value=self.app.current_prompt['llm_partial_response']), # results_partial_display
415
+ gr.update(value=user_text), # results_user_input_display
416
+ gr.update(value=self.app.current_prompt['llm_full_response_original']), # original_response_display
417
+ gr.update(value=generated_response), # results_continuation_display
418
+ gr.update(visible=True, value=violin_plot) if violin_plot else gr.update(visible=False), # violin_plot_display
419
+ "results", # current_page state
420
+ True, # has_results state
421
+ gr.update(visible=True, elem_classes=["nav-button", "active"]), # nav_results (visible + active)
422
+ gr.update(visible=True, elem_classes=["nav-button"]), # nav_session (visible + inactive)
423
+ gr.update(elem_classes=["nav-button"]), # nav_welcome (inactive)
424
+ gr.update(elem_classes=["nav-button"]), # nav_tutorial (inactive)
425
+ gr.update(elem_classes=["nav-button"]), # nav_creative (inactive)
426
+ gr.update(elem_classes=["nav-button"]) # nav_gallery (inactive)
427
+ )
428
+
429
+ def try_same_prompt(self):
430
+ switch_result = self.switch_to_page("creative")
431
+ return switch_result + (
432
+ gr.update(interactive=True), # submit_btn
433
+ gr.update(value=""), # user_input (clear)
434
+ gr.update(visible=False), # loading_message
435
+ gr.update(visible=False), # error_message
436
+ gr.update(value="**Tokens:** 0/5"), # token_counter (reset)
437
+ gr.update(value="Once you start typing, I'll show you what you've written, but in tokens!") # token_visualization (reset)
438
+ )
439
+
440
+ def try_new_prompt(self):
441
+ prompt_data = self.app.get_random_prompt()
442
+ initial_examples = self.app.data_manager.get_inspire_me_examples(
443
+ prompt_data["prompt"],
444
+ prompt_data["llm_partial_response"],
445
+ limit=5
446
+ )
447
+ inspire_html = self.generate_inspire_me_html(initial_examples)
448
+ switch_result = self.switch_to_page("creative")
449
+ return switch_result + (
450
+ prompt_data['prompt'], # prompt_display
451
+ prompt_data['llm_partial_response'], # partial_response_display
452
+ gr.update(interactive=True), # submit_btn
453
+ "", # user_input (clear)
454
+ gr.update(visible=False), # loading_message
455
+ gr.update(visible=False), # error_message
456
+ inspire_html # inspire_me_display
457
+ )
458
+
459
+ def load_creative_with_prompt(self):
460
+ """Load creative page with a new prompt"""
461
+ prompt_data = self.app.get_random_prompt()
462
+ initial_examples = self.app.data_manager.get_inspire_me_examples(
463
+ prompt_data["prompt"],
464
+ prompt_data["llm_partial_response"],
465
+ limit=5
466
+ )
467
+ inspire_html = self.generate_inspire_me_html(initial_examples)
468
+ switch_result = self.switch_to_page("creative")
469
+ return switch_result + (prompt_data['prompt'], prompt_data['llm_partial_response'], inspire_html)
470
+
471
+ def load_gallery_with_content(self):
472
+ """Load gallery page with gallery content"""
473
+ gallery_html = self.generate_gallery_html(0.3)
474
+ switch_result = self.switch_to_page("gallery")
475
+ return switch_result + (gallery_html,)
476
+
477
+ def load_tutorial_with_content(self):
478
+ """Load tutorial page with tutorial content initialized"""
479
+ example = TUTORIAL_EXAMPLE
480
+ step_content = self.get_tutorial_step_content(1)
481
+ switch_result = self.switch_to_page("tutorial")
482
+ return switch_result + (
483
+ step_content["explanation"], # tutorial_step_explanation
484
+ gr.update(value=example["prompt"], visible=step_content["show_prompt"]), # tutorial_prompt_display
485
+ gr.update(value=example["llm_partial_response"], visible=step_content["show_partial"]), # tutorial_partial_response_display
486
+ gr.update(visible=step_content["show_input"]), # tutorial_input_section
487
+ gr.update(visible=step_content["show_results"]), # tutorial_results_section
488
+ gr.update(visible=False), # tutorial_prev_btn (hidden on step 1)
489
+ gr.update(visible=True), # tutorial_next_btn
490
+ gr.update(visible=False), # tutorial_play_btn (hidden until last step)
491
+ 1 # Reset tutorial step to 1
492
+ )
493
+
494
+ def navigate_to_session(self):
495
+ main_stats, history, achievements = self.app.session_manager.generate_session_page_html(
496
+ self.app.template_renderer,
497
+ self.app.statistics_calculator,
498
+ self.app.scorer
499
+ )
500
+ switch_result = self.switch_to_page("session")
501
+ return switch_result + (main_stats, history, achievements)
src/ui/template_renderer.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Template rendering functionality"""
2
+
3
+ import os
4
+
5
+
6
+ class TemplateRenderer:
7
+ """Handles loading and rendering of HTML templates"""
8
+
9
+ def load_template(self, template_path, **kwargs):
10
+ """Load HTML template from file and format with provided variables"""
11
+ try:
12
+ with open(f"templates/components/{template_path}", "r", encoding="utf-8") as f:
13
+ template_content = f.read()
14
+ return template_content.format(**kwargs)
15
+ except FileNotFoundError:
16
+ print(f"Warning: Template file {template_path} not found")
17
+ return ""
18
+ except Exception as e:
19
+ print(f"Error loading template {template_path}: {e}")
20
+ return ""
static/css/base.css ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Main Layout Styles */
2
+ .main-title {
3
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
4
+ color: white !important;
5
+ padding: 30px;
6
+ border-radius: 15px;
7
+ text-align: center;
8
+ font-size: 36px;
9
+ font-weight: bold;
10
+ margin: 20px 0;
11
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
12
+ }
13
+
14
+ .main-title h1 {
15
+ color: white !important;
16
+ margin: 0;
17
+ }
18
+
19
+ /* Token Styles */
20
+ .token-counter {
21
+ background-color: #f0f0f0;
22
+ padding: 10px;
23
+ border-radius: 8px;
24
+ text-align: center;
25
+ margin: 5px 0;
26
+ border: 2px solid #ddd;
27
+ }
28
+
29
+ .token-counter.valid {
30
+ border-color: #4CAF50;
31
+ background-color: #e8f5e8;
32
+ }
33
+
34
+ .token-counter.warning {
35
+ border-color: #ff9800;
36
+ background-color: #fff3e0;
37
+ }
38
+
39
+ .token-counter.error {
40
+ border-color: #f44336;
41
+ background-color: #ffebee;
42
+ }
43
+
44
+ .difficulty-indicator {
45
+ background-color: #e3f2fd;
46
+ padding: 8px;
47
+ border-radius: 8px;
48
+ text-align: center;
49
+ margin: 5px 0;
50
+ font-size: 14px;
51
+ }
52
+
53
+ .difficulty-indicator.easy { background-color: #f1f8e9; color: #8bc34a; }
54
+ .difficulty-indicator.medium { background-color: #fff8e1; color: #ffc107; }
55
+ .difficulty-indicator.hard { background-color: #fce4ec; color: #e91e63; }
56
+
57
+ .token-explanation {
58
+ background-color: #e3f2fd;
59
+ padding: 10px 15px;
60
+ border-radius: 8px;
61
+ border-left: 4px solid #2196F3;
62
+ margin: 10px 0;
63
+ font-size: 14px;
64
+ color: #1565C0;
65
+ opacity: 0.9;
66
+ }
67
+
68
+ .token-visualization {
69
+ background-color: #f9f9f9;
70
+ padding: 12px;
71
+ border-radius: 8px;
72
+ border: 1px solid #ddd;
73
+ margin: 10px 0;
74
+ min-height: 40px;
75
+ font-family: monospace;
76
+ font-size: 14px;
77
+ line-height: 1.5;
78
+ }
79
+
80
+ .token {
81
+ display: inline-block;
82
+ padding: 2px 6px;
83
+ margin: 2px;
84
+ border-radius: 4px;
85
+ border: 1px solid;
86
+ font-weight: bold;
87
+ }
88
+
89
+ .token.token-1 { background-color: #e8eaf6; border-color: #667eea; color: #667eea; }
90
+ .token.token-2 { background-color: #f1f8e9; border-color: #8bc34a; color: #8bc34a; }
91
+ .token.token-3 { background-color: #fff8e1; border-color: #ffc107; color: #ffc107; }
92
+ .token.token-4 { background-color: #fce4ec; border-color: #e91e63; color: #e91e63; }
93
+ .token.token-5 { background-color: #f3e5f5; border-color: #9c27b0; color: #9c27b0; }
94
+ .token.token-excess { background-color: #ffebee; border-color: #d32f2f; color: #d32f2f; }
95
+
96
+ /* Response Styles */
97
+ .original-response {
98
+ background-color: #fafafa !important;
99
+ border-left: 4px solid #999 !important;
100
+ }
101
+
102
+ .streaming-response {
103
+ background-color: #f1f8e9 !important;
104
+ border-left: 4px solid #8bc34a !important;
105
+ font-family: monospace;
106
+ }
107
+
108
+ /* Animation Styles */
109
+ .countdown {
110
+ background: linear-gradient(45deg, #ff6b6b, #ff8e8e);
111
+ color: white;
112
+ padding: 20px;
113
+ border-radius: 15px;
114
+ text-align: center;
115
+ font-size: 24px;
116
+ font-weight: bold;
117
+ margin: 20px 0;
118
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
119
+ animation: pulse 1s infinite;
120
+ }
121
+
122
+ @keyframes pulse {
123
+ 0% { transform: scale(1); }
124
+ 50% { transform: scale(1.05); }
125
+ 100% { transform: scale(1); }
126
+ }
127
+
128
+ @keyframes bounce {
129
+ 0%, 20%, 50%, 80%, 100% { transform: translateY(0); }
130
+ 40% { transform: translateY(-10px); }
131
+ 60% { transform: translateY(-5px); }
132
+ }
133
+
134
+ @keyframes float {
135
+ 0% { transform: translateY(0px); }
136
+ 50% { transform: translateY(-5px); }
137
+ 100% { transform: translateY(0px); }
138
+ }
139
+
140
+ @keyframes fadeIn {
141
+ from { opacity: 0; transform: translateY(20px); }
142
+ to { opacity: 1; transform: translateY(0); }
143
+ }
144
+
145
+ @keyframes glow {
146
+ from { box-shadow: 0 0 5px #4CAF50; }
147
+ to { box-shadow: 0 0 15px #4CAF50, 0 0 20px #4CAF50; }
148
+ }
149
+
150
+ /* Animation Assignments */
151
+ .start-button:hover {
152
+ animation: bounce 0.6s ease !important;
153
+ }
154
+
155
+
156
+ .game-mechanics {
157
+ animation: fadeIn 1s ease-in;
158
+ }
159
+
160
+ .token-counter.valid {
161
+ animation: glow 2s ease-in-out infinite alternate;
162
+ }
static/css/components.css ADDED
@@ -0,0 +1,214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Button Styles */
2
+ .start-button {
3
+ background: linear-gradient(45deg, #ff6b6b, #ff8e8e) !important;
4
+ color: white !important;
5
+ font-size: 24px !important;
6
+ font-weight: bold !important;
7
+ padding: 20px 40px !important;
8
+ border-radius: 50px !important;
9
+ border: none !important;
10
+ box-shadow: 0 6px 12px rgba(255, 107, 107, 0.3) !important;
11
+ transition: all 0.3s ease !important;
12
+ }
13
+
14
+ .start-button:hover {
15
+ transform: translateY(-2px) !important;
16
+ box-shadow: 0 8px 16px rgba(255, 107, 107, 0.4) !important;
17
+ }
18
+
19
+ .try-again-button {
20
+ background: linear-gradient(45deg, #4CAF50, #66BB6A) !important;
21
+ color: white !important;
22
+ font-size: 20px !important;
23
+ font-weight: bold !important;
24
+ padding: 15px 30px !important;
25
+ border-radius: 40px !important;
26
+ border: none !important;
27
+ box-shadow: 0 6px 12px rgba(76, 175, 80, 0.3) !important;
28
+ transition: all 0.3s ease !important;
29
+ min-width: 180px !important;
30
+ margin: 10px !important;
31
+ }
32
+
33
+ .try-again-button:hover {
34
+ transform: translateY(-2px) !important;
35
+ box-shadow: 0 8px 16px rgba(76, 175, 80, 0.4) !important;
36
+ background: linear-gradient(45deg, #66BB6A, #4CAF50) !important;
37
+ }
38
+
39
+ /* Content Box Styles */
40
+ .prompt-box {
41
+ background-color: #e8eaf6 !important;
42
+ border-left: 4px solid #667eea !important;
43
+ }
44
+
45
+ .assistant-box {
46
+ background-color: #f1f8e9 !important;
47
+ border-left: 4px solid #8bc34a !important;
48
+ }
49
+
50
+ .user-box {
51
+ background-color: #fff8e1 !important;
52
+ border-left: 4px solid #ffc107 !important;
53
+ }
54
+
55
+ .results-box {
56
+ background-color: #fce4ec !important;
57
+ border-left: 4px solid #e91e63 !important;
58
+ }
59
+
60
+ .error-box {
61
+ background-color: #ffebee !important;
62
+ border-left: 4px solid #f44336 !important;
63
+ }
64
+
65
+ /* Tooltip Styles */
66
+ .help-tooltip {
67
+ display: inline-block;
68
+ position: relative;
69
+ cursor: help;
70
+ margin-left: 5px;
71
+ opacity: 0.7;
72
+ transition: opacity 0.3s;
73
+ }
74
+
75
+ .help-tooltip:hover {
76
+ opacity: 1;
77
+ }
78
+
79
+ .help-tooltip .tooltip-text {
80
+ visibility: hidden;
81
+ width: 280px;
82
+ background-color: #333;
83
+ color: white;
84
+ text-align: left;
85
+ border-radius: 8px;
86
+ padding: 12px;
87
+ position: absolute;
88
+ z-index: 1000;
89
+ bottom: 125%;
90
+ left: 50%;
91
+ margin-left: -140px;
92
+ box-shadow: 0 4px 8px rgba(0,0,0,0.3);
93
+ font-size: 13px;
94
+ line-height: 1.4;
95
+ }
96
+
97
+ .help-tooltip .tooltip-text::after {
98
+ content: "";
99
+ position: absolute;
100
+ top: 100%;
101
+ left: 50%;
102
+ margin-left: -5px;
103
+ border-width: 5px;
104
+ border-style: solid;
105
+ border-color: #333 transparent transparent transparent;
106
+ }
107
+
108
+ .help-tooltip:hover .tooltip-text {
109
+ visibility: visible;
110
+ animation: fadeIn 0.3s;
111
+ }
112
+
113
+ @keyframes fadeIn {
114
+ from { opacity: 0; }
115
+ to { opacity: 1; }
116
+ }
117
+
118
+ /* Smart Tip Container */
119
+ .smart-tip-container {
120
+ background: linear-gradient(45deg, #667eea, #764ba2);
121
+ border-radius: 12px;
122
+ padding: 15px;
123
+ margin: 15px 0;
124
+ color: white;
125
+ border-left: 4px solid #ffa726;
126
+ }
127
+
128
+ .smart-tip-icon {
129
+ font-size: 20px;
130
+ margin-right: 8px;
131
+ }
132
+
133
+ .smart-tip-text {
134
+ font-size: 16px;
135
+ font-weight: 500;
136
+ }
137
+
138
+ /* Additional Button Styles */
139
+ .submit-button {
140
+ transition: all 0.3s ease;
141
+ }
142
+
143
+ .submit-button:hover {
144
+ transform: translateY(-2px);
145
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2);
146
+ }
147
+
148
+ .primary-button {
149
+ background: linear-gradient(45deg, #ff6b6b, #ff8e8e) !important;
150
+ color: white !important;
151
+ font-size: 22px !important;
152
+ font-weight: bold !important;
153
+ padding: 18px 35px !important;
154
+ border-radius: 50px !important;
155
+ border: none !important;
156
+ box-shadow: 0 6px 12px rgba(255, 107, 107, 0.3) !important;
157
+ transition: all 0.3s ease !important;
158
+ min-width: 200px !important;
159
+ margin: 10px !important;
160
+ }
161
+
162
+ .primary-button:hover {
163
+ transform: translateY(-2px) !important;
164
+ box-shadow: 0 8px 16px rgba(255, 107, 107, 0.4) !important;
165
+ background: linear-gradient(45deg, #ff8e8e, #ff6b6b) !important;
166
+ }
167
+
168
+ .secondary-button {
169
+ background: linear-gradient(45deg, #667eea, #764ba2) !important;
170
+ color: white !important;
171
+ font-size: 22px !important;
172
+ font-weight: bold !important;
173
+ padding: 18px 35px !important;
174
+ border-radius: 50px !important;
175
+ border: none !important;
176
+ box-shadow: 0 6px 12px rgba(102, 126, 234, 0.3) !important;
177
+ transition: all 0.3s ease !important;
178
+ min-width: 200px !important;
179
+ margin: 10px !important;
180
+ }
181
+
182
+ .secondary-button:hover {
183
+ transform: translateY(-2px) !important;
184
+ box-shadow: 0 8px 16px rgba(102, 126, 234, 0.4) !important;
185
+ background: linear-gradient(45deg, #764ba2, #667eea) !important;
186
+ }
187
+
188
+ .tutorial-button {
189
+ background: linear-gradient(45deg, #4CAF50, #66BB6A) !important;
190
+ color: white !important;
191
+ font-size: 18px !important;
192
+ font-weight: bold !important;
193
+ padding: 12px 25px !important;
194
+ border-radius: 30px !important;
195
+ border: none !important;
196
+ box-shadow: 0 4px 8px rgba(76, 175, 80, 0.3) !important;
197
+ transition: all 0.3s ease !important;
198
+ margin: 0 10px !important;
199
+ min-width: 120px !important;
200
+ }
201
+
202
+ .tutorial-button:hover {
203
+ transform: translateY(-2px) !important;
204
+ box-shadow: 0 6px 12px rgba(76, 175, 80, 0.4) !important;
205
+ }
206
+
207
+ .tutorial-button.secondary {
208
+ background: linear-gradient(45deg, #9E9E9E, #607D8B) !important;
209
+ box-shadow: 0 4px 8px rgba(158, 158, 158, 0.3) !important;
210
+ }
211
+
212
+ .tutorial-button.secondary:hover {
213
+ box-shadow: 0 6px 12px rgba(158, 158, 158, 0.4) !important;
214
+ }
static/css/gallery.css ADDED
@@ -0,0 +1,524 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Tutorial Styles */
2
+ .tutorial-container {
3
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
4
+ border-radius: 20px;
5
+ color: white;
6
+ margin: 20px 0;
7
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
8
+ overflow: hidden;
9
+ }
10
+
11
+ .tutorial-header {
12
+ text-align: center;
13
+ padding: 30px 20px 20px 20px;
14
+ background: rgba(255, 255, 255, 0.1);
15
+ }
16
+
17
+ .tutorial-title {
18
+ font-size: 36px;
19
+ font-weight: bold;
20
+ margin-bottom: 10px;
21
+ }
22
+
23
+ .tutorial-subtitle {
24
+ font-size: 18px;
25
+ opacity: 0.9;
26
+ }
27
+
28
+ .tutorial-step {
29
+ padding: 30px;
30
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
31
+ }
32
+
33
+ .tutorial-step:last-child {
34
+ border-bottom: none;
35
+ }
36
+
37
+ .tutorial-step.active {
38
+ background: rgba(255, 255, 255, 0.05);
39
+ border-left: 5px solid #ffa726;
40
+ }
41
+
42
+ .tutorial-step.inactive {
43
+ opacity: 0.4;
44
+ pointer-events: none;
45
+ }
46
+
47
+ .step-number {
48
+ display: inline-block;
49
+ background: #ffa726;
50
+ color: white;
51
+ width: 30px;
52
+ height: 30px;
53
+ border-radius: 50%;
54
+ text-align: center;
55
+ line-height: 30px;
56
+ font-weight: bold;
57
+ margin-right: 15px;
58
+ }
59
+
60
+ .step-title {
61
+ font-size: 24px;
62
+ font-weight: bold;
63
+ margin-bottom: 10px;
64
+ }
65
+
66
+ .step-description {
67
+ font-size: 16px;
68
+ line-height: 1.6;
69
+ margin-bottom: 15px;
70
+ }
71
+
72
+ .tutorial-navigation {
73
+ text-align: center;
74
+ padding: 30px;
75
+ background: rgba(255, 255, 255, 0.05);
76
+ }
77
+
78
+ .expandable-section {
79
+ background: rgba(255, 255, 255, 0.1);
80
+ border-radius: 10px;
81
+ margin: 15px 0;
82
+ cursor: pointer;
83
+ transition: background-color 0.3s ease;
84
+ }
85
+
86
+ .expandable-section:hover {
87
+ background: rgba(255, 255, 255, 0.15);
88
+ }
89
+
90
+ .expandable-header {
91
+ padding: 15px 20px;
92
+ font-weight: bold;
93
+ display: flex;
94
+ justify-content: space-between;
95
+ align-items: center;
96
+ }
97
+
98
+ .expandable-content {
99
+ padding: 0 20px 20px 20px;
100
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
101
+ }
102
+
103
+ .tutorial-step-explanation {
104
+ background: rgba(255, 255, 255, 0.1);
105
+ border-radius: 15px;
106
+ padding: 25px;
107
+ margin: 20px 0;
108
+ color: white;
109
+ font-size: 18px;
110
+ line-height: 1.6;
111
+ border-left: 5px solid #ffa726;
112
+ }
113
+
114
+ /* Gallery Styles */
115
+ .gallery-container {
116
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
117
+ border-radius: 20px;
118
+ color: white;
119
+ margin: 20px 0;
120
+ padding: 30px;
121
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
122
+ }
123
+
124
+ .gallery-item {
125
+ background: rgba(255, 255, 255, 0.1);
126
+ border-radius: 15px;
127
+ padding: 20px;
128
+ margin: 15px 0;
129
+ border-left: 5px solid #ffa726;
130
+ }
131
+
132
+ .gallery-item-score {
133
+ background: linear-gradient(45deg, #ff6b6b, #ffa726);
134
+ color: white;
135
+ padding: 8px 16px;
136
+ border-radius: 20px;
137
+ font-size: 14px;
138
+ font-weight: bold;
139
+ display: inline-block;
140
+ margin-bottom: 10px;
141
+ }
142
+
143
+ /* Inspire Me Styles */
144
+ .inspire-me-container {
145
+ background: rgba(255, 255, 255, 0.05);
146
+ border: 2px dashed rgba(255, 165, 38, 0.3);
147
+ border-radius: 15px;
148
+ padding: 20px;
149
+ margin: 15px 0;
150
+ text-align: center;
151
+ }
152
+
153
+ .inspire-me-button {
154
+ background: linear-gradient(45deg, #ffa726, #ff8f00) !important;
155
+ color: white !important;
156
+ font-size: 16px !important;
157
+ font-weight: bold !important;
158
+ padding: 12px 25px !important;
159
+ border-radius: 25px !important;
160
+ border: none !important;
161
+ box-shadow: 0 4px 8px rgba(255, 167, 38, 0.3) !important;
162
+ transition: all 0.3s ease !important;
163
+ margin: 10px !important;
164
+ }
165
+
166
+ .inspire-me-button:hover {
167
+ transform: translateY(-2px) !important;
168
+ box-shadow: 0 6px 12px rgba(255, 167, 38, 0.4) !important;
169
+ }
170
+
171
+ .inspire-example {
172
+ background: rgba(255, 255, 255, 0.1);
173
+ border-radius: 10px;
174
+ padding: 15px;
175
+ margin: 10px 0;
176
+ text-align: left;
177
+ border-left: 3px solid #ffa726;
178
+ }
179
+
180
+ .inspire-example-text {
181
+ font-family: monospace;
182
+ font-size: 14px;
183
+ color: #ffa726;
184
+ margin: 5px 0;
185
+ }
186
+
187
+ .inspire-example-score {
188
+ font-size: 12px;
189
+ opacity: 0.8;
190
+ color: #66BB6A;
191
+ }
192
+
193
+ /* Section Header Styles */
194
+ .section-header {
195
+ background: rgba(255, 255, 255, 0.05);
196
+ padding: 15px 20px;
197
+ border-radius: 10px;
198
+ margin: 20px 0 15px 0;
199
+ text-align: center;
200
+ color: #667eea;
201
+ font-weight: bold;
202
+ border: 1px solid rgba(102, 126, 234, 0.2);
203
+ }
204
+
205
+ /* Session Progress Styles */
206
+ .session-progress {
207
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
208
+ border-radius: 15px;
209
+ padding: 25px;
210
+ margin: 20px 0;
211
+ color: white;
212
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
213
+ }
214
+
215
+ .session-stats {
216
+ display: flex;
217
+ justify-content: space-between;
218
+ margin: 20px 0;
219
+ flex-wrap: wrap;
220
+ gap: 10px;
221
+ }
222
+
223
+ .stat-item {
224
+ background-color: rgba(255, 255, 255, 0.1);
225
+ padding: 15px;
226
+ border-radius: 10px;
227
+ text-align: center;
228
+ flex: 1;
229
+ min-width: 100px;
230
+ }
231
+
232
+ .stat-value {
233
+ font-size: 24px;
234
+ font-weight: bold;
235
+ color: #ffa726;
236
+ margin-bottom: 5px;
237
+ }
238
+
239
+ .stat-label {
240
+ font-size: 12px;
241
+ opacity: 0.8;
242
+ text-transform: uppercase;
243
+ letter-spacing: 0.5px;
244
+ }
245
+
246
+ .session-achievements {
247
+ background-color: rgba(255, 255, 255, 0.05);
248
+ border-radius: 10px;
249
+ padding: 15px;
250
+ margin: 15px 0;
251
+ }
252
+
253
+ .recent-attempts {
254
+ background-color: rgba(255, 255, 255, 0.05);
255
+ border-radius: 10px;
256
+ padding: 15px;
257
+ margin: 15px 0;
258
+ }
259
+
260
+ .attempt-item {
261
+ display: flex;
262
+ justify-content: space-between;
263
+ align-items: center;
264
+ padding: 8px 0;
265
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
266
+ }
267
+
268
+ .attempt-item:last-child {
269
+ border-bottom: none;
270
+ }
271
+
272
+ .attempt-number {
273
+ font-weight: bold;
274
+ opacity: 0.7;
275
+ min-width: 30px;
276
+ }
277
+
278
+ .attempt-input {
279
+ flex: 1;
280
+ margin: 0 10px;
281
+ font-family: monospace;
282
+ font-size: 14px;
283
+ color: #ffa726;
284
+ }
285
+
286
+ .attempt-score {
287
+ font-weight: bold;
288
+ white-space: nowrap;
289
+ }
290
+
291
+ .ranking-highlight {
292
+ background-color: rgba(76, 175, 80, 0.2);
293
+ border: 2px solid rgba(76, 175, 80, 0.3);
294
+ }
295
+
296
+ .session-overview {
297
+ margin-top: 20px;
298
+ }
299
+
300
+ .best-response-showcase {
301
+ background: rgba(255, 255, 255, 0.1);
302
+ border-radius: 12px;
303
+ padding: 20px;
304
+ margin: 15px 0;
305
+ border-left: 4px solid #4CAF50;
306
+ }
307
+
308
+ .best-response-showcase h4 {
309
+ margin: 0 0 15px 0;
310
+ color: #ffa726;
311
+ font-size: 18px;
312
+ }
313
+
314
+ .best-response-content {
315
+ display: flex;
316
+ justify-content: space-between;
317
+ align-items: center;
318
+ flex-wrap: wrap;
319
+ gap: 10px;
320
+ }
321
+
322
+ .best-input {
323
+ font-family: monospace;
324
+ font-size: 16px;
325
+ color: #ffa726;
326
+ font-weight: bold;
327
+ flex: 1;
328
+ min-width: 200px;
329
+ }
330
+
331
+ .best-metrics {
332
+ display: flex;
333
+ gap: 10px;
334
+ flex-wrap: wrap;
335
+ }
336
+
337
+ .score-badge, .rank-badge {
338
+ background: rgba(255, 255, 255, 0.2);
339
+ padding: 6px 12px;
340
+ border-radius: 20px;
341
+ font-size: 14px;
342
+ font-weight: bold;
343
+ white-space: nowrap;
344
+ }
345
+
346
+ .rank-badge {
347
+ background: rgba(76, 175, 80, 0.3);
348
+ color: #4CAF50;
349
+ }
350
+
351
+ .performance-trend {
352
+ background: rgba(255, 255, 255, 0.08);
353
+ border-radius: 8px;
354
+ padding: 15px;
355
+ margin-top: 15px;
356
+ font-size: 14px;
357
+ }
358
+
359
+ .trend-detail {
360
+ opacity: 0.7;
361
+ font-size: 12px;
362
+ margin-left: 10px;
363
+ }
364
+
365
+ .attempt-item.enhanced {
366
+ padding: 12px 0;
367
+ align-items: flex-start;
368
+ }
369
+
370
+ .attempt-content {
371
+ flex: 1;
372
+ margin: 0 15px;
373
+ }
374
+
375
+ .attempt-details {
376
+ display: flex;
377
+ justify-content: space-between;
378
+ align-items: center;
379
+ margin-top: 8px;
380
+ font-size: 12px;
381
+ opacity: 0.8;
382
+ flex-wrap: wrap;
383
+ gap: 10px;
384
+ }
385
+
386
+ .timestamp {
387
+ color: rgba(255, 255, 255, 0.6);
388
+ }
389
+
390
+ .ranking-info {
391
+ font-weight: bold;
392
+ white-space: nowrap;
393
+ }
394
+
395
+ .attempt-metrics {
396
+ display: flex;
397
+ flex-direction: column;
398
+ align-items: flex-end;
399
+ gap: 4px;
400
+ min-width: 80px;
401
+ }
402
+
403
+ .token-count {
404
+ font-size: 11px;
405
+ opacity: 0.7;
406
+ white-space: nowrap;
407
+ }
408
+
409
+ .session-tip {
410
+ background: rgba(255, 255, 255, 0.1);
411
+ border-radius: 8px;
412
+ padding: 12px;
413
+ margin-top: 20px;
414
+ border-left: 4px solid #ffa726;
415
+ font-size: 14px;
416
+ }
417
+
418
+ /* Results Header Styles */
419
+ .results-header {
420
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
421
+ border-radius: 15px;
422
+ padding: 25px;
423
+ margin: 20px 0;
424
+ color: white;
425
+ text-align: center;
426
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
427
+ }
428
+
429
+ .results-header h1 {
430
+ color: white !important;
431
+ margin-bottom: 15px;
432
+ font-size: 32px;
433
+ }
434
+
435
+ .results-header p {
436
+ margin: 10px 0;
437
+ opacity: 0.9;
438
+ }
439
+
440
+ .results-header a {
441
+ color: #ffa726;
442
+ text-decoration: none;
443
+ font-weight: 500;
444
+ transition: color 0.3s ease;
445
+ }
446
+
447
+ .results-header a:hover {
448
+ color: #ffcc80;
449
+ text-decoration: underline;
450
+ }
451
+
452
+ .results-section-header {
453
+ background: rgba(102, 126, 234, 0.1);
454
+ border-left: 4px solid #667eea;
455
+ padding: 15px 20px;
456
+ border-radius: 8px;
457
+ margin: 30px 0 20px 0;
458
+ font-size: 20px;
459
+ font-weight: bold;
460
+ color: #667eea;
461
+ }
462
+
463
+ .section-divider {
464
+ margin: 40px 0;
465
+ border: none;
466
+ height: 2px;
467
+ background: linear-gradient(to right, transparent, rgba(102, 126, 234, 0.3), transparent);
468
+ }
469
+
470
+ .action-section {
471
+ background: rgba(102, 126, 234, 0.05);
472
+ border-radius: 15px;
473
+ padding: 30px;
474
+ margin: 40px 0;
475
+ text-align: center;
476
+ border: 2px solid rgba(102, 126, 234, 0.1);
477
+ }
478
+
479
+ /* Navigation Styles */
480
+ .nav-container {
481
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
482
+ padding: 20px;
483
+ border-radius: 15px;
484
+ margin: 20px 0;
485
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
486
+ }
487
+
488
+ .nav-button {
489
+ background: rgba(255, 255, 255, 0.2) !important;
490
+ color: white !important;
491
+ font-size: 16px !important;
492
+ font-weight: bold !important;
493
+ padding: 12px 20px !important;
494
+ border-radius: 25px !important;
495
+ border: 2px solid transparent !important;
496
+ margin: 0 5px !important;
497
+ min-width: 140px !important;
498
+ transition: all 0.3s ease !important;
499
+ backdrop-filter: blur(10px) !important;
500
+ }
501
+
502
+ .nav-button:hover {
503
+ background: rgba(255, 255, 255, 0.3) !important;
504
+ transform: translateY(-2px) !important;
505
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2) !important;
506
+ }
507
+
508
+ .nav-button.active {
509
+ background: linear-gradient(45deg, #ffa726, #ff8f00) !important;
510
+ color: white !important;
511
+ border-color: #ffa726 !important;
512
+ box-shadow: 0 4px 12px rgba(255, 167, 38, 0.4) !important;
513
+ }
514
+
515
+ .nav-button.active:hover {
516
+ background: linear-gradient(45deg, #ff8f00, #ffa726) !important;
517
+ transform: translateY(-1px) !important;
518
+ }
519
+
520
+ .nav-button:disabled {
521
+ opacity: 0.5 !important;
522
+ cursor: not-allowed !important;
523
+ transform: none !important;
524
+ }
static/css/game.css ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Game Mechanics Styles */
2
+ .game-mechanics {
3
+ background: rgba(255, 255, 255, 0.1);
4
+ border-radius: 15px;
5
+ padding: 30px;
6
+ margin: 30px 0;
7
+ backdrop-filter: blur(10px);
8
+ }
9
+
10
+ .mechanic-item {
11
+ margin: 15px 0;
12
+ font-size: 18px;
13
+ display: flex;
14
+ align-items: center;
15
+ justify-content: center;
16
+ gap: 10px;
17
+ }
static/css/landing.css ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .landing-page {
2
+ text-align: center;
3
+ padding: 40px 20px;
4
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
5
+ border-radius: 20px;
6
+ color: white;
7
+ margin: 20px 0;
8
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
9
+ }
10
+ .landing-title {
11
+ font-size: 48px;
12
+ font-weight: bold;
13
+ margin-bottom: 20px;
14
+ animation: float 3s ease-in-out infinite;
15
+ }
16
+ .landing-subtitle {
17
+ font-size: 24px;
18
+ margin-bottom: 30px;
19
+ opacity: 0.9;
20
+ }
21
+
22
+ .landing-description {
23
+ font-size: 20px;
24
+ margin: 20px 0;
25
+ opacity: 0.85;
26
+ font-weight: 500;
27
+ }
static/css/scores.css ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Score Display Styles */
2
+ .cosine-distance {
3
+ background-color: #4CAF50;
4
+ color: white;
5
+ padding: 20px;
6
+ border-radius: 10px;
7
+ text-align: center;
8
+ font-size: 24px;
9
+ font-weight: bold;
10
+ margin: 20px 0;
11
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
12
+ }
13
+
14
+ .score-container {
15
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
16
+ border-radius: 20px;
17
+ padding: 30px;
18
+ margin: 20px 0;
19
+ color: white;
20
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
21
+ }
22
+
23
+ .score-header {
24
+ font-size: 28px;
25
+ font-weight: bold;
26
+ text-align: center;
27
+ margin-bottom: 20px;
28
+ }
29
+
30
+ /* Progress Bar Styles */
31
+ .progress-bar-container {
32
+ background-color: rgba(255, 255, 255, 0.2);
33
+ border-radius: 15px;
34
+ height: 30px;
35
+ margin: 15px 0;
36
+ overflow: hidden;
37
+ position: relative;
38
+ }
39
+
40
+ .progress-bar {
41
+ height: 100%;
42
+ border-radius: 15px;
43
+ background: linear-gradient(45deg, #ff6b6b, #ffa726);
44
+ transition: width 1.5s ease-in-out;
45
+ position: relative;
46
+ }
47
+
48
+ .progress-bar::after {
49
+ content: '';
50
+ position: absolute;
51
+ top: 0;
52
+ left: 0;
53
+ bottom: 0;
54
+ right: 0;
55
+ background: linear-gradient(45deg, transparent 33%, rgba(255, 255, 255, 0.3) 50%, transparent 66%);
56
+ animation: shimmer 2s infinite;
57
+ }
58
+
59
+ @keyframes shimmer {
60
+ 0% { transform: translateX(-100%); }
61
+ 100% { transform: translateX(100%); }
62
+ }
63
+
64
+ /* Score Details */
65
+ .score-details {
66
+ display: flex;
67
+ justify-content: space-between;
68
+ margin-top: 20px;
69
+ flex-wrap: wrap;
70
+ }
71
+
72
+ .score-metric {
73
+ background-color: rgba(255, 255, 255, 0.1);
74
+ padding: 15px;
75
+ border-radius: 10px;
76
+ text-align: center;
77
+ margin: 5px;
78
+ flex: 1;
79
+ min-width: 150px;
80
+ }
81
+
82
+ .metric-value {
83
+ font-size: 20px;
84
+ font-weight: bold;
85
+ color: #ffa726;
86
+ }
87
+
88
+ .metric-label {
89
+ font-size: 14px;
90
+ opacity: 0.9;
91
+ margin-top: 5px;
92
+ }
93
+
94
+ /* Achievements */
95
+ .achievements-container {
96
+ background-color: rgba(255, 255, 255, 0.1);
97
+ border-radius: 15px;
98
+ padding: 20px;
99
+ margin: 20px 0;
100
+ text-align: center;
101
+ }
102
+
103
+ .achievements-title {
104
+ font-size: 18px;
105
+ font-weight: bold;
106
+ margin-bottom: 15px;
107
+ color: #ffa726;
108
+ }
109
+
110
+ .achievement-badges {
111
+ display: flex;
112
+ flex-wrap: wrap;
113
+ justify-content: center;
114
+ gap: 10px;
115
+ }
116
+
117
+ .achievement-badge {
118
+ background: linear-gradient(45deg, #ff6b6b, #ffa726);
119
+ color: white;
120
+ padding: 8px 16px;
121
+ border-radius: 20px;
122
+ font-size: 14px;
123
+ font-weight: bold;
124
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
125
+ animation: badge-glow 2s ease-in-out;
126
+ }
127
+
128
+ @keyframes badge-glow {
129
+ 0% { box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); }
130
+ 50% { box-shadow: 0 4px 12px rgba(255, 165, 38, 0.4); }
131
+ 100% { box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); }
132
+ }
templates/components/achievement-badge.html ADDED
@@ -0,0 +1 @@
 
 
1
+ <span class="achievement-badge">{achievement}</span>
templates/components/attempt-item.html ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ <div class="attempt-item">
2
+ <span class="attempt-number">#{attempt_number}</span>
3
+ <span class="attempt-input">"{user_input}"</span>
4
+ <span class="attempt-score" style="color: {score_color};">
5
+ {cosine_distance:.3f} ({num_tokens} token{plural})
6
+ </span>
7
+ </div>
templates/components/inspire-me-empty.html ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ <div class="inspire-example">
2
+ <p>🌟 <strong>You're the first to try this prompt!</strong></p>
3
+ <p>Be bold and creative - you're setting the bar for others to follow!</p>
4
+ </div>
templates/components/inspire-me-example.html ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ <div class="inspire-example">
2
+ <div class="inspire-example-text">"{user_input}"</div>
3
+ <div class="inspire-example-score">
4
+ Creativity: {score:.3f} ({tokens} token{plural})
5
+ </div>
6
+ </div>
templates/components/inspire-me-footer.html ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ <div class="inspire-example">
2
+ <p style="text-align: center; font-style: italic; opacity: 0.8;">
3
+ ✨ Now it's your turn! Can you be even more creative?
4
+ </p>
5
+ </div>
templates/components/inspire-me-header.html ADDED
@@ -0,0 +1 @@
 
 
1
+ <h4>💡 Here are some creative examples from others:</h4>
templates/components/recent-attempts.html ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ <div class="recent-attempts">
2
+ <h4>📊 Your Recent Attempts:</h4>
3
+ {attempts}
4
+ </div>
templates/components/session-achievements.html ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ <div class="session-progress">
2
+ {achievements_content}
3
+ </div>
templates/components/session-enhanced-attempt.html ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div class="attempt-item enhanced">
2
+ <span class="attempt-number">#{attempt_number}</span>
3
+ <div class="attempt-content">
4
+ <div class="attempt-input">"{display_input}"</div>
5
+ <div class="attempt-details">
6
+ <span class="timestamp">{timestamp}</span>
7
+ <span class="ranking-info" style="color: {ranking_color};">{ranking_info}</span>
8
+ </div>
9
+ </div>
10
+ <div class="attempt-metrics">
11
+ <span class="attempt-score" style="color: {score_color};">{cosine_distance:.3f}</span>
12
+ <span class="token-count">{num_tokens} tokens</span>
13
+ </div>
14
+ </div>
templates/components/session-history.html ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ <div class="session-progress">
2
+ <div class="recent-attempts">
3
+ {attempts}
4
+ {more_attempts}
5
+ </div>
6
+ </div>
templates/components/session-main-stats.html ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div class="session-progress">
2
+ <div class="session-stats">
3
+ <div class="stat-item">
4
+ <div class="stat-value">{total_attempts}</div>
5
+ <div class="stat-label">Creative Attempts</div>
6
+ </div>
7
+ <div class="stat-item">
8
+ <div class="stat-value">{best_score:.3f}</div>
9
+ <div class="stat-label">Personal Best</div>
10
+ </div>
11
+ <div class="stat-item">
12
+ <div class="stat-value">{average_score:.3f}</div>
13
+ <div class="stat-label">Average Score</div>
14
+ </div>
15
+ {ranking_stats}
16
+ </div>
17
+
18
+ <div class="session-overview">
19
+ <div class="best-response-showcase">
20
+ <h4>🎯 Your Best Response</h4>
21
+ <div class="best-response-content">
22
+ <span class="best-input">"{best_input}"</span>
23
+ <div class="best-metrics">
24
+ <span class="score-badge">Score: {best_score:.3f}</span>
25
+ {best_ranking}
26
+ </div>
27
+ </div>
28
+ </div>
29
+ {trend_info}
30
+ </div>
31
+ </div>
templates/components/session-page-empty.html ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div class="session-progress">
2
+ <h3>🌟 Your Creative Journey Awaits!</h3>
3
+ <p style="font-size: 18px; margin: 20px 0;">You haven't started your creative adventure yet!</p>
4
+ <p>💡 Once you begin helping me be creative, you'll see:</p>
5
+ <ul style="text-align: left; margin: 20px 0;">
6
+ <li>📊 Your personal progress and community rankings</li>
7
+ <li>🏆 Achievements you've earned</li>
8
+ <li>📈 Your creativity trends and improvement</li>
9
+ <li>🎯 Personal best responses with ranking context</li>
10
+ </ul>
11
+ </div>
templates/components/session-progress-empty.html ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ <div class="session-progress">
2
+ <h3>🎯 Your Session Progress</h3>
3
+ <p>🌟 Welcome! This is your first creative attempt in this session.</p>
4
+ <p>💡 <strong>Tip:</strong> Try different approaches with each prompt to discover what makes the most creative responses!</p>
5
+ </div>
templates/components/session-progress-main.html ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div class="session-progress">
2
+ <h3>🎯 Your Session Progress</h3>
3
+ <div class="session-stats">
4
+ <div class="stat-item">
5
+ <div class="stat-value">{total_attempts}</div>
6
+ <div class="stat-label">Attempts</div>
7
+ </div>
8
+ <div class="stat-item">
9
+ <div class="stat-value">{best_score:.3f}</div>
10
+ <div class="stat-label">Best Score</div>
11
+ </div>
12
+ <div class="stat-item">
13
+ <div class="stat-value">{average_score:.3f}</div>
14
+ <div class="stat-label">Average Score</div>
15
+ </div>
16
+ <div class="stat-item">
17
+ <div class="stat-value">{prompts_tried}</div>
18
+ <div class="stat-label">Prompts Tried</div>
19
+ </div>
20
+ </div>
21
+
22
+ {achievement_badges}
23
+ {recent_html}
24
+
25
+ <div class="session-tip">
26
+ 💡 <strong>Keep experimenting!</strong> Each attempt helps you learn what makes responses more creative.
27
+ </div>
28
+ </div>
uv.lock ADDED
The diff for this file is too large to render. See raw diff