tina4-python 3.13.88__tar.gz → 3.13.90__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tina4_python-3.13.88 → tina4_python-3.13.90}/PKG-INFO +1 -1
- {tina4_python-3.13.88 → tina4_python-3.13.90}/pyproject.toml +1 -1
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/cli/__init__.py +32 -17
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/frond/engine.py +11 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/frond/parser.py +59 -2
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/public/js/tina4-dev-admin.min.js +2 -2
- {tina4_python-3.13.88 → tina4_python-3.13.90}/.gitignore +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/README.md +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/CLAUDE.md +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/HtmlElement.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/Testing.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/ai/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/api/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/auth/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/cache/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/container/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/context/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/context/chunker.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/core/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/core/cache.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/core/constants.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/core/events.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/core/middleware.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/core/rate_limiter.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/core/request.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/core/response.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/core/router.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/core/server.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/crud/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/database/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/database/adapter.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/database/connection.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/database/firebird.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/database/mongodb.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/database/mssql.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/database/mysql.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/database/odbc.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/database/postgres.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/database/sqlite.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/debug/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/debug/error_overlay.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/dev_admin/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/dev_admin/metrics.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/dev_admin/plan.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/dev_admin/project_index.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/docs.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/docstore/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/dotenv/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/env.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/frond/FROND.md +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/frond/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/frond/compiler.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/gallery/auth/meta.json +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/gallery/database/meta.json +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/gallery/error-overlay/meta.json +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/gallery/orm/meta.json +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/gallery/queue/meta.json +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/gallery/rest-api/meta.json +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/gallery/templates/meta.json +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/graphql/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/i18n/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/mcp/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/mcp/protocol.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/mcp/tools.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/messenger/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/migration/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/migration/runner.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/mqtt/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/mqtt/message.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/orm/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/orm/fields.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/orm/model.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/public/__feedback/widget.js +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/public/css/tina4.css +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/public/css/tina4.min.css +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/public/images/logo.svg +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/public/js/frond.js +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/public/js/frond.min.js +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/public/js/tina4.min.js +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/public/js/tina4js.min.js +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/query_builder/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/queue/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/queue/job.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/queue/kafka_backend.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/queue/lite_backend.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/queue/mongo_backend.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/queue/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/queue_backends/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/queue_backends/kafka_backend.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/queue_backends/mongo_backend.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/realtime/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/realtime/models/Attachment.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/realtime/models/Channel.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/realtime/models/ChannelMember.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/realtime/models/Message.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/realtime/models/Workspace.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/realtime/models/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/realtime/storage.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/scss/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/scss/tina4css/_alerts.scss +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/scss/tina4css/_badges.scss +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/scss/tina4css/_buttons.scss +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/scss/tina4css/_cards.scss +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/scss/tina4css/_forms.scss +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/scss/tina4css/_grid.scss +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/scss/tina4css/_modals.scss +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/scss/tina4css/_nav.scss +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/scss/tina4css/_pagination.scss +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/scss/tina4css/_reset.scss +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/scss/tina4css/_tables.scss +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/scss/tina4css/_typography.scss +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/scss/tina4css/_utilities.scss +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/scss/tina4css/_variables.scss +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/scss/tina4css/base.scss +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/scss/tina4css/colors.scss +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/scss/tina4css/tina4.scss +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/seeder/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/service/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/session/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/session_handlers/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/session_handlers/mongodb_handler.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/session_handlers/redis_handler.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/session_handlers/valkey_handler.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/swagger/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/templates/components/crud.twig +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/templates/docker/python/Dockerfile +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/templates/docker/uv/Dockerfile +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/templates/errors/302.twig +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/templates/errors/401.twig +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/templates/errors/502.twig +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/templates/errors/503.twig +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/templates/errors/base.twig +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/templates/frontend/README.md +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/templates/readme.md +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/test/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/test_client/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/validator/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/websocket/__init__.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/websocket/backplane.py +0 -0
- {tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/wsdl/__init__.py +0 -0
|
@@ -110,6 +110,21 @@ def _parse_fields(fields_str: str) -> list[tuple[str, str]]:
|
|
|
110
110
|
return result
|
|
111
111
|
|
|
112
112
|
|
|
113
|
+
# Called without --fields, the generators fall back to a single `name` string
|
|
114
|
+
# column. That default MUST be materialised here, in one place, and then flow
|
|
115
|
+
# into the model, the migration, the template and the test alike. It used to
|
|
116
|
+
# live only inside the model template, so `generate model X` / `generate crud X`
|
|
117
|
+
# wrote a model declaring `name` while the migration - built from the parsed
|
|
118
|
+
# field list, which was empty - created only id + created_at. The first write
|
|
119
|
+
# then failed with "table x has no column named name".
|
|
120
|
+
DEFAULT_FIELDS: list[tuple[str, str]] = [("name", "string")]
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _fields_or_default(fields_str: str) -> list[tuple[str, str]]:
|
|
124
|
+
"""Parsed --fields, or the default single `name` column when none given."""
|
|
125
|
+
return _parse_fields(fields_str) or list(DEFAULT_FIELDS)
|
|
126
|
+
|
|
127
|
+
|
|
113
128
|
def _parse_flags(args: list[str]) -> tuple[dict, list[str]]:
|
|
114
129
|
"""Parse --key value and --flag from args. Returns (flags, positional)."""
|
|
115
130
|
# Boolean-only flags that never take a value argument
|
|
@@ -1083,7 +1098,7 @@ def _gen_model(name: str, flags: dict, *, emit_test: bool = True):
|
|
|
1083
1098
|
SQLite roundtrip test. Composite generators (crud/auth) pass emit_test=False
|
|
1084
1099
|
and emit their own broader test instead.
|
|
1085
1100
|
"""
|
|
1086
|
-
fields =
|
|
1101
|
+
fields = _fields_or_default(flags.get("fields", ""))
|
|
1087
1102
|
table = _to_table(name)
|
|
1088
1103
|
|
|
1089
1104
|
# Determine which ORM field types we need to import
|
|
@@ -1091,20 +1106,15 @@ def _gen_model(name: str, flags: dict, *, emit_test: bool = True):
|
|
|
1091
1106
|
for _, ftype in fields:
|
|
1092
1107
|
info = FIELD_TYPE_MAP.get(ftype, FIELD_TYPE_MAP["string"])
|
|
1093
1108
|
used_types.add(info["orm"])
|
|
1094
|
-
if not fields:
|
|
1095
|
-
used_types.add("StringField")
|
|
1096
1109
|
used_types.add("DateTimeField") # for created_at
|
|
1097
1110
|
|
|
1098
1111
|
imports = ", ".join(sorted(used_types))
|
|
1099
1112
|
|
|
1100
1113
|
# Build field lines
|
|
1101
1114
|
field_lines = [f" id = IntegerField(primary_key=True, auto_increment=True)"]
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
field_lines.append(f" {fname} = {info['orm']}()")
|
|
1106
|
-
else:
|
|
1107
|
-
field_lines.append(" name = StringField()")
|
|
1115
|
+
for fname, ftype in fields:
|
|
1116
|
+
info = FIELD_TYPE_MAP.get(ftype, FIELD_TYPE_MAP["string"])
|
|
1117
|
+
field_lines.append(f" {fname} = {info['orm']}()")
|
|
1108
1118
|
field_lines.append(" created_at = DateTimeField()")
|
|
1109
1119
|
|
|
1110
1120
|
# Write model file
|
|
@@ -1227,6 +1237,8 @@ async def get_{singular}(request, response):
|
|
|
1227
1237
|
async def create_{singular}(request, response):
|
|
1228
1238
|
"""Create a new {singular}. {write_doc}"""
|
|
1229
1239
|
{ext_create} item = {model}.create(request.body)
|
|
1240
|
+
if item is False:
|
|
1241
|
+
return response({{"error": "Could not create {singular}"}}, 400)
|
|
1230
1242
|
return response(item.to_dict(), 201)
|
|
1231
1243
|
|
|
1232
1244
|
|
|
@@ -1241,7 +1253,8 @@ async def update_{singular}(request, response):
|
|
|
1241
1253
|
{ext_update} for key, value in request.body.items():
|
|
1242
1254
|
if hasattr(item, key) and key != "id":
|
|
1243
1255
|
setattr(item, key, value)
|
|
1244
|
-
item.save()
|
|
1256
|
+
if item.save() is False:
|
|
1257
|
+
return response({{"error": "Could not update {singular}"}}, 400)
|
|
1245
1258
|
return response(item.to_dict())
|
|
1246
1259
|
|
|
1247
1260
|
|
|
@@ -1359,7 +1372,7 @@ def _gen_crud(name: str, flags: dict):
|
|
|
1359
1372
|
|
|
1360
1373
|
tina4python generate crud Product --fields "name:string,price:float"
|
|
1361
1374
|
"""
|
|
1362
|
-
fields =
|
|
1375
|
+
fields = _fields_or_default(flags.get("fields", ""))
|
|
1363
1376
|
table = _to_table(name)
|
|
1364
1377
|
route_name = table + "s" # routes are plural
|
|
1365
1378
|
|
|
@@ -1381,7 +1394,7 @@ def _gen_crud(name: str, flags: dict):
|
|
|
1381
1394
|
template_path = template_dir / f"{route_name}.twig"
|
|
1382
1395
|
if not template_path.exists():
|
|
1383
1396
|
# Build column headers from fields
|
|
1384
|
-
cols = [f for f, _ in fields]
|
|
1397
|
+
cols = [f for f, _ in fields]
|
|
1385
1398
|
th = "\n ".join(f"<th>{c.replace('_', ' ').title()}</th>" for c in cols)
|
|
1386
1399
|
td = "\n ".join(f"<td>{{{{ item.{c} }}}}</td>" for c in cols)
|
|
1387
1400
|
|
|
@@ -1753,7 +1766,9 @@ def _sample_literal(field_type: str) -> str:
|
|
|
1753
1766
|
|
|
1754
1767
|
def _emit_model_test(model: str, table: str, fields: list) -> None:
|
|
1755
1768
|
"""model → real SQLite roundtrip (create / read back / missing → None)."""
|
|
1756
|
-
|
|
1769
|
+
# Reuse the single DEFAULT_FIELDS constant rather than re-stating the literal,
|
|
1770
|
+
# so the co-emitted test can never describe a shape the model does not have.
|
|
1771
|
+
fields = fields or list(DEFAULT_FIELDS)
|
|
1757
1772
|
payload = ", ".join(f'"{fname}": {_sample_literal(ftype)}' for fname, ftype in fields)
|
|
1758
1773
|
# Assert a STRING field round-trips (type-safe); else just the id round-trips
|
|
1759
1774
|
# (avoids datetime/bool/float equality pitfalls on the read-back).
|
|
@@ -2602,7 +2617,7 @@ def _gen_form(name: str, flags: dict = None):
|
|
|
2602
2617
|
tina4python generate form Product --fields "name:string,price:float"
|
|
2603
2618
|
"""
|
|
2604
2619
|
flags = flags or {}
|
|
2605
|
-
fields =
|
|
2620
|
+
fields = _fields_or_default(flags.get("fields", ""))
|
|
2606
2621
|
table = _to_table(name)
|
|
2607
2622
|
route_name = table + "s"
|
|
2608
2623
|
|
|
@@ -2624,7 +2639,7 @@ def _gen_form(name: str, flags: dict = None):
|
|
|
2624
2639
|
|
|
2625
2640
|
# Build form fields
|
|
2626
2641
|
field_html = ""
|
|
2627
|
-
for fname, ftype in
|
|
2642
|
+
for fname, ftype in fields:
|
|
2628
2643
|
itype = input_types.get(ftype, "text")
|
|
2629
2644
|
label = fname.replace("_", " ").title()
|
|
2630
2645
|
step = ' step="0.01"' if ftype in ("float", "numeric", "decimal") else ""
|
|
@@ -2685,14 +2700,14 @@ def _gen_view(name: str, flags: dict = None):
|
|
|
2685
2700
|
tina4python generate view Product --fields "name:string,price:float"
|
|
2686
2701
|
"""
|
|
2687
2702
|
flags = flags or {}
|
|
2688
|
-
fields =
|
|
2703
|
+
fields = _fields_or_default(flags.get("fields", ""))
|
|
2689
2704
|
table = _to_table(name)
|
|
2690
2705
|
route_name = table + "s"
|
|
2691
2706
|
|
|
2692
2707
|
target = Path("src/templates/pages")
|
|
2693
2708
|
target.mkdir(parents=True, exist_ok=True)
|
|
2694
2709
|
|
|
2695
|
-
cols = [f for f, _ in fields]
|
|
2710
|
+
cols = [f for f, _ in fields]
|
|
2696
2711
|
|
|
2697
2712
|
# List view
|
|
2698
2713
|
list_path = target / f"{route_name}.twig"
|
|
@@ -2030,6 +2030,17 @@ class Frond:
|
|
|
2030
2030
|
elif kind == "set":
|
|
2031
2031
|
self._handle_set(node.content, context)
|
|
2032
2032
|
|
|
2033
|
+
elif kind == "set_block":
|
|
2034
|
+
# {% set name %}...{% endset %} -- render the body and bind it,
|
|
2035
|
+
# emitting nothing here. The captured value is a SafeString
|
|
2036
|
+
# because it is template output that has already been escaped
|
|
2037
|
+
# on the way in; re-escaping it at {{ name }} would double-encode
|
|
2038
|
+
# every entity. Twig and Jinja2 both mark the capture safe.
|
|
2039
|
+
if node.name:
|
|
2040
|
+
context[node.name] = SafeString(
|
|
2041
|
+
self._render_nodes(node.body, context)
|
|
2042
|
+
)
|
|
2043
|
+
|
|
2033
2044
|
elif kind == "include":
|
|
2034
2045
|
# Sandbox: check tag
|
|
2035
2046
|
if not (self._sandbox and self._allowed_tags is not None
|
|
@@ -223,6 +223,21 @@ class SetNode:
|
|
|
223
223
|
strip_before: bool = False
|
|
224
224
|
|
|
225
225
|
|
|
226
|
+
@dataclass(slots=True)
|
|
227
|
+
class SetBlockNode:
|
|
228
|
+
"""{% set name %}...{% endset %} — capture the rendered body into ``name``.
|
|
229
|
+
|
|
230
|
+
Core syntax in BOTH reference engines (Twig and Jinja2) and broken in all
|
|
231
|
+
four Tina4 frameworks until 3.13.89: the body rendered inline where it stood
|
|
232
|
+
and the variable was never assigned, so ``{% set g %}hi{% endset %}[{{ g }}]``
|
|
233
|
+
printed ``hi[]`` instead of ``[hi]``.
|
|
234
|
+
"""
|
|
235
|
+
kind = "set_block"
|
|
236
|
+
name: str = ""
|
|
237
|
+
body: list = field(default_factory=list)
|
|
238
|
+
strip_before: bool = False
|
|
239
|
+
|
|
240
|
+
|
|
226
241
|
@dataclass(slots=True)
|
|
227
242
|
class IncludeNode:
|
|
228
243
|
"""{% include "file" [with {...}] [ignore missing] %}."""
|
|
@@ -462,6 +477,28 @@ def _needs_live_strip(tokens: list, index: int, strip_before: bool) -> bool:
|
|
|
462
477
|
return strip_before and index > 0 and tokens[index - 1][0] != TEXT
|
|
463
478
|
|
|
464
479
|
|
|
480
|
+
# Every tag that OPENS a construct. An unknown tag is a typo, and 3.13.89 makes
|
|
481
|
+
# it raise rather than render its body: a mistyped guard -- {% iff is_admin %}
|
|
482
|
+
# instead of {% if is_admin %} -- used to render the gated content
|
|
483
|
+
# UNCONDITIONALLY, so a reviewer saw a guard that was not there. Twig and Jinja2
|
|
484
|
+
# both raise on an unknown tag; Frond now does too. There is no user-extension
|
|
485
|
+
# point for tags in any of the four frameworks, so an unknown name is always a
|
|
486
|
+
# mistake, never a plugin.
|
|
487
|
+
_KNOWN_TAGS = frozenset({
|
|
488
|
+
"autoescape", "block", "cache", "extends", "for", "from", "if", "import",
|
|
489
|
+
"include", "live", "macro", "raw", "set", "spaceless",
|
|
490
|
+
})
|
|
491
|
+
|
|
492
|
+
# Terminators and branch keywords. These reach _parse_block only when stray
|
|
493
|
+
# (their own collector consumes them in the normal case), and a stray one keeps
|
|
494
|
+
# the old render-nothing behaviour -- see the comment at the raise.
|
|
495
|
+
_TERMINATORS = frozenset({
|
|
496
|
+
"elif", "else", "elseif", "endautoescape", "endblock", "endcache",
|
|
497
|
+
"endfor", "endif", "endlive", "endmacro", "endraw", "endset",
|
|
498
|
+
"endspaceless",
|
|
499
|
+
})
|
|
500
|
+
|
|
501
|
+
|
|
465
502
|
def _parse_block(content: str, strip_before: bool, strip_after: bool,
|
|
466
503
|
tokens: list, i: int):
|
|
467
504
|
"""Build the node for the BLOCK token at ``i``. Returns (node, next_index);
|
|
@@ -477,6 +514,16 @@ def _parse_block(content: str, strip_before: bool, strip_after: bool,
|
|
|
477
514
|
return _parse_for(content, strip_before, strip_after, tokens, i)
|
|
478
515
|
|
|
479
516
|
if tag == "set":
|
|
517
|
+
# An assignment has an ``=``; without one this is the BLOCK form,
|
|
518
|
+
# {% set name %}...{% endset %}, which captures its rendered body.
|
|
519
|
+
# A bare ``in`` test is exact here, not a shortcut: the block form's tag
|
|
520
|
+
# content is only ever ``set <name>``, so an ``=`` anywhere -- even
|
|
521
|
+
# inside a quoted value like {% set msg = "a = b" %} -- means assignment.
|
|
522
|
+
if "=" not in content:
|
|
523
|
+
body, end = _collect_body(tokens, i, "set", "endset")
|
|
524
|
+
_bake_strip_after_end(tokens, strip_after, end)
|
|
525
|
+
name = parts[1] if len(parts) > 1 else ""
|
|
526
|
+
return SetBlockNode(name, _parse_nodes(body), strip_before), end
|
|
480
527
|
return SetNode(content, strip_before), i + 1
|
|
481
528
|
|
|
482
529
|
if tag == "include":
|
|
@@ -525,8 +572,18 @@ def _parse_block(content: str, strip_before: bool, strip_after: bool,
|
|
|
525
572
|
if tag in ("block", "endblock"):
|
|
526
573
|
return BlockMarkerNode(parts[1] if len(parts) > 1 else "", strip_before), i + 1
|
|
527
574
|
|
|
528
|
-
|
|
529
|
-
|
|
575
|
+
if tag == "" or tag in _TERMINATORS:
|
|
576
|
+
# An empty tag ({% %}, or a bare whitespace-control {%- -%}) and a
|
|
577
|
+
# stray terminator (an {% endif %} with no {% if %}): no output,
|
|
578
|
+
# advance one token. Malformed, but it has always rendered nothing, and
|
|
579
|
+
# nothing is the safe answer -- unlike an unknown tag it cannot expose
|
|
580
|
+
# content that was meant to be gated.
|
|
581
|
+
return SkipNode(strip_before), i + 1
|
|
582
|
+
|
|
583
|
+
raise ValueError(
|
|
584
|
+
f'Frond: unknown tag "{tag}" -- known tags are: '
|
|
585
|
+
+ ", ".join(sorted(_KNOWN_TAGS))
|
|
586
|
+
)
|
|
530
587
|
|
|
531
588
|
|
|
532
589
|
def _parse_if(content: str, strip_before: bool, strip_after: bool,
|
|
@@ -985,7 +985,7 @@ ${t.traceback||""}`;window.__switchTab("chat"),setTimeout(()=>{window.__prefillC
|
|
|
985
985
|
<span class="thread-pip"></span>
|
|
986
986
|
<span class="thread-title">${g(t.title)}</span>
|
|
987
987
|
${o}
|
|
988
|
-
</div>`}).join("")}let cd=!1;async function Qa(i=0){for(let e=0;;e++)try{Se=await NZ(),cd=!1,EQ();return}catch(t){if(e>=i){cd=!0,console.error("refreshThreadList failed",t);return}await new Promise(n=>setTimeout(n,250*2**e))}}function HZ(i){const e=document.getElementById("editor-ai-messages");if(!e)return;const t=rt.get(i)||[];if(!t.length){e.innerHTML='<div class="ai-msg ai-bot" style="opacity:0.6">Start the conversation…</div>';return}e.innerHTML="";for(const n of t){const r=document.createElement("div");r.className=`ai-msg ai-${n.role==="user"?"user":"bot"}`,n.role==="user"?r.textContent=n.content:r.innerHTML=fs(n.content),e.appendChild(r)}e.scrollTop=e.scrollHeight}async function AQ(i,e=!1){if(!(!e&&rt.has(i)))try{const t=await FZ(i);rt.set(i,t)}catch(t){console.error(`loadThreadMessages(${i}) failed`,t),rt.set(i,[])}}async function dd(i){if(i!==ve){ve=i;try{localStorage.setItem(WQ,i)}catch{}EQ(),await AQ(i),HZ(i)}}async function KZ(){try{const i=await cs();Se.push(i),rt.set(i.id,[]),await dd(i.id);const e=document.getElementById("editor-ai-input");e==null||e.focus()}catch(i){AZ(`<span style="color:var(--danger)">Couldn't create thread: ${g(String((i==null?void 0:i.message)||i))}</span>`,"bot")}}async function JZ(i){const e=document.querySelector(`.thread-row[data-thread-id="${CSS.escape(i)}"]`),t=e==null?void 0:e.querySelector(".thread-title");if(!e||!t)return;const n=t.textContent||"",r=document.createElement("input");r.className="thread-title-edit",r.value=n,t.replaceWith(r),r.focus(),r.select();const s=a=>{const l=document.createElement("span");l.className="thread-title",l.textContent=a,r.replaceWith(l)},o=async()=>{const a=r.value.trim();if(!a||a===n){s(n);return}try{const l=await ga(i,{title:a}),O=Se.find(c=>c.id===i);O&&(O.title=l.title),s(l.title)}catch(l){console.error("rename failed",l),s(n)}};r.addEventListener("blur",()=>{o()}),r.addEventListener("keydown",a=>{a.key==="Enter"?(a.preventDefault(),r.blur()):a.key==="Escape"&&(a.preventDefault(),s(n))})}async function ez(){if(await Qa(4),!Se.length){ve=null,er();return}try{const i=localStorage.getItem(WQ);i&&Se.some(e=>e.id===i)&&(ve=i)}catch{}ya()}window.__editorThreadNew=()=>{KZ()},window.__editorThreadSwitch=i=>{dd(i)},window.__editorThreadRename=i=>{JZ(i)};const tz={done:"DONE",awaiting_customer:"AWAITING YOU",wont_do:"WONT DO",blocked:"BLOCKED",feedback:"NEW FEEDBACK",idle:"IDLE",running:"RUNNING"};function hd(i){const e=tz[i]||i.toUpperCase();return`<span class="status-pill" data-status="${g(i)}">${g(e)}</span>`}function fd(i){if(!i)return"";let e;if(/^\d+Z$/.test(i)?e=new Date(parseInt(i,10)*1e3):e=new Date(i),isNaN(e.getTime()))return i;const t=n=>String(n).padStart(2,"0");return`${t(e.getDate())}/${t(e.getMonth()+1)}/${e.getFullYear()} ${t(e.getHours())}:${t(e.getMinutes())}`}let Jn=null;function ya(){const i=
|
|
988
|
+
</div>`}).join("")}let cd=!1;async function Qa(i=0){for(let e=0;;e++)try{Se=await NZ(),cd=!1,EQ();return}catch(t){if(e>=i){cd=!0,console.error("refreshThreadList failed",t);return}await new Promise(n=>setTimeout(n,250*2**e))}}function HZ(i){const e=document.getElementById("editor-ai-messages");if(!e)return;const t=rt.get(i)||[];if(!t.length){e.innerHTML='<div class="ai-msg ai-bot" style="opacity:0.6">Start the conversation…</div>';return}e.innerHTML="";for(const n of t){const r=document.createElement("div");r.className=`ai-msg ai-${n.role==="user"?"user":"bot"}`,n.role==="user"?r.textContent=n.content:r.innerHTML=fs(n.content),e.appendChild(r)}e.scrollTop=e.scrollHeight}async function AQ(i,e=!1){if(!(!e&&rt.has(i)))try{const t=await FZ(i);rt.set(i,t)}catch(t){console.error(`loadThreadMessages(${i}) failed`,t),rt.set(i,[])}}async function dd(i){if(i!==ve){ve=i;try{localStorage.setItem(WQ,i)}catch{}EQ(),await AQ(i),HZ(i)}}async function KZ(){try{const i=await cs();Se.push(i),rt.set(i.id,[]),await dd(i.id);const e=document.getElementById("editor-ai-input");e==null||e.focus()}catch(i){AZ(`<span style="color:var(--danger)">Couldn't create thread: ${g(String((i==null?void 0:i.message)||i))}</span>`,"bot")}}async function JZ(i){const e=document.querySelector(`.thread-row[data-thread-id="${CSS.escape(i)}"]`),t=e==null?void 0:e.querySelector(".thread-title");if(!e||!t)return;const n=t.textContent||"",r=document.createElement("input");r.className="thread-title-edit",r.value=n,t.replaceWith(r),r.focus(),r.select();const s=a=>{const l=document.createElement("span");l.className="thread-title",l.textContent=a,r.replaceWith(l)},o=async()=>{const a=r.value.trim();if(!a||a===n){s(n);return}try{const l=await ga(i,{title:a}),O=Se.find(c=>c.id===i);O&&(O.title=l.title),s(l.title)}catch(l){console.error("rename failed",l),s(n)}};r.addEventListener("blur",()=>{o()}),r.addEventListener("keydown",a=>{a.key==="Enter"?(a.preventDefault(),r.blur()):a.key==="Escape"&&(a.preventDefault(),s(n))})}async function ez(){if(await Qa(4),!Se.length){ve=null,er();return}try{const i=localStorage.getItem(WQ);i&&Se.some(e=>e.id===i)&&(ve=i)}catch{}ya()}window.__editorThreadNew=()=>{KZ()},window.__editorThreadSwitch=i=>{dd(i)},window.__editorThreadRename=i=>{JZ(i)};const tz={done:"DONE",awaiting_customer:"AWAITING YOU",wont_do:"WONT DO",blocked:"BLOCKED",feedback:"NEW FEEDBACK",idle:"IDLE",running:"RUNNING"};function hd(i){const e=tz[i]||i.toUpperCase();return`<span class="status-pill" data-status="${g(i)}">${g(e)}</span>`}function fd(i){if(!i)return"";let e;if(/^\d+Z$/.test(i)?e=new Date(parseInt(i,10)*1e3):e=new Date(i),isNaN(e.getTime()))return i;const t=n=>String(n).padStart(2,"0");return`${t(e.getDate())}/${t(e.getMonth()+1)}/${e.getFullYear()} ${t(e.getHours())}:${t(e.getMinutes())}`}let Jn=null;function ya(){const i=s=>document.getElementById(s),e=i("threads-pane-head-list");e&&(e.hidden=!1);const t=i("threads-pane-head-detail");t&&(t.hidden=!0);const n=i("threads-list-view");n&&(n.hidden=!1);const r=i("threads-detail-view");r&&(r.hidden=!0),Qa().then(er)}async function ba(i){await dd(i);const e=Se.find(c=>c.id===i);if(!e)return;const t=c=>document.getElementById(c),n=t("threads-pane-head-list");n&&(n.hidden=!0);const r=t("threads-pane-head-detail");r&&(r.hidden=!1);const s=t("threads-list-view");s&&(s.hidden=!0);const o=t("threads-detail-view");o&&(o.hidden=!1);const a=t("threads-detail-title");a&&(a.textContent=e.title||"Thread");const l=t("threads-detail-meta"),O=e.sender?`<span>📨 from ${g(e.sender)}</span>`:"";l&&(l.innerHTML=`${hd(e.status_hint||"idle")} <span>${g(fd(e.last_message_at))}</span> ${O}`),pd(i),setTimeout(()=>{var c;return(c=t("threads-reply-input"))==null?void 0:c.focus()},30)}async function iz(){if(!ve)return;const i=ve;try{await ga(i,{archived:!0,closure_reason:"done"});const e=Se.find(t=>t.id===i);e&&(e.archived=!0,e.closure_reason="done"),ve=null,ya()}catch(e){console.error("archive failed",e)}}async function nz(i){try{await ga(i,{archived:!0});const e=Se.find(t=>t.id===i);e&&(e.archived=!0),ve===i?(ve=null,ya()):er()}catch(e){console.error("archive-from-list failed",e)}}function er(){const i=document.getElementById("threads-rows");if(!i)return;if(!Se.length){i.innerHTML=cd?`<div class="threads-empty">
|
|
989
989
|
<div style="margin-bottom:0.6rem">Can't reach the agent — threads unavailable.</div>
|
|
990
990
|
<button type="button" class="action-pill"
|
|
991
991
|
onclick="window.__threadsRetry()">Retry</button>
|
|
@@ -1012,7 +1012,7 @@ ${t.traceback||""}`;window.__switchTab("chat"),setTimeout(()=>{window.__prefillC
|
|
|
1012
1012
|
<span>severity: ${g(e.severity||"-")}</span>
|
|
1013
1013
|
</div>
|
|
1014
1014
|
<div style="margin-top:0.3rem">${g(e.summary||"")}</div>
|
|
1015
|
-
</div>`}catch{return fs(i)}}async function az(){try{const i=await cs();Se.push(i),rt.set(i.id,[]),await ba(i.id)}catch(i){console.error("threadsNew failed",i)}}async function lz(){if(!ve)return;const i=Se.find(t=>t.id===ve);if(!i)return;const e=window.prompt("Rename thread:",i.title);if(!(e==null||e.trim()===""||e===i.title))try{const t=await ga(ve,{title:e.trim()});i.title=t.title;const n=document.getElementById("threads-detail-title");n&&(n.textContent=t.title),er()}catch(t){console.error("rename failed",t)}}function Oz(i){return i?i.status_hint==="done"||i.status_hint==="wont_do"||i.closure_reason==="done"||i.closure_reason==="wont_do":!1}async function md(){const i=document.getElementById("threads-reply-input");if(!i)return;let e=i.value.trim();if(!e)return;i.value="";const t=e.match(/^new topic:\s*(.*)$/is);if(t){const o=t[1].trim();try{const a=await cs(o.slice(0,80)||void 0);if(Se.push(a),rt.set(a.id,[]),await ba(a.id),!o)return;e=o}catch(a){console.error("New Topic spawn failed",a);return}}if(!t&&ve&&Oz(Se.find(o=>o.id===ve)))try{const o=await cs(e.slice(0,80)||void 0);Se.push(o),rt.set(o.id,[]),await ba(o.id)}catch(o){console.error("auto-new-thread on done reply failed",o);return}if(!ve)try{const o=await cs(e.slice(0,80));Se.push(o),rt.set(o.id,[]),ve=o.id}catch(o){console.error("auto-create failed",o);return}const n=ve,r=rt.get(n)||[];r.push({id:`local-${Date.now()}`,role:"user",content:e,timestamp:new Date().toISOString(),thread_id:n}),rt.set(n,r),pd(n),$a.add(n),er();const s=document.getElementById("threads-chat");Jn==null||Jn.abort(),Jn=new AbortController;try{await pz(e,n,Jn.signal,s)}catch(o){if((o==null?void 0:o.name)!=="AbortError"){const a=document.createElement("div");a.className="ai-msg ai-bot",a.style.color="var(--danger,#f38ba8)",a.textContent=`Connection failed: ${(o==null?void 0:o.message)||o}`,s==null||s.appendChild(a)}}finally{$a.delete(n),Jn=null;try{await AQ(n,!0)}catch{}await Qa(),pd(n);const o=Se.find(
|
|
1015
|
+
</div>`}catch{return fs(i)}}async function az(){try{const i=await cs();Se.push(i),rt.set(i.id,[]),await ba(i.id)}catch(i){console.error("threadsNew failed",i)}}async function lz(){if(!ve)return;const i=Se.find(t=>t.id===ve);if(!i)return;const e=window.prompt("Rename thread:",i.title);if(!(e==null||e.trim()===""||e===i.title))try{const t=await ga(ve,{title:e.trim()});i.title=t.title;const n=document.getElementById("threads-detail-title");n&&(n.textContent=t.title),er()}catch(t){console.error("rename failed",t)}}function Oz(i){return i?i.status_hint==="done"||i.status_hint==="wont_do"||i.closure_reason==="done"||i.closure_reason==="wont_do":!1}async function md(){const i=document.getElementById("threads-reply-input");if(!i)return;let e=i.value.trim();if(!e)return;i.value="";const t=e.match(/^new topic:\s*(.*)$/is);if(t){const o=t[1].trim();try{const a=await cs(o.slice(0,80)||void 0);if(Se.push(a),rt.set(a.id,[]),await ba(a.id),!o)return;e=o}catch(a){console.error("New Topic spawn failed",a);return}}if(!t&&ve&&Oz(Se.find(o=>o.id===ve)))try{const o=await cs(e.slice(0,80)||void 0);Se.push(o),rt.set(o.id,[]),await ba(o.id)}catch(o){console.error("auto-new-thread on done reply failed",o);return}if(!ve)try{const o=await cs(e.slice(0,80));Se.push(o),rt.set(o.id,[]),ve=o.id}catch(o){console.error("auto-create failed",o);return}const n=ve,r=rt.get(n)||[];r.push({id:`local-${Date.now()}`,role:"user",content:e,timestamp:new Date().toISOString(),thread_id:n}),rt.set(n,r),pd(n),$a.add(n),er();const s=document.getElementById("threads-chat");Jn==null||Jn.abort(),Jn=new AbortController;try{await pz(e,n,Jn.signal,s)}catch(o){if((o==null?void 0:o.name)!=="AbortError"){const a=document.createElement("div");a.className="ai-msg ai-bot",a.style.color="var(--danger,#f38ba8)",a.textContent=`Connection failed: ${(o==null?void 0:o.message)||o}`,s==null||s.appendChild(a)}}finally{$a.delete(n),Jn=null;try{await AQ(n,!0)}catch{}await Qa(),pd(n);const o=Se.find(l=>l.id===n),a=document.getElementById("threads-detail-meta");if(o&&a){const l=o.sender?`<span>📨 from ${g(o.sender)}</span>`:"";a.innerHTML=`${hd(o.status_hint||"idle")} <span>${g(fd(o.last_message_at))}</span> ${l}`}}}queueMicrotask(()=>{const i=document.getElementById("threads-reply-form");i&&i.addEventListener("submit",t=>{t.preventDefault(),md()});const e=document.getElementById("threads-reply-input");e&&e.addEventListener("keydown",t=>{t.key==="Enter"&&!t.shiftKey&&(t.preventDefault(),t.stopPropagation(),md())})}),window.__threadsShowList=()=>ya(),window.__threadsShowDetail=i=>{ba(i)},window.__threadsNew=()=>{az()},window.__threadsRetry=()=>{Qa(2).then(er)},window.__threadsRenameActive=()=>{lz()};let un=!1;async function jQ(){const i=document.getElementById("plans-panel"),e=document.getElementById("plans-toggle-btn");i&&(un=!un,i.hidden=!un,e==null||e.classList.toggle("active",un),un&&await cz())}async function cz(){const i=document.getElementById("plans-rows");if(i){i.innerHTML='<div class="threads-empty">Loading plans…</div>';try{const e=await Mi("plan_list",{}),t=e.ok&&Array.isArray(e.result)?e.result:[];if(!t.length){i.innerHTML='<div class="threads-empty">No plans yet — they appear here when the planner agent creates one.</div>';return}const n=[...t].sort((r,s)=>String(s.name||s.file||"").localeCompare(String(r.name||r.file||"")));i.innerHTML=n.map(r=>{var h,f;const s=String(r.name||r.file||""),o=String(r.path||`plan/${s}`),a=String(r.title||s).slice(0,60),l=r.steps_done??((h=r.progress)==null?void 0:h.done)??0,O=r.steps_total??((f=r.progress)==null?void 0:f.total)??0,c=O>0?`${l}/${O} steps`:"",d=r.is_current||r.current?" · ★ current":"";return`<div class="plan-row" onclick="window.__plansOpen('${g(o)}')" title="Open ${g(o)} in editor">
|
|
1016
1016
|
<div class="plan-name">${g(a)}</div>
|
|
1017
1017
|
<div class="plan-meta">${g(s)}${c?" · "+g(c):""}${d}</div>
|
|
1018
1018
|
</div>`}).join("")}catch{i.innerHTML='<div class="threads-empty" style="color:var(--danger,#f38ba8)">Failed to load plans</div>'}}}async function dz(i){var t;try{await ui(i)}catch(n){console.error("plansOpen failed",n)}un=!1;const e=document.getElementById("plans-panel");e&&(e.hidden=!0),(t=document.getElementById("plans-toggle-btn"))==null||t.classList.remove("active")}window.__plansToggle=()=>{jQ()},window.__plansOpen=i=>{dz(i)};let ds=!1;async function hz(){const i=document.getElementById("grounding-panel"),e=document.getElementById("grounding-toggle-btn");i&&(un&&jQ(),ds=!ds,i.hidden=!ds,e==null||e.classList.toggle("active",ds),ds&&await MQ())}async function MQ(){const i=document.getElementById("grounding-body");if(!i)return;i.innerHTML='<div class="threads-empty">Loading…</div>';let e={};try{const r=await fetch("/__dev/api/grounding/status");r.ok&&(e=await r.json())}catch{}const t=g(e.url||"https://mcp.tina4.com"),n=e.configured?`<span style="color:var(--success,#a6e3a1)">● Configured</span> <span style="opacity:0.6">(…${g(e.last4||"")})</span>`:'<span style="color:var(--warn,#f9e2af)">○ Not set</span> — using local corpus fallback';i.innerHTML=`
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/public/images/tina4-logo-icon.webp
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/public/swagger/oauth2-redirect.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/queue_backends/rabbitmq_backend.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/session_handlers/mongodb_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/session_handlers/redis_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/session_handlers/valkey_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/templates/docker/distroless/Dockerfile
RENAMED
|
File without changes
|
{tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/templates/docker/poetry/Dockerfile
RENAMED
|
File without changes
|
{tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/templates/docker/python/Dockerfile
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/translations/af/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/translations/af/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/translations/en/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/translations/en/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/translations/es/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/translations/es/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/translations/fr/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/translations/fr/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/translations/ja/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/translations/ja/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/translations/zh/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.88 → tina4_python-3.13.90}/tina4_python/translations/zh/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|