alloy-runtime-cli 0.1.0__py3-none-any.whl
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.
- alloy_runtime_cli-0.1.0.dist-info/METADATA +61 -0
- alloy_runtime_cli-0.1.0.dist-info/RECORD +451 -0
- alloy_runtime_cli-0.1.0.dist-info/WHEEL +5 -0
- alloy_runtime_cli-0.1.0.dist-info/entry_points.txt +2 -0
- alloy_runtime_cli-0.1.0.dist-info/top_level.txt +1 -0
- cli/__init__.py +0 -0
- cli/commands/__init__.py +0 -0
- cli/commands/admin/__init__.py +0 -0
- cli/commands/admin/bootstrap_command.py +118 -0
- cli/commands/admin/credentials/__init__.py +0 -0
- cli/commands/admin/credentials/create/__init__.py +0 -0
- cli/commands/admin/credentials/create/command.py +148 -0
- cli/commands/admin/credentials/create/presenter.py +16 -0
- cli/commands/admin/credentials/grant/__init__.py +0 -0
- cli/commands/admin/credentials/grant/command.py +119 -0
- cli/commands/admin/credentials/grant/fields.py +33 -0
- cli/commands/admin/credentials/grant/presenter.py +23 -0
- cli/commands/agents/__init__.py +0 -0
- cli/commands/agents/create/__init__.py +0 -0
- cli/commands/agents/create/command.py +475 -0
- cli/commands/agents/create/fields.py +64 -0
- cli/commands/agents/create/presenter.py +68 -0
- cli/commands/agents/delete/__init__.py +0 -0
- cli/commands/agents/delete/command.py +47 -0
- cli/commands/agents/delete/presenter.py +16 -0
- cli/commands/agents/get/command.py +37 -0
- cli/commands/agents/get/presenter.py +32 -0
- cli/commands/agents/list/__init__.py +1 -0
- cli/commands/agents/list/command.py +54 -0
- cli/commands/agents/list/presenter.py +82 -0
- cli/commands/agents/update/__init__.py +0 -0
- cli/commands/agents/update/command.py +435 -0
- cli/commands/agents/update/fields.py +40 -0
- cli/commands/agents/update/presenter.py +68 -0
- cli/commands/audio/__init__.py +0 -0
- cli/commands/audio/transcribe/__init__.py +0 -0
- cli/commands/audio/transcribe/command.py +144 -0
- cli/commands/audio/transcribe/presenter.py +15 -0
- cli/commands/auth/__init__.py +0 -0
- cli/commands/auth/login/__init__.py +0 -0
- cli/commands/auth/login/command.py +80 -0
- cli/commands/auth/signup/__init__.py +0 -0
- cli/commands/auth/signup/command.py +115 -0
- cli/commands/billing/__init__.py +1 -0
- cli/commands/billing/costs/__init__.py +1 -0
- cli/commands/billing/costs/by_agent/__init__.py +1 -0
- cli/commands/billing/costs/by_agent/command.py +57 -0
- cli/commands/billing/costs/by_agent/presenter.py +81 -0
- cli/commands/billing/costs/by_model/__init__.py +1 -0
- cli/commands/billing/costs/by_model/command.py +57 -0
- cli/commands/billing/costs/by_model/presenter.py +80 -0
- cli/commands/billing/costs/daily/__init__.py +1 -0
- cli/commands/billing/costs/daily/command.py +55 -0
- cli/commands/billing/costs/daily/presenter.py +75 -0
- cli/commands/billing/costs/summary/__init__.py +1 -0
- cli/commands/billing/costs/summary/command.py +57 -0
- cli/commands/billing/costs/summary/presenter.py +42 -0
- cli/commands/billing/projects/__init__.py +1 -0
- cli/commands/billing/projects/create/__init__.py +1 -0
- cli/commands/billing/projects/create/command.py +60 -0
- cli/commands/billing/projects/create/presenter.py +26 -0
- cli/commands/billing/projects/get/__init__.py +1 -0
- cli/commands/billing/projects/get/command.py +33 -0
- cli/commands/billing/projects/get/presenter.py +32 -0
- cli/commands/billing/projects/list/__init__.py +1 -0
- cli/commands/billing/projects/list/command.py +40 -0
- cli/commands/billing/projects/list/presenter.py +57 -0
- cli/commands/content/__init__.py +1 -0
- cli/commands/content/delete/__init__.py +0 -0
- cli/commands/content/delete/command.py +49 -0
- cli/commands/content/delete/presenter.py +18 -0
- cli/commands/content/edit/__init__.py +1 -0
- cli/commands/content/edit/command.py +155 -0
- cli/commands/content/edit/editor.py +150 -0
- cli/commands/content/edit/presenter.py +146 -0
- cli/commands/content/get/__init__.py +1 -0
- cli/commands/content/get/command.py +39 -0
- cli/commands/content/get/presenter.py +176 -0
- cli/commands/content/list/__init__.py +1 -0
- cli/commands/content/list/command.py +347 -0
- cli/commands/content/list/export_formatters.py +409 -0
- cli/commands/content/list/export_handler.py +165 -0
- cli/commands/content/list/presenter.py +190 -0
- cli/commands/credentials/__init__.py +0 -0
- cli/commands/credentials/create/__init__.py +0 -0
- cli/commands/credentials/create/command.py +165 -0
- cli/commands/credentials/create/fields.py +38 -0
- cli/commands/credentials/create/presenter.py +20 -0
- cli/commands/credentials/update/__init__.py +0 -0
- cli/commands/credentials/update/command.py +53 -0
- cli/commands/credentials/update/fields.py +71 -0
- cli/commands/credentials/update/presenter.py +16 -0
- cli/commands/flag_utils.py +366 -0
- cli/commands/generate/__init__.py +0 -0
- cli/commands/generate/cancel/__init__.py +1 -0
- cli/commands/generate/cancel/command.py +44 -0
- cli/commands/generate/cancel/presenter.py +26 -0
- cli/commands/generate/status/__init__.py +1 -0
- cli/commands/generate/status/command.py +58 -0
- cli/commands/generate/status/presenter.py +78 -0
- cli/commands/generate/text/__init__.py +0 -0
- cli/commands/generate/text/command.py +1325 -0
- cli/commands/generate/text/concurrent_renderer.py +355 -0
- cli/commands/generate/text/presenter.py +287 -0
- cli/commands/generate/text/stream_renderer.py +129 -0
- cli/commands/knowledge/__init__.py +0 -0
- cli/commands/knowledge/collections/__init__.py +0 -0
- cli/commands/knowledge/collections/cluster/__init__.py +0 -0
- cli/commands/knowledge/collections/cluster/command.py +64 -0
- cli/commands/knowledge/collections/cluster/presenter.py +74 -0
- cli/commands/knowledge/collections/cluster_status/__init__.py +0 -0
- cli/commands/knowledge/collections/cluster_status/command.py +46 -0
- cli/commands/knowledge/collections/cluster_status/presenter.py +10 -0
- cli/commands/knowledge/collections/create/__init__.py +0 -0
- cli/commands/knowledge/collections/create/command.py +137 -0
- cli/commands/knowledge/collections/create/presenter.py +38 -0
- cli/commands/knowledge/collections/delete/__init__.py +1 -0
- cli/commands/knowledge/collections/delete/command.py +47 -0
- cli/commands/knowledge/collections/delete/presenter.py +20 -0
- cli/commands/knowledge/collections/get/__init__.py +1 -0
- cli/commands/knowledge/collections/get/command.py +30 -0
- cli/commands/knowledge/collections/get/presenter.py +44 -0
- cli/commands/knowledge/collections/list/__init__.py +1 -0
- cli/commands/knowledge/collections/list/command.py +41 -0
- cli/commands/knowledge/collections/list/presenter.py +68 -0
- cli/commands/knowledge/collections/update/__init__.py +0 -0
- cli/commands/knowledge/collections/update/command.py +97 -0
- cli/commands/knowledge/collections/update/presenter.py +42 -0
- cli/commands/knowledge/documents/__init__.py +0 -0
- cli/commands/knowledge/documents/bulk_metadata/__init__.py +0 -0
- cli/commands/knowledge/documents/bulk_metadata/command.py +119 -0
- cli/commands/knowledge/documents/bulk_metadata/presenter.py +36 -0
- cli/commands/knowledge/documents/delete/__init__.py +0 -0
- cli/commands/knowledge/documents/delete/command.py +47 -0
- cli/commands/knowledge/documents/delete/presenter.py +20 -0
- cli/commands/knowledge/documents/get/__init__.py +0 -0
- cli/commands/knowledge/documents/get/command.py +39 -0
- cli/commands/knowledge/documents/get/presenter.py +78 -0
- cli/commands/knowledge/documents/ingest/__init__.py +0 -0
- cli/commands/knowledge/documents/ingest/command.py +222 -0
- cli/commands/knowledge/documents/ingest/presenter.py +41 -0
- cli/commands/knowledge/documents/list/__init__.py +0 -0
- cli/commands/knowledge/documents/list/command.py +69 -0
- cli/commands/knowledge/documents/list/presenter.py +86 -0
- cli/commands/knowledge/documents/reingest/__init__.py +0 -0
- cli/commands/knowledge/documents/reingest/command.py +102 -0
- cli/commands/knowledge/documents/reingest/presenter.py +70 -0
- cli/commands/knowledge/documents/update/__init__.py +0 -0
- cli/commands/knowledge/documents/update/command.py +85 -0
- cli/commands/knowledge/documents/update/presenter.py +37 -0
- cli/commands/knowledge/recover/__init__.py +0 -0
- cli/commands/knowledge/recover/command.py +46 -0
- cli/commands/knowledge/recover/presenter.py +79 -0
- cli/commands/knowledge/search/__init__.py +0 -0
- cli/commands/knowledge/search/command.py +218 -0
- cli/commands/knowledge/search/presenter.py +111 -0
- cli/commands/knowledge/synthesis/__init__.py +0 -0
- cli/commands/knowledge/synthesis/create/__init__.py +0 -0
- cli/commands/knowledge/synthesis/create/command.py +127 -0
- cli/commands/knowledge/synthesis/create/presenter.py +33 -0
- cli/commands/knowledge/synthesis/delete/__init__.py +0 -0
- cli/commands/knowledge/synthesis/delete/command.py +53 -0
- cli/commands/knowledge/synthesis/delete/presenter.py +31 -0
- cli/commands/knowledge/synthesis/get/__init__.py +0 -0
- cli/commands/knowledge/synthesis/get/command.py +55 -0
- cli/commands/knowledge/synthesis/get/presenter.py +114 -0
- cli/commands/knowledge/synthesis/list/__init__.py +0 -0
- cli/commands/knowledge/synthesis/list/command.py +132 -0
- cli/commands/knowledge/synthesis/list/presenter.py +84 -0
- cli/commands/knowledge/synthesis/refresh/__init__.py +0 -0
- cli/commands/knowledge/synthesis/refresh/command.py +42 -0
- cli/commands/knowledge/synthesis/refresh/presenter.py +33 -0
- cli/commands/knowledge/synthesis/update/__init__.py +0 -0
- cli/commands/knowledge/synthesis/update/command.py +76 -0
- cli/commands/knowledge/synthesis/update/presenter.py +41 -0
- cli/commands/models/__init__.py +0 -0
- cli/commands/models/list/__init__.py +0 -0
- cli/commands/models/list/command.py +84 -0
- cli/commands/models/list/presenter.py +114 -0
- cli/commands/organizations/__init__.py +0 -0
- cli/commands/organizations/create/command.py +32 -0
- cli/commands/organizations/create/presenter.py +9 -0
- cli/commands/pipelines/__init__.py +1 -0
- cli/commands/pipelines/approvals/__init__.py +1 -0
- cli/commands/pipelines/approvals/decide_command.py +77 -0
- cli/commands/pipelines/approvals/get_command.py +44 -0
- cli/commands/pipelines/approvals/presenter.py +56 -0
- cli/commands/pipelines/costs/__init__.py +1 -0
- cli/commands/pipelines/costs/command.py +57 -0
- cli/commands/pipelines/costs/daily_command.py +54 -0
- cli/commands/pipelines/costs/daily_presenter.py +59 -0
- cli/commands/pipelines/costs/presenter.py +37 -0
- cli/commands/pipelines/create/__init__.py +1 -0
- cli/commands/pipelines/create/command.py +103 -0
- cli/commands/pipelines/create/presenter.py +22 -0
- cli/commands/pipelines/env_vars/__init__.py +1 -0
- cli/commands/pipelines/env_vars/command.py +51 -0
- cli/commands/pipelines/env_vars/presenter.py +16 -0
- cli/commands/pipelines/execute/__init__.py +1 -0
- cli/commands/pipelines/execute/command.py +142 -0
- cli/commands/pipelines/execute/presenter.py +47 -0
- cli/commands/pipelines/executions/__init__.py +1 -0
- cli/commands/pipelines/executions/costs/__init__.py +1 -0
- cli/commands/pipelines/executions/costs/command.py +48 -0
- cli/commands/pipelines/executions/costs/presenter.py +29 -0
- cli/commands/pipelines/executions/costs_by_model/__init__.py +1 -0
- cli/commands/pipelines/executions/costs_by_model/command.py +50 -0
- cli/commands/pipelines/executions/costs_by_model/presenter.py +78 -0
- cli/commands/pipelines/executions/costs_by_step/__init__.py +1 -0
- cli/commands/pipelines/executions/costs_by_step/command.py +50 -0
- cli/commands/pipelines/executions/costs_by_step/presenter.py +72 -0
- cli/commands/pipelines/executions/get_command.py +38 -0
- cli/commands/pipelines/executions/list_command.py +123 -0
- cli/commands/pipelines/executions/presenter.py +131 -0
- cli/commands/pipelines/executions/rerun_command.py +41 -0
- cli/commands/pipelines/executions/update/__init__.py +1 -0
- cli/commands/pipelines/executions/update/command.py +110 -0
- cli/commands/pipelines/executions/update/presenter.py +28 -0
- cli/commands/pipelines/get/__init__.py +1 -0
- cli/commands/pipelines/get/command.py +33 -0
- cli/commands/pipelines/get/presenter.py +48 -0
- cli/commands/pipelines/list/__init__.py +1 -0
- cli/commands/pipelines/list/command.py +53 -0
- cli/commands/pipelines/list/presenter.py +66 -0
- cli/commands/pipelines/schedules/__init__.py +1 -0
- cli/commands/pipelines/schedules/create_command.py +119 -0
- cli/commands/pipelines/schedules/create_presenter.py +35 -0
- cli/commands/pipelines/schedules/delete_command.py +52 -0
- cli/commands/pipelines/schedules/env_vars_command.py +59 -0
- cli/commands/pipelines/schedules/env_vars_presenter.py +16 -0
- cli/commands/pipelines/schedules/get_command.py +38 -0
- cli/commands/pipelines/schedules/list_command.py +33 -0
- cli/commands/pipelines/schedules/once_command.py +90 -0
- cli/commands/pipelines/schedules/once_presenter.py +30 -0
- cli/commands/pipelines/schedules/presenter.py +104 -0
- cli/commands/pipelines/schedules/update_command.py +139 -0
- cli/commands/pipelines/schedules/update_presenter.py +29 -0
- cli/commands/render/__init__.py +0 -0
- cli/commands/render/html_to_image/__init__.py +0 -0
- cli/commands/render/html_to_image/command.py +170 -0
- cli/commands/schemas/__init__.py +0 -0
- cli/commands/schemas/create/__init__.py +0 -0
- cli/commands/schemas/create/command.py +122 -0
- cli/commands/schemas/create/presenter.py +53 -0
- cli/commands/schemas/delete/command.py +45 -0
- cli/commands/schemas/delete/presenter.py +9 -0
- cli/commands/schemas/get/__init__.py +0 -0
- cli/commands/schemas/get/command.py +56 -0
- cli/commands/schemas/get/presenter.py +129 -0
- cli/commands/schemas/list/__init__.py +0 -0
- cli/commands/schemas/list/command.py +64 -0
- cli/commands/schemas/list/presenter.py +133 -0
- cli/commands/schemas/update/__init__.py +0 -0
- cli/commands/schemas/update/command.py +369 -0
- cli/commands/schemas/update/presenter.py +53 -0
- cli/commands/sessions/__init__.py +1 -0
- cli/commands/sessions/delete/__init__.py +1 -0
- cli/commands/sessions/delete/command.py +47 -0
- cli/commands/sessions/delete/presenter.py +10 -0
- cli/commands/sessions/get/__init__.py +1 -0
- cli/commands/sessions/get/command.py +42 -0
- cli/commands/sessions/get/presenter.py +59 -0
- cli/commands/sessions/list/__init__.py +1 -0
- cli/commands/sessions/list/command.py +61 -0
- cli/commands/sessions/list/presenter.py +68 -0
- cli/commands/sessions/messages/__init__.py +1 -0
- cli/commands/sessions/messages/command.py +78 -0
- cli/commands/sessions/messages/presenter.py +79 -0
- cli/commands/shared_flags.py +500 -0
- cli/commands/sync/__init__.py +0 -0
- cli/commands/sync/command.py +45 -0
- cli/commands/sync/presenter.py +49 -0
- cli/commands/tags/__init__.py +1 -0
- cli/commands/tags/create/__init__.py +1 -0
- cli/commands/tags/create/command.py +60 -0
- cli/commands/tags/delete/__init__.py +1 -0
- cli/commands/tags/delete/command.py +47 -0
- cli/commands/tags/delete/presenter.py +10 -0
- cli/commands/tags/get/command.py +31 -0
- cli/commands/tags/get/presenter.py +23 -0
- cli/commands/tags/list/__init__.py +1 -0
- cli/commands/tags/list/command.py +52 -0
- cli/commands/tags/list/presenter.py +49 -0
- cli/commands/tags/update/command.py +64 -0
- cli/commands/tags/update/presenter.py +9 -0
- cli/commands/templates/__init__.py +0 -0
- cli/commands/templates/create/__init__.py +0 -0
- cli/commands/templates/create/command.py +152 -0
- cli/commands/templates/create/presenter.py +86 -0
- cli/commands/templates/delete/__init__.py +0 -0
- cli/commands/templates/delete/command.py +47 -0
- cli/commands/templates/delete/presenter.py +16 -0
- cli/commands/templates/get/__init__.py +0 -0
- cli/commands/templates/get/command.py +52 -0
- cli/commands/templates/get/presenter.py +233 -0
- cli/commands/templates/get_by_version/command.py +32 -0
- cli/commands/templates/get_by_version/presenter.py +30 -0
- cli/commands/templates/list/__init__.py +1 -0
- cli/commands/templates/list/command.py +102 -0
- cli/commands/templates/list/presenter.py +93 -0
- cli/commands/templates/render/__init__.py +0 -0
- cli/commands/templates/render/command.py +115 -0
- cli/commands/templates/render/presenter.py +276 -0
- cli/commands/templates/update/__init__.py +0 -0
- cli/commands/templates/update/command.py +199 -0
- cli/commands/templates/update/presenter.py +94 -0
- cli/commands/templates/version/__init__.py +1 -0
- cli/commands/templates/version/command.py +116 -0
- cli/commands/templates/version/presenter.py +100 -0
- cli/commands/tool_configs/__init__.py +0 -0
- cli/commands/tool_configs/create/__init__.py +0 -0
- cli/commands/tool_configs/create/command.py +118 -0
- cli/commands/tool_configs/create/presenter.py +53 -0
- cli/commands/tool_configs/delete/__init__.py +0 -0
- cli/commands/tool_configs/delete/command.py +47 -0
- cli/commands/tool_configs/delete/presenter.py +18 -0
- cli/commands/tool_configs/get/__init__.py +0 -0
- cli/commands/tool_configs/get/command.py +31 -0
- cli/commands/tool_configs/get/presenter.py +62 -0
- cli/commands/tool_configs/list/__init__.py +0 -0
- cli/commands/tool_configs/list/command.py +59 -0
- cli/commands/tool_configs/list/presenter.py +60 -0
- cli/commands/tool_configs/update/__init__.py +0 -0
- cli/commands/tool_configs/update/command.py +128 -0
- cli/commands/tool_configs/update/presenter.py +53 -0
- cli/commands/tools/__init__.py +1 -0
- cli/commands/tools/get/__init__.py +1 -0
- cli/commands/tools/get/command.py +42 -0
- cli/commands/tools/get/presenter.py +45 -0
- cli/commands/tools/list/__init__.py +1 -0
- cli/commands/tools/list/command.py +56 -0
- cli/commands/tools/list/presenter.py +44 -0
- cli/commands/users/__init__.py +0 -0
- cli/commands/users/create/command.py +53 -0
- cli/commands/users/create/presenter.py +9 -0
- cli/commands/whoami/__init__.py +0 -0
- cli/commands/whoami/command.py +42 -0
- cli/infrastructure/__init__.py +0 -0
- cli/infrastructure/auth_storage.py +71 -0
- cli/infrastructure/client_factory.py +36 -0
- cli/infrastructure/command.py +75 -0
- cli/infrastructure/config.py +188 -0
- cli/infrastructure/console.py +27 -0
- cli/infrastructure/editor.py +138 -0
- cli/infrastructure/error_display.py +178 -0
- cli/infrastructure/field_extractor.py +360 -0
- cli/infrastructure/file_content.py +210 -0
- cli/infrastructure/filter_parser.py +256 -0
- cli/infrastructure/formatters/__init__.py +0 -0
- cli/infrastructure/formatters/base.py +99 -0
- cli/infrastructure/formatters/compact_formatter.py +245 -0
- cli/infrastructure/formatters/json_formatter.py +84 -0
- cli/infrastructure/formatters/lines_formatter.py +102 -0
- cli/infrastructure/formatting/__init__.py +0 -0
- cli/infrastructure/formatting/fields.py +193 -0
- cli/infrastructure/forms/__init__.py +0 -0
- cli/infrastructure/forms/agent_picker.py +123 -0
- cli/infrastructure/forms/agent_tool_editor.py +384 -0
- cli/infrastructure/forms/agent_tools_manager.py +212 -0
- cli/infrastructure/forms/base_picker.py +469 -0
- cli/infrastructure/forms/components.py +126 -0
- cli/infrastructure/forms/json_schema_builder.py +149 -0
- cli/infrastructure/forms/model_picker.py +134 -0
- cli/infrastructure/forms/parsers.py +173 -0
- cli/infrastructure/forms/resolution_modal.py +302 -0
- cli/infrastructure/forms/schema_picker.py +137 -0
- cli/infrastructure/forms/tag_management_modal.py +103 -0
- cli/infrastructure/forms/tag_picker.py +207 -0
- cli/infrastructure/forms/template_picker.py +131 -0
- cli/infrastructure/forms/tool_config_picker.py +130 -0
- cli/infrastructure/forms/tool_picker.py +103 -0
- cli/infrastructure/injection/__init__.py +0 -0
- cli/infrastructure/injection/parser.py +302 -0
- cli/infrastructure/injection/resolver.py +399 -0
- cli/infrastructure/kv_parser.py +130 -0
- cli/infrastructure/local_storage.py +227 -0
- cli/infrastructure/macro_parser.py +215 -0
- cli/infrastructure/output.py +192 -0
- cli/infrastructure/provider_setup.py +81 -0
- cli/infrastructure/renderers/__init__.py +0 -0
- cli/infrastructure/renderers/entity_renderer.py +77 -0
- cli/infrastructure/renderers/list_renderer.py +114 -0
- cli/infrastructure/scope_utils.py +47 -0
- cli/infrastructure/spinner.py +101 -0
- cli/infrastructure/tui/__init__.py +0 -0
- cli/infrastructure/tui/clipboard.py +41 -0
- cli/infrastructure/tui/formatters.py +105 -0
- cli/infrastructure/tui/preview.py +14 -0
- cli/infrastructure/tui/selectable.py +198 -0
- cli/infrastructure/validation/__init__.py +0 -0
- cli/infrastructure/validation/tag_validation.py +74 -0
- cli/main.py +759 -0
- cli/tui/__init__.py +0 -0
- cli/tui/app.py +199 -0
- cli/tui/app_store.py +73 -0
- cli/tui/chat/__init__.py +0 -0
- cli/tui/chat/commands/__init__.py +0 -0
- cli/tui/chat/commands/base.py +65 -0
- cli/tui/chat/commands/create_session.py +135 -0
- cli/tui/chat/commands/load_session.py +119 -0
- cli/tui/chat/commands/regenerate.py +120 -0
- cli/tui/chat/commands/reload_session.py +63 -0
- cli/tui/chat/commands/send_message.py +190 -0
- cli/tui/chat/commands/undo.py +66 -0
- cli/tui/chat/editor.py +71 -0
- cli/tui/chat/messages.py +223 -0
- cli/tui/chat/pane.py +141 -0
- cli/tui/chat/renderers/__init__.py +0 -0
- cli/tui/chat/renderers/base.py +72 -0
- cli/tui/chat/renderers/markdown.py +250 -0
- cli/tui/chat/renderers/plain.py +83 -0
- cli/tui/chat/screen.py +1155 -0
- cli/tui/chat/services/__init__.py +0 -0
- cli/tui/chat/services/injection.py +386 -0
- cli/tui/chat/services/name_generator.py +256 -0
- cli/tui/chat/slash_commands.py +424 -0
- cli/tui/chat/store.py +280 -0
- cli/tui/chat/types.py +220 -0
- cli/tui/chat/widgets/__init__.py +0 -0
- cli/tui/chat/widgets/chat_header.py +75 -0
- cli/tui/chat/widgets/chat_input.py +362 -0
- cli/tui/chat/widgets/injection_popup.py +161 -0
- cli/tui/chat/widgets/message_display.py +287 -0
- cli/tui/chat/widgets/session_sidebar.py +214 -0
- cli/tui/chat/widgets/welcome_screen.py +290 -0
- cli/tui/screens/__init__.py +0 -0
- cli/tui/screens/agents.py +344 -0
- cli/tui/screens/base.py +301 -0
- cli/tui/screens/content.py +508 -0
- cli/tui/screens/dashboard.py +89 -0
- cli/tui/screens/models.py +96 -0
- cli/tui/screens/nav_screen.py +186 -0
- cli/tui/screens/schemas.py +522 -0
- cli/tui/screens/templates.py +734 -0
- cli/tui/screens/tool_configs.py +335 -0
- cli/tui/styles/__init__.py +0 -0
- cli/tui/widgets/__init__.py +0 -0
- cli/tui/widgets/agent_create_modal.py +139 -0
- cli/tui/widgets/agent_form_modal.py +659 -0
- cli/tui/widgets/agent_update_modal.py +299 -0
- cli/tui/widgets/base_form_modal.py +77 -0
- cli/tui/widgets/confirm_modal.py +75 -0
- cli/tui/widgets/help_modal.py +145 -0
- cli/tui/widgets/new_session_modal.py +328 -0
- cli/tui/widgets/schema_create_modal.py +271 -0
- cli/tui/widgets/schema_update_modal.py +188 -0
- cli/tui/widgets/status_footer.py +147 -0
- cli/tui/widgets/template_create_modal.py +502 -0
- cli/tui/widgets/template_update_modal.py +308 -0
- cli/tui/widgets/tool_config_create_modal.py +216 -0
- cli/tui/widgets/tool_config_update_modal.py +208 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
"""Resolution modal for macro-to-value resolution.
|
|
2
|
+
|
|
3
|
+
Provides a modal screen that allows users to search and select
|
|
4
|
+
the target for a macro (fragment template or content part).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from textual import on, work
|
|
11
|
+
from textual.app import ComposeResult
|
|
12
|
+
from textual.binding import Binding
|
|
13
|
+
from textual.containers import Vertical
|
|
14
|
+
from textual.screen import ModalScreen
|
|
15
|
+
from textual.widgets import Button, Input, Label, OptionList, Static
|
|
16
|
+
from textual.widgets.option_list import Option
|
|
17
|
+
|
|
18
|
+
from alloy_runtime_sdk.api_client.client import ApiClient
|
|
19
|
+
from alloy_runtime_types.enums.content_enums import ContentStorageType
|
|
20
|
+
from alloy_runtime_types.enums.template_enums import TemplateContentType
|
|
21
|
+
from alloy_runtime_sdk.exceptions.errors import RequestError
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ResolutionModal(ModalScreen[dict[str, Any] | None]):
|
|
25
|
+
"""Modal for resolving a macro to a specific template or content part.
|
|
26
|
+
|
|
27
|
+
For fragment macros: searches templates and returns the fragment name
|
|
28
|
+
For text/json macros: searches content parts and returns the UUID
|
|
29
|
+
|
|
30
|
+
Usage:
|
|
31
|
+
def on_resolution_complete(result: dict | None):
|
|
32
|
+
if result:
|
|
33
|
+
# result["value"] is the resolved value (name or UUID)
|
|
34
|
+
# result["display"] is a human-readable display string
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
self.push_screen(
|
|
38
|
+
ResolutionModal(
|
|
39
|
+
client=server_client,
|
|
40
|
+
macro_type="fragment",
|
|
41
|
+
query="header",
|
|
42
|
+
),
|
|
43
|
+
on_resolution_complete
|
|
44
|
+
)
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
BINDINGS = [
|
|
48
|
+
Binding("escape", "cancel", "Cancel", show=True),
|
|
49
|
+
Binding("enter", "select", "Select", show=True),
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
client: ApiClient,
|
|
55
|
+
macro_type: str,
|
|
56
|
+
query: str,
|
|
57
|
+
**kwargs: Any,
|
|
58
|
+
) -> None:
|
|
59
|
+
"""Initialize the resolution modal.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
client: ApiClient instance for API calls
|
|
63
|
+
macro_type: Type of macro being resolved (fragment, text, json, schema)
|
|
64
|
+
query: Initial search query from the macro
|
|
65
|
+
"""
|
|
66
|
+
super().__init__(**kwargs)
|
|
67
|
+
self._client = client
|
|
68
|
+
self._macro_type = macro_type
|
|
69
|
+
self._initial_query = query
|
|
70
|
+
self._search_results: list[dict[str, Any]] = []
|
|
71
|
+
self._last_query: str = ""
|
|
72
|
+
|
|
73
|
+
def compose(self) -> ComposeResult:
|
|
74
|
+
type_label = {
|
|
75
|
+
"fragment": "Fragment Template",
|
|
76
|
+
"text": "Content Part (Text)",
|
|
77
|
+
"json": "Content Part (JSON)",
|
|
78
|
+
"schema": "Schema (JSON/Regex)",
|
|
79
|
+
}.get(self._macro_type, self._macro_type)
|
|
80
|
+
|
|
81
|
+
with Vertical():
|
|
82
|
+
yield Label(f"Resolve @{self._macro_type}(...)", classes="modal-title")
|
|
83
|
+
yield Static(f"Searching for: {type_label}", classes="macro-info")
|
|
84
|
+
yield Input(
|
|
85
|
+
value=self._initial_query,
|
|
86
|
+
placeholder="Type to search...",
|
|
87
|
+
id="search-input",
|
|
88
|
+
)
|
|
89
|
+
yield OptionList(id="results-list")
|
|
90
|
+
yield Static("Type to search...", classes="status-line", id="status")
|
|
91
|
+
with Vertical(classes="button-row"):
|
|
92
|
+
yield Button("Select", id="select-btn", variant="success")
|
|
93
|
+
yield Button("Skip", id="skip-btn", variant="warning")
|
|
94
|
+
|
|
95
|
+
def on_mount(self) -> None:
|
|
96
|
+
"""Focus the search input and trigger initial search."""
|
|
97
|
+
self.query_one("#search-input", Input).focus()
|
|
98
|
+
# Trigger initial search with the query from the macro
|
|
99
|
+
if self._initial_query:
|
|
100
|
+
self._search(self._initial_query)
|
|
101
|
+
|
|
102
|
+
@on(Input.Changed, "#search-input")
|
|
103
|
+
def on_search_changed(self, event: Input.Changed) -> None:
|
|
104
|
+
"""Search on input change."""
|
|
105
|
+
query = event.value.strip()
|
|
106
|
+
if query != self._last_query:
|
|
107
|
+
self._last_query = query
|
|
108
|
+
self._search(query)
|
|
109
|
+
|
|
110
|
+
@work(exclusive=True)
|
|
111
|
+
async def _search(self, query: str) -> None:
|
|
112
|
+
"""Fetch results from the appropriate API endpoint."""
|
|
113
|
+
status = self.query_one("#status", Static)
|
|
114
|
+
options = self.query_one("#results-list", OptionList)
|
|
115
|
+
|
|
116
|
+
if not query:
|
|
117
|
+
status.update("Type to search...")
|
|
118
|
+
options.clear_options()
|
|
119
|
+
self._search_results = []
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
status.update("Searching...")
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
if self._macro_type == "fragment":
|
|
126
|
+
results = await self._search_templates(query)
|
|
127
|
+
elif self._macro_type == "schema":
|
|
128
|
+
results = await self._search_schemas(query)
|
|
129
|
+
else:
|
|
130
|
+
results = await self._search_content_parts(query)
|
|
131
|
+
|
|
132
|
+
self._search_results = results
|
|
133
|
+
self._update_options()
|
|
134
|
+
|
|
135
|
+
count = len(results)
|
|
136
|
+
if count == 0:
|
|
137
|
+
status.update("No results found")
|
|
138
|
+
else:
|
|
139
|
+
status.update(f"{count} result{'s' if count != 1 else ''} found")
|
|
140
|
+
|
|
141
|
+
except RequestError as e:
|
|
142
|
+
status.update(f"[red]Error: {e}[/]")
|
|
143
|
+
self._search_results = []
|
|
144
|
+
options.clear_options()
|
|
145
|
+
except Exception as e:
|
|
146
|
+
status.update(f"[red]Error: {e}[/]")
|
|
147
|
+
self._search_results = []
|
|
148
|
+
options.clear_options()
|
|
149
|
+
|
|
150
|
+
async def _search_templates(self, query: str) -> list[dict[str, Any]]:
|
|
151
|
+
"""Search for fragment templates."""
|
|
152
|
+
response = await self._client.list_templates(
|
|
153
|
+
search=query,
|
|
154
|
+
content_type=TemplateContentType.FRAGMENT,
|
|
155
|
+
limit=50,
|
|
156
|
+
)
|
|
157
|
+
return [template.model_dump() for template in response.templates]
|
|
158
|
+
|
|
159
|
+
async def _search_content_parts(self, query: str) -> list[dict[str, Any]]:
|
|
160
|
+
"""Search for content parts filtered by storage type based on macro type."""
|
|
161
|
+
# Determine storage type filter based on macro type
|
|
162
|
+
# text needs content_text (storage_type=text)
|
|
163
|
+
# json needs content_structured (storage_type=structured)
|
|
164
|
+
storage_type = (
|
|
165
|
+
ContentStorageType.TEXT
|
|
166
|
+
if self._macro_type == "text"
|
|
167
|
+
else ContentStorageType.STRUCTURED
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
response = await self._client.list_content_parts(
|
|
171
|
+
search=query,
|
|
172
|
+
storage_type=storage_type,
|
|
173
|
+
limit=50,
|
|
174
|
+
)
|
|
175
|
+
return [part.model_dump() for part in response.content_parts]
|
|
176
|
+
|
|
177
|
+
async def _search_schemas(self, query: str) -> list[dict[str, Any]]:
|
|
178
|
+
"""Search for schemas."""
|
|
179
|
+
response = await self._client.list_schemas(search=query, limit=50)
|
|
180
|
+
return [schema.model_dump() for schema in response.schemas]
|
|
181
|
+
|
|
182
|
+
def _update_options(self) -> None:
|
|
183
|
+
"""Update the option list with current search results."""
|
|
184
|
+
options = self.query_one("#results-list", OptionList)
|
|
185
|
+
options.clear_options()
|
|
186
|
+
|
|
187
|
+
for i, item in enumerate(self._search_results):
|
|
188
|
+
display_text = self._format_result(item)
|
|
189
|
+
options.add_option(Option(display_text, id=f"opt-{i}"))
|
|
190
|
+
|
|
191
|
+
if self._search_results:
|
|
192
|
+
options.highlighted = 0
|
|
193
|
+
|
|
194
|
+
def _format_result(self, item: dict[str, Any]) -> str:
|
|
195
|
+
"""Format a search result for display."""
|
|
196
|
+
if self._macro_type == "fragment":
|
|
197
|
+
# Template result
|
|
198
|
+
name = item.get("name", "Unknown")
|
|
199
|
+
version = item.get("latest_version_number", "?")
|
|
200
|
+
visibility = item.get("visibility_id", "private")
|
|
201
|
+
vis_label = {"private": "Priv", "organization": "Org", "public": "Pub"}.get(
|
|
202
|
+
visibility, visibility[:4]
|
|
203
|
+
)
|
|
204
|
+
return f"{name} (v{version}) [{vis_label}]"
|
|
205
|
+
elif self._macro_type == "schema":
|
|
206
|
+
# Schema result
|
|
207
|
+
name = item.get("schema_name", "Unknown")
|
|
208
|
+
version = item.get("version", "?")
|
|
209
|
+
format_id = item.get("schema_format_id", "unknown")
|
|
210
|
+
description = item.get("description") or ""
|
|
211
|
+
desc_preview = (
|
|
212
|
+
(
|
|
213
|
+
f" - {description[:30]}..."
|
|
214
|
+
if len(description) > 30
|
|
215
|
+
else f" - {description}"
|
|
216
|
+
)
|
|
217
|
+
if description
|
|
218
|
+
else ""
|
|
219
|
+
)
|
|
220
|
+
return f"{name} (v{version}) [{format_id}]{desc_preview}"
|
|
221
|
+
else:
|
|
222
|
+
# Content part result
|
|
223
|
+
content_type = item.get("content_type_id", "unknown")
|
|
224
|
+
part_id = str(item.get("id", ""))[:8]
|
|
225
|
+
|
|
226
|
+
# Get preview based on storage type
|
|
227
|
+
if self._macro_type == "text":
|
|
228
|
+
content_text = item.get("content_text") or ""
|
|
229
|
+
preview = (
|
|
230
|
+
content_text[:50] + "..."
|
|
231
|
+
if len(content_text) > 50
|
|
232
|
+
else content_text
|
|
233
|
+
)
|
|
234
|
+
preview = preview.replace("\n", " ")
|
|
235
|
+
else: # json
|
|
236
|
+
content_structured = item.get("content_structured")
|
|
237
|
+
if content_structured:
|
|
238
|
+
preview = json.dumps(content_structured)[:50] + "..."
|
|
239
|
+
else:
|
|
240
|
+
preview = "(no structured content)"
|
|
241
|
+
|
|
242
|
+
return f"[{content_type}] {preview} ({part_id}...)"
|
|
243
|
+
|
|
244
|
+
def _get_selected_value(self) -> dict[str, Any] | None:
|
|
245
|
+
"""Get the resolved value for the currently selected item."""
|
|
246
|
+
options = self.query_one("#results-list", OptionList)
|
|
247
|
+
if options.highlighted is None:
|
|
248
|
+
return None
|
|
249
|
+
|
|
250
|
+
index = options.highlighted
|
|
251
|
+
if index < 0 or index >= len(self._search_results):
|
|
252
|
+
return None
|
|
253
|
+
|
|
254
|
+
item = self._search_results[index]
|
|
255
|
+
|
|
256
|
+
if self._macro_type == "fragment":
|
|
257
|
+
# Return the fragment NAME (not UUID) - this is what the renderer expects
|
|
258
|
+
return {
|
|
259
|
+
"value": item.get("name"),
|
|
260
|
+
"display": self._format_result(item),
|
|
261
|
+
"item": item,
|
|
262
|
+
}
|
|
263
|
+
elif self._macro_type == "schema":
|
|
264
|
+
# Return the schema NAME - renderer supports name lookups with priority ordering
|
|
265
|
+
return {
|
|
266
|
+
"value": item.get("schema_name"),
|
|
267
|
+
"display": self._format_result(item),
|
|
268
|
+
"item": item,
|
|
269
|
+
}
|
|
270
|
+
else:
|
|
271
|
+
# Return the content part UUID
|
|
272
|
+
return {
|
|
273
|
+
"value": str(item.get("id")),
|
|
274
|
+
"display": self._format_result(item),
|
|
275
|
+
"item": item,
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
@on(OptionList.OptionSelected, "#results-list")
|
|
279
|
+
def on_option_selected(self, event: OptionList.OptionSelected) -> None:
|
|
280
|
+
"""Handle double-click or enter on option."""
|
|
281
|
+
self.action_select()
|
|
282
|
+
|
|
283
|
+
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
284
|
+
"""Handle button presses."""
|
|
285
|
+
if event.button.id == "select-btn":
|
|
286
|
+
self.action_select()
|
|
287
|
+
elif event.button.id == "skip-btn":
|
|
288
|
+
self.action_cancel()
|
|
289
|
+
|
|
290
|
+
def action_select(self) -> None:
|
|
291
|
+
"""Select the current item and dismiss."""
|
|
292
|
+
result = self._get_selected_value()
|
|
293
|
+
if result:
|
|
294
|
+
self.dismiss(result)
|
|
295
|
+
else:
|
|
296
|
+
# No selection - show feedback
|
|
297
|
+
status = self.query_one("#status", Static)
|
|
298
|
+
status.update("[yellow]Select an item first[/]")
|
|
299
|
+
|
|
300
|
+
def action_cancel(self) -> None:
|
|
301
|
+
"""Cancel resolution and dismiss with None."""
|
|
302
|
+
self.dismiss(None)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Schema picker component for selecting input/output schemas.
|
|
2
|
+
|
|
3
|
+
A specialized search picker that uses the ApiClient to fetch schemas
|
|
4
|
+
and returns the selected schema's ID.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from alloy_runtime_sdk.api_client.client import ApiClient
|
|
10
|
+
|
|
11
|
+
from cli.infrastructure.forms.base_picker import SingleSelectPicker
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _format_schema(schema: dict[str, Any]) -> str:
|
|
15
|
+
"""Format a schema for display in the picker."""
|
|
16
|
+
name = schema.get("schema_name", "Unknown")
|
|
17
|
+
version = schema.get("version", "?")
|
|
18
|
+
schema_format = schema.get("schema_format_id", "unknown")
|
|
19
|
+
|
|
20
|
+
# Determine scope from ownership fields
|
|
21
|
+
user_id = schema.get("user_id")
|
|
22
|
+
org_id = schema.get("organization_id")
|
|
23
|
+
if user_id:
|
|
24
|
+
scope = "User"
|
|
25
|
+
elif org_id:
|
|
26
|
+
scope = "Org"
|
|
27
|
+
else:
|
|
28
|
+
scope = "Global"
|
|
29
|
+
|
|
30
|
+
# Truncate description if present
|
|
31
|
+
description = schema.get("description")
|
|
32
|
+
desc_part = ""
|
|
33
|
+
if description:
|
|
34
|
+
if len(description) > 30:
|
|
35
|
+
description = description[:27] + "..."
|
|
36
|
+
desc_part = f" - {description}"
|
|
37
|
+
|
|
38
|
+
return f"{name} (v{version}) [{schema_format}] {scope}{desc_part}"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class SchemaPicker(SingleSelectPicker[dict[str, Any]]):
|
|
42
|
+
"""Schema picker for selecting input or output schemas.
|
|
43
|
+
|
|
44
|
+
Searches schemas via the ApiClient and returns the selected schema's ID.
|
|
45
|
+
User sees friendly schema names with version and format info, not UUIDs.
|
|
46
|
+
|
|
47
|
+
Usage:
|
|
48
|
+
yield SchemaPicker(
|
|
49
|
+
client=server_client,
|
|
50
|
+
id="output-schema",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# Get selected schema ID
|
|
54
|
+
picker = self.query_one("#output-schema", SchemaPicker)
|
|
55
|
+
schema_id = picker.selected_id # UUID string or None
|
|
56
|
+
|
|
57
|
+
# Optionally filter by format
|
|
58
|
+
yield SchemaPicker(
|
|
59
|
+
client=server_client,
|
|
60
|
+
schema_format="json_schema", # Only show JSON schemas
|
|
61
|
+
id="output-schema",
|
|
62
|
+
)
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
def __init__(
|
|
66
|
+
self,
|
|
67
|
+
client: ApiClient,
|
|
68
|
+
schema_format: str | None = None,
|
|
69
|
+
placeholder: str = "Search schemas (e.g., user profile, json output)...",
|
|
70
|
+
empty_message: str = "No schema selected (optional)",
|
|
71
|
+
**kwargs: Any,
|
|
72
|
+
) -> None:
|
|
73
|
+
"""Initialize the schema picker.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
client: ApiClient instance for API calls
|
|
77
|
+
schema_format: Optional filter by schema format (json_schema or regex)
|
|
78
|
+
placeholder: Placeholder text for search input
|
|
79
|
+
empty_message: Message shown when nothing is selected
|
|
80
|
+
"""
|
|
81
|
+
super().__init__(
|
|
82
|
+
client=client,
|
|
83
|
+
placeholder=placeholder,
|
|
84
|
+
empty_message=empty_message,
|
|
85
|
+
**kwargs,
|
|
86
|
+
)
|
|
87
|
+
self._schema_format = schema_format
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def selected_schema_id(self) -> str | None:
|
|
91
|
+
"""Alias for selected_id - returns the schema UUID."""
|
|
92
|
+
return self.selected_id
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def selected_schema_name(self) -> str | None:
|
|
96
|
+
"""Get the name of the selected schema."""
|
|
97
|
+
if self._selected_item is None:
|
|
98
|
+
return None
|
|
99
|
+
return self._selected_item.get("schema_name")
|
|
100
|
+
|
|
101
|
+
async def _fetch_items(self, query: str) -> list[dict[str, Any]]:
|
|
102
|
+
"""Fetch schemas matching query from the API."""
|
|
103
|
+
response = await self._client.list_schemas(
|
|
104
|
+
search=query,
|
|
105
|
+
schema_format=self._schema_format,
|
|
106
|
+
limit=50,
|
|
107
|
+
)
|
|
108
|
+
return [schema.model_dump() for schema in response.schemas]
|
|
109
|
+
|
|
110
|
+
def _format_item(self, item: dict[str, Any]) -> str:
|
|
111
|
+
"""Format schema for display in option list."""
|
|
112
|
+
return _format_schema(item)
|
|
113
|
+
|
|
114
|
+
def _get_item_id(self, item: dict[str, Any]) -> str:
|
|
115
|
+
"""Get unique identifier for a schema."""
|
|
116
|
+
return str(item.get("id", ""))
|
|
117
|
+
|
|
118
|
+
def set_initial_schema(self, schema: dict[str, Any]) -> None:
|
|
119
|
+
"""Pre-populate the picker with an existing schema selection.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
schema: Schema dict with at least 'id' and 'schema_name' keys
|
|
123
|
+
"""
|
|
124
|
+
self.set_initial_selection(schema)
|
|
125
|
+
|
|
126
|
+
def set_initial_schema_by_id(
|
|
127
|
+
self, schema_id: str, name: str = "Selected Schema"
|
|
128
|
+
) -> None:
|
|
129
|
+
"""Pre-populate the picker with a schema by its ID.
|
|
130
|
+
|
|
131
|
+
Use this when you only have the schema ID and not the full schema data.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
schema_id: The schema UUID
|
|
135
|
+
name: Display name for the schema
|
|
136
|
+
"""
|
|
137
|
+
self.set_initial_selection({"id": schema_id, "schema_name": name})
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Tag management modal for adding/removing tags from content.
|
|
2
|
+
|
|
3
|
+
Provides a modal screen that allows users to manage tags on a content part,
|
|
4
|
+
with the ability to search for tags and toggle their selection.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from textual import on
|
|
10
|
+
from textual.app import ComposeResult
|
|
11
|
+
from textual.binding import Binding
|
|
12
|
+
from textual.containers import Horizontal, Vertical
|
|
13
|
+
from textual.screen import ModalScreen
|
|
14
|
+
from textual.widgets import Button, Label, Static
|
|
15
|
+
|
|
16
|
+
from alloy_runtime_sdk.api_client.client import ApiClient
|
|
17
|
+
|
|
18
|
+
from cli.infrastructure.forms.tag_picker import TagPicker
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TagManagementModal(ModalScreen[list[str] | None]):
|
|
22
|
+
"""Modal for managing tags on a content part.
|
|
23
|
+
|
|
24
|
+
Allows users to search for and select multiple tags. Returns the
|
|
25
|
+
updated list of tag paths on save, or None if cancelled.
|
|
26
|
+
|
|
27
|
+
Usage:
|
|
28
|
+
def on_tags_saved(result: list[str] | None):
|
|
29
|
+
if result is not None:
|
|
30
|
+
# result is the list of tag paths to set
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
self.push_screen(
|
|
34
|
+
TagManagementModal(
|
|
35
|
+
client=server_client,
|
|
36
|
+
content_part_id="uuid-here",
|
|
37
|
+
current_tags=[{"tag_path": "existing.tag", ...}],
|
|
38
|
+
),
|
|
39
|
+
on_tags_saved
|
|
40
|
+
)
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
BINDINGS = [
|
|
44
|
+
Binding("escape", "cancel", "Cancel", show=True),
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
client: ApiClient,
|
|
50
|
+
content_part_id: str,
|
|
51
|
+
current_tags: list[dict[str, Any]],
|
|
52
|
+
**kwargs: Any,
|
|
53
|
+
) -> None:
|
|
54
|
+
"""Initialize the tag management modal.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
client: ApiClient instance for API calls
|
|
58
|
+
content_part_id: ID of the content part being edited
|
|
59
|
+
current_tags: Current tags on the content part
|
|
60
|
+
"""
|
|
61
|
+
super().__init__(**kwargs)
|
|
62
|
+
self._client = client
|
|
63
|
+
self._content_part_id = content_part_id
|
|
64
|
+
self._current_tags = current_tags
|
|
65
|
+
|
|
66
|
+
def compose(self) -> ComposeResult:
|
|
67
|
+
# Format content ID for display (truncated)
|
|
68
|
+
display_id = self._content_part_id[:8] + "..."
|
|
69
|
+
|
|
70
|
+
with Vertical():
|
|
71
|
+
yield Label("Manage Tags", classes="modal-title")
|
|
72
|
+
yield Static(f"Content: {display_id}", classes="content-info")
|
|
73
|
+
yield Static("Search and select tags:", classes="section-label")
|
|
74
|
+
yield TagPicker(
|
|
75
|
+
client=self._client,
|
|
76
|
+
placeholder="Search for tags...",
|
|
77
|
+
empty_message="No tags selected",
|
|
78
|
+
id="tag-picker",
|
|
79
|
+
)
|
|
80
|
+
with Horizontal(classes="button-row"):
|
|
81
|
+
yield Button("Save", id="save-btn", variant="success")
|
|
82
|
+
yield Button("Cancel", id="cancel-btn", variant="default")
|
|
83
|
+
|
|
84
|
+
def on_mount(self) -> None:
|
|
85
|
+
"""Pre-populate with current tags."""
|
|
86
|
+
picker = self.query_one("#tag-picker", TagPicker)
|
|
87
|
+
if self._current_tags:
|
|
88
|
+
picker.set_initial_selection(self._current_tags)
|
|
89
|
+
|
|
90
|
+
@on(Button.Pressed, "#save-btn")
|
|
91
|
+
def on_save_pressed(self, event: Button.Pressed) -> None:
|
|
92
|
+
"""Save and dismiss with selected tags."""
|
|
93
|
+
picker = self.query_one("#tag-picker", TagPicker)
|
|
94
|
+
self.dismiss(picker.selected_tags)
|
|
95
|
+
|
|
96
|
+
@on(Button.Pressed, "#cancel-btn")
|
|
97
|
+
def on_cancel_pressed(self, event: Button.Pressed) -> None:
|
|
98
|
+
"""Cancel and dismiss with None."""
|
|
99
|
+
self.dismiss(None)
|
|
100
|
+
|
|
101
|
+
def action_cancel(self) -> None:
|
|
102
|
+
"""Cancel via escape key."""
|
|
103
|
+
self.dismiss(None)
|