Spaces:
Sleeping
Sleeping
Alon Albalak
commited on
Commit
·
71a764a
1
Parent(s):
391f70f
initial commmit
Browse files- .gitattributes +1 -0
- .gitignore +207 -0
- data/prompts.jsonl +7 -0
- data/results.jsonl +3 -0
- main.py +119 -0
- pyproject.toml +17 -0
- requirements.txt +313 -0
- src/__init__.py +25 -0
- src/config/__init__.py +23 -0
- src/config/settings.py +76 -0
- src/models/__init__.py +7 -0
- src/models/data_manager.py +101 -0
- src/models/llm_manager.py +75 -0
- src/models/similarity_calculator.py +27 -0
- src/scoring/__init__.py +7 -0
- src/scoring/achievements.py +44 -0
- src/scoring/scorer.py +220 -0
- src/scoring/statistics.py +143 -0
- src/session/__init__.py +5 -0
- src/session/session_manager.py +316 -0
- src/ui/__init__.py +7 -0
- src/ui/interface_builder.py +550 -0
- src/ui/page_handlers.py +501 -0
- src/ui/template_renderer.py +20 -0
- static/css/base.css +162 -0
- static/css/components.css +214 -0
- static/css/gallery.css +524 -0
- static/css/game.css +17 -0
- static/css/landing.css +27 -0
- static/css/scores.css +132 -0
- templates/components/achievement-badge.html +1 -0
- templates/components/attempt-item.html +7 -0
- templates/components/inspire-me-empty.html +4 -0
- templates/components/inspire-me-example.html +6 -0
- templates/components/inspire-me-footer.html +5 -0
- templates/components/inspire-me-header.html +1 -0
- templates/components/recent-attempts.html +4 -0
- templates/components/session-achievements.html +3 -0
- templates/components/session-enhanced-attempt.html +14 -0
- templates/components/session-history.html +6 -0
- templates/components/session-main-stats.html +31 -0
- templates/components/session-page-empty.html +11 -0
- templates/components/session-progress-empty.html +5 -0
- templates/components/session-progress-main.html +28 -0
- 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
|
|
|