superqode 0.1.5__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.
- superqode/__init__.py +33 -0
- superqode/acp/__init__.py +23 -0
- superqode/acp/client.py +913 -0
- superqode/acp/permission_screen.py +457 -0
- superqode/acp/types.py +480 -0
- superqode/acp_discovery.py +856 -0
- superqode/agent/__init__.py +22 -0
- superqode/agent/edit_strategies.py +334 -0
- superqode/agent/loop.py +892 -0
- superqode/agent/qe_report_templates.py +39 -0
- superqode/agent/system_prompts.py +353 -0
- superqode/agent_output.py +721 -0
- superqode/agent_stream.py +953 -0
- superqode/agents/__init__.py +59 -0
- superqode/agents/acp_registry.py +305 -0
- superqode/agents/client.py +249 -0
- superqode/agents/data/augmentcode.com.toml +51 -0
- superqode/agents/data/cagent.dev.toml +51 -0
- superqode/agents/data/claude.com.toml +60 -0
- superqode/agents/data/codeassistant.dev.toml +51 -0
- superqode/agents/data/codex.openai.com.toml +57 -0
- superqode/agents/data/fastagent.ai.toml +66 -0
- superqode/agents/data/geminicli.com.toml +77 -0
- superqode/agents/data/goose.block.xyz.toml +54 -0
- superqode/agents/data/junie.jetbrains.com.toml +56 -0
- superqode/agents/data/kimi.moonshot.cn.toml +57 -0
- superqode/agents/data/llmlingagent.dev.toml +51 -0
- superqode/agents/data/molt.bot.toml +49 -0
- superqode/agents/data/opencode.ai.toml +60 -0
- superqode/agents/data/stakpak.dev.toml +51 -0
- superqode/agents/data/vtcode.dev.toml +51 -0
- superqode/agents/discovery.py +266 -0
- superqode/agents/messaging.py +160 -0
- superqode/agents/persona.py +166 -0
- superqode/agents/registry.py +421 -0
- superqode/agents/schema.py +72 -0
- superqode/agents/unified.py +367 -0
- superqode/app/__init__.py +111 -0
- superqode/app/constants.py +314 -0
- superqode/app/css.py +366 -0
- superqode/app/models.py +118 -0
- superqode/app/suggester.py +125 -0
- superqode/app/widgets.py +1591 -0
- superqode/app_enhanced.py +399 -0
- superqode/app_main.py +17187 -0
- superqode/approval.py +312 -0
- superqode/atomic.py +296 -0
- superqode/commands/__init__.py +1 -0
- superqode/commands/acp.py +965 -0
- superqode/commands/agents.py +180 -0
- superqode/commands/auth.py +278 -0
- superqode/commands/config.py +374 -0
- superqode/commands/init.py +826 -0
- superqode/commands/providers.py +819 -0
- superqode/commands/qe.py +1145 -0
- superqode/commands/roles.py +380 -0
- superqode/commands/serve.py +172 -0
- superqode/commands/suggestions.py +127 -0
- superqode/commands/superqe.py +460 -0
- superqode/config/__init__.py +51 -0
- superqode/config/loader.py +812 -0
- superqode/config/schema.py +498 -0
- superqode/core/__init__.py +111 -0
- superqode/core/roles.py +281 -0
- superqode/danger.py +386 -0
- superqode/data/superqode-template.yaml +1522 -0
- superqode/design_system.py +1080 -0
- superqode/dialogs/__init__.py +6 -0
- superqode/dialogs/base.py +39 -0
- superqode/dialogs/model.py +130 -0
- superqode/dialogs/provider.py +870 -0
- superqode/diff_view.py +919 -0
- superqode/enterprise.py +21 -0
- superqode/evaluation/__init__.py +25 -0
- superqode/evaluation/adapters.py +93 -0
- superqode/evaluation/behaviors.py +89 -0
- superqode/evaluation/engine.py +209 -0
- superqode/evaluation/scenarios.py +96 -0
- superqode/execution/__init__.py +36 -0
- superqode/execution/linter.py +538 -0
- superqode/execution/modes.py +347 -0
- superqode/execution/resolver.py +283 -0
- superqode/execution/runner.py +642 -0
- superqode/file_explorer.py +811 -0
- superqode/file_viewer.py +471 -0
- superqode/flash.py +183 -0
- superqode/guidance/__init__.py +58 -0
- superqode/guidance/config.py +203 -0
- superqode/guidance/prompts.py +71 -0
- superqode/harness/__init__.py +54 -0
- superqode/harness/accelerator.py +291 -0
- superqode/harness/config.py +319 -0
- superqode/harness/validator.py +147 -0
- superqode/history.py +279 -0
- superqode/integrations/superopt_runner.py +124 -0
- superqode/logging/__init__.py +49 -0
- superqode/logging/adapters.py +219 -0
- superqode/logging/formatter.py +923 -0
- superqode/logging/integration.py +341 -0
- superqode/logging/sinks.py +170 -0
- superqode/logging/unified_log.py +417 -0
- superqode/lsp/__init__.py +26 -0
- superqode/lsp/client.py +544 -0
- superqode/main.py +1069 -0
- superqode/mcp/__init__.py +89 -0
- superqode/mcp/auth_storage.py +380 -0
- superqode/mcp/client.py +1236 -0
- superqode/mcp/config.py +319 -0
- superqode/mcp/integration.py +337 -0
- superqode/mcp/oauth.py +436 -0
- superqode/mcp/oauth_callback.py +385 -0
- superqode/mcp/types.py +290 -0
- superqode/memory/__init__.py +31 -0
- superqode/memory/feedback.py +342 -0
- superqode/memory/store.py +522 -0
- superqode/notifications.py +369 -0
- superqode/optimization/__init__.py +5 -0
- superqode/optimization/config.py +33 -0
- superqode/permissions/__init__.py +25 -0
- superqode/permissions/rules.py +488 -0
- superqode/plan.py +323 -0
- superqode/providers/__init__.py +33 -0
- superqode/providers/gateway/__init__.py +165 -0
- superqode/providers/gateway/base.py +228 -0
- superqode/providers/gateway/litellm_gateway.py +1170 -0
- superqode/providers/gateway/openresponses_gateway.py +436 -0
- superqode/providers/health.py +297 -0
- superqode/providers/huggingface/__init__.py +74 -0
- superqode/providers/huggingface/downloader.py +472 -0
- superqode/providers/huggingface/endpoints.py +442 -0
- superqode/providers/huggingface/hub.py +531 -0
- superqode/providers/huggingface/inference.py +394 -0
- superqode/providers/huggingface/transformers_runner.py +516 -0
- superqode/providers/local/__init__.py +100 -0
- superqode/providers/local/base.py +438 -0
- superqode/providers/local/discovery.py +418 -0
- superqode/providers/local/lmstudio.py +256 -0
- superqode/providers/local/mlx.py +457 -0
- superqode/providers/local/ollama.py +486 -0
- superqode/providers/local/sglang.py +268 -0
- superqode/providers/local/tgi.py +260 -0
- superqode/providers/local/tool_support.py +477 -0
- superqode/providers/local/vllm.py +258 -0
- superqode/providers/manager.py +1338 -0
- superqode/providers/models.py +1016 -0
- superqode/providers/models_dev.py +578 -0
- superqode/providers/openresponses/__init__.py +87 -0
- superqode/providers/openresponses/converters/__init__.py +17 -0
- superqode/providers/openresponses/converters/messages.py +343 -0
- superqode/providers/openresponses/converters/tools.py +268 -0
- superqode/providers/openresponses/schema/__init__.py +56 -0
- superqode/providers/openresponses/schema/models.py +585 -0
- superqode/providers/openresponses/streaming/__init__.py +5 -0
- superqode/providers/openresponses/streaming/parser.py +338 -0
- superqode/providers/openresponses/tools/__init__.py +21 -0
- superqode/providers/openresponses/tools/apply_patch.py +352 -0
- superqode/providers/openresponses/tools/code_interpreter.py +290 -0
- superqode/providers/openresponses/tools/file_search.py +333 -0
- superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
- superqode/providers/registry.py +716 -0
- superqode/providers/usage.py +332 -0
- superqode/pure_mode.py +384 -0
- superqode/qr/__init__.py +23 -0
- superqode/qr/dashboard.py +781 -0
- superqode/qr/generator.py +1018 -0
- superqode/qr/templates.py +135 -0
- superqode/safety/__init__.py +41 -0
- superqode/safety/sandbox.py +413 -0
- superqode/safety/warnings.py +256 -0
- superqode/server/__init__.py +33 -0
- superqode/server/lsp_server.py +775 -0
- superqode/server/web.py +250 -0
- superqode/session/__init__.py +25 -0
- superqode/session/persistence.py +580 -0
- superqode/session/sharing.py +477 -0
- superqode/session.py +475 -0
- superqode/sidebar.py +2991 -0
- superqode/stream_view.py +648 -0
- superqode/styles/__init__.py +3 -0
- superqode/superqe/__init__.py +184 -0
- superqode/superqe/acp_runner.py +1064 -0
- superqode/superqe/constitution/__init__.py +62 -0
- superqode/superqe/constitution/evaluator.py +308 -0
- superqode/superqe/constitution/loader.py +432 -0
- superqode/superqe/constitution/schema.py +250 -0
- superqode/superqe/events.py +591 -0
- superqode/superqe/frameworks/__init__.py +65 -0
- superqode/superqe/frameworks/base.py +234 -0
- superqode/superqe/frameworks/e2e.py +263 -0
- superqode/superqe/frameworks/executor.py +237 -0
- superqode/superqe/frameworks/javascript.py +409 -0
- superqode/superqe/frameworks/python.py +373 -0
- superqode/superqe/frameworks/registry.py +92 -0
- superqode/superqe/mcp_tools/__init__.py +47 -0
- superqode/superqe/mcp_tools/core_tools.py +418 -0
- superqode/superqe/mcp_tools/registry.py +230 -0
- superqode/superqe/mcp_tools/testing_tools.py +167 -0
- superqode/superqe/noise.py +89 -0
- superqode/superqe/orchestrator.py +778 -0
- superqode/superqe/roles.py +609 -0
- superqode/superqe/session.py +713 -0
- superqode/superqe/skills/__init__.py +57 -0
- superqode/superqe/skills/base.py +106 -0
- superqode/superqe/skills/core_skills.py +899 -0
- superqode/superqe/skills/registry.py +90 -0
- superqode/superqe/verifier.py +101 -0
- superqode/superqe_cli.py +76 -0
- superqode/tool_call.py +358 -0
- superqode/tools/__init__.py +93 -0
- superqode/tools/agent_tools.py +496 -0
- superqode/tools/base.py +324 -0
- superqode/tools/batch_tool.py +133 -0
- superqode/tools/diagnostics.py +311 -0
- superqode/tools/edit_tools.py +653 -0
- superqode/tools/enhanced_base.py +515 -0
- superqode/tools/file_tools.py +269 -0
- superqode/tools/file_tracking.py +45 -0
- superqode/tools/lsp_tools.py +610 -0
- superqode/tools/network_tools.py +350 -0
- superqode/tools/permissions.py +400 -0
- superqode/tools/question_tool.py +324 -0
- superqode/tools/search_tools.py +598 -0
- superqode/tools/shell_tools.py +259 -0
- superqode/tools/todo_tools.py +121 -0
- superqode/tools/validation.py +80 -0
- superqode/tools/web_tools.py +639 -0
- superqode/tui.py +1152 -0
- superqode/tui_integration.py +875 -0
- superqode/tui_widgets/__init__.py +27 -0
- superqode/tui_widgets/widgets/__init__.py +18 -0
- superqode/tui_widgets/widgets/progress.py +185 -0
- superqode/tui_widgets/widgets/tool_display.py +188 -0
- superqode/undo_manager.py +574 -0
- superqode/utils/__init__.py +5 -0
- superqode/utils/error_handling.py +323 -0
- superqode/utils/fuzzy.py +257 -0
- superqode/widgets/__init__.py +477 -0
- superqode/widgets/agent_collab.py +390 -0
- superqode/widgets/agent_store.py +936 -0
- superqode/widgets/agent_switcher.py +395 -0
- superqode/widgets/animation_manager.py +284 -0
- superqode/widgets/code_context.py +356 -0
- superqode/widgets/command_palette.py +412 -0
- superqode/widgets/connection_status.py +537 -0
- superqode/widgets/conversation_history.py +470 -0
- superqode/widgets/diff_indicator.py +155 -0
- superqode/widgets/enhanced_status_bar.py +385 -0
- superqode/widgets/enhanced_toast.py +476 -0
- superqode/widgets/file_browser.py +809 -0
- superqode/widgets/file_reference.py +585 -0
- superqode/widgets/issue_timeline.py +340 -0
- superqode/widgets/leader_key.py +264 -0
- superqode/widgets/mode_switcher.py +445 -0
- superqode/widgets/model_picker.py +234 -0
- superqode/widgets/permission_preview.py +1205 -0
- superqode/widgets/prompt.py +358 -0
- superqode/widgets/provider_connect.py +725 -0
- superqode/widgets/pty_shell.py +587 -0
- superqode/widgets/qe_dashboard.py +321 -0
- superqode/widgets/resizable_sidebar.py +377 -0
- superqode/widgets/response_changes.py +218 -0
- superqode/widgets/response_display.py +528 -0
- superqode/widgets/rich_tool_display.py +613 -0
- superqode/widgets/sidebar_panels.py +1180 -0
- superqode/widgets/slash_complete.py +356 -0
- superqode/widgets/split_view.py +612 -0
- superqode/widgets/status_bar.py +273 -0
- superqode/widgets/superqode_display.py +786 -0
- superqode/widgets/thinking_display.py +815 -0
- superqode/widgets/throbber.py +87 -0
- superqode/widgets/toast.py +206 -0
- superqode/widgets/unified_output.py +1073 -0
- superqode/workspace/__init__.py +75 -0
- superqode/workspace/artifacts.py +472 -0
- superqode/workspace/coordinator.py +353 -0
- superqode/workspace/diff_tracker.py +429 -0
- superqode/workspace/git_guard.py +373 -0
- superqode/workspace/git_snapshot.py +526 -0
- superqode/workspace/manager.py +750 -0
- superqode/workspace/snapshot.py +357 -0
- superqode/workspace/watcher.py +535 -0
- superqode/workspace/worktree.py +440 -0
- superqode-0.1.5.dist-info/METADATA +204 -0
- superqode-0.1.5.dist-info/RECORD +288 -0
- superqode-0.1.5.dist-info/WHEEL +5 -0
- superqode-0.1.5.dist-info/entry_points.txt +3 -0
- superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
- superqode-0.1.5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Enhanced Toast/Flash System - Rich Notifications.
|
|
3
|
+
|
|
4
|
+
Provides a comprehensive notification system with:
|
|
5
|
+
- Multiple toast types (success, warning, error, info, progress)
|
|
6
|
+
- Stacking and queuing
|
|
7
|
+
- Progress toasts with updates
|
|
8
|
+
- Interactive toasts with actions
|
|
9
|
+
- Auto-dismiss with configurable timeouts
|
|
10
|
+
|
|
11
|
+
Enhances the basic toast widget with production-ready features.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import asyncio
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from datetime import datetime
|
|
19
|
+
from enum import Enum
|
|
20
|
+
from typing import Callable, Dict, List, Optional
|
|
21
|
+
from uuid import uuid4
|
|
22
|
+
|
|
23
|
+
from rich.console import RenderableType
|
|
24
|
+
from rich.panel import Panel
|
|
25
|
+
from rich.progress import Progress, BarColumn, TextColumn
|
|
26
|
+
from rich.text import Text
|
|
27
|
+
from textual.reactive import reactive
|
|
28
|
+
from textual.widgets import Static
|
|
29
|
+
from textual.containers import Container
|
|
30
|
+
from textual.timer import Timer
|
|
31
|
+
from textual import events
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ToastType(Enum):
|
|
35
|
+
"""Type of toast notification."""
|
|
36
|
+
|
|
37
|
+
SUCCESS = "success"
|
|
38
|
+
WARNING = "warning"
|
|
39
|
+
ERROR = "error"
|
|
40
|
+
INFO = "info"
|
|
41
|
+
PROGRESS = "progress"
|
|
42
|
+
ACTION = "action"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class ToastConfig:
|
|
47
|
+
"""Configuration for toast behavior."""
|
|
48
|
+
|
|
49
|
+
# Timing
|
|
50
|
+
default_timeout: float = 5.0
|
|
51
|
+
error_timeout: float = 10.0
|
|
52
|
+
progress_timeout: float = 30.0
|
|
53
|
+
|
|
54
|
+
# Display
|
|
55
|
+
max_visible: int = 5
|
|
56
|
+
position: str = "bottom-right" # top-right, top-left, bottom-right, bottom-left
|
|
57
|
+
animation: bool = True
|
|
58
|
+
|
|
59
|
+
# Sound (if terminal supports)
|
|
60
|
+
sound_enabled: bool = False
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass
|
|
64
|
+
class Toast:
|
|
65
|
+
"""A single toast notification."""
|
|
66
|
+
|
|
67
|
+
id: str
|
|
68
|
+
type: ToastType
|
|
69
|
+
title: str
|
|
70
|
+
message: str = ""
|
|
71
|
+
timeout: float = 5.0
|
|
72
|
+
created_at: datetime = field(default_factory=datetime.now)
|
|
73
|
+
|
|
74
|
+
# Progress toast specific
|
|
75
|
+
progress: float = 0.0 # 0.0 to 1.0
|
|
76
|
+
progress_text: str = ""
|
|
77
|
+
|
|
78
|
+
# Action toast specific
|
|
79
|
+
actions: List[Dict] = field(default_factory=list) # [{"label": "...", "callback": ...}]
|
|
80
|
+
|
|
81
|
+
# State
|
|
82
|
+
dismissed: bool = False
|
|
83
|
+
pinned: bool = False # Don't auto-dismiss
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def is_expired(self) -> bool:
|
|
87
|
+
"""Check if toast has expired."""
|
|
88
|
+
if self.pinned or self.dismissed:
|
|
89
|
+
return False
|
|
90
|
+
if self.type == ToastType.PROGRESS and self.progress < 1.0:
|
|
91
|
+
return False
|
|
92
|
+
elapsed = (datetime.now() - self.created_at).total_seconds()
|
|
93
|
+
return elapsed >= self.timeout
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# Toast type styling
|
|
97
|
+
TOAST_STYLES = {
|
|
98
|
+
ToastType.SUCCESS: {
|
|
99
|
+
"icon": "✓",
|
|
100
|
+
"color": "#22c55e",
|
|
101
|
+
"border": "#16a34a",
|
|
102
|
+
"bg": "#052e16",
|
|
103
|
+
},
|
|
104
|
+
ToastType.WARNING: {
|
|
105
|
+
"icon": "⚠",
|
|
106
|
+
"color": "#eab308",
|
|
107
|
+
"border": "#ca8a04",
|
|
108
|
+
"bg": "#422006",
|
|
109
|
+
},
|
|
110
|
+
ToastType.ERROR: {
|
|
111
|
+
"icon": "✗",
|
|
112
|
+
"color": "#ef4444",
|
|
113
|
+
"border": "#dc2626",
|
|
114
|
+
"bg": "#450a0a",
|
|
115
|
+
},
|
|
116
|
+
ToastType.INFO: {
|
|
117
|
+
"icon": "ℹ",
|
|
118
|
+
"color": "#3b82f6",
|
|
119
|
+
"border": "#2563eb",
|
|
120
|
+
"bg": "#172554",
|
|
121
|
+
},
|
|
122
|
+
ToastType.PROGRESS: {
|
|
123
|
+
"icon": "◐",
|
|
124
|
+
"color": "#8b5cf6",
|
|
125
|
+
"border": "#7c3aed",
|
|
126
|
+
"bg": "#2e1065",
|
|
127
|
+
},
|
|
128
|
+
ToastType.ACTION: {
|
|
129
|
+
"icon": "▶",
|
|
130
|
+
"color": "#06b6d4",
|
|
131
|
+
"border": "#0891b2",
|
|
132
|
+
"bg": "#083344",
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class ToastWidget(Static):
|
|
138
|
+
"""Single toast notification widget."""
|
|
139
|
+
|
|
140
|
+
DEFAULT_CSS = """
|
|
141
|
+
ToastWidget {
|
|
142
|
+
width: 50;
|
|
143
|
+
height: auto;
|
|
144
|
+
margin: 0 0 1 0;
|
|
145
|
+
layer: notification;
|
|
146
|
+
}
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
def __init__(self, toast: Toast, on_dismiss: Callable[[], None], **kwargs):
|
|
150
|
+
super().__init__(**kwargs)
|
|
151
|
+
self.toast = toast
|
|
152
|
+
self._on_dismiss = on_dismiss
|
|
153
|
+
self._animation_frame = 0
|
|
154
|
+
|
|
155
|
+
def on_click(self, event: events.Click) -> None:
|
|
156
|
+
"""Dismiss on click."""
|
|
157
|
+
if self.toast.type != ToastType.ACTION:
|
|
158
|
+
self._on_dismiss()
|
|
159
|
+
|
|
160
|
+
def on_key(self, event: events.Key) -> None:
|
|
161
|
+
"""Handle key events for actions."""
|
|
162
|
+
if self.toast.type == ToastType.ACTION and self.toast.actions:
|
|
163
|
+
for i, action in enumerate(self.toast.actions):
|
|
164
|
+
if event.key == str(i + 1):
|
|
165
|
+
callback = action.get("callback")
|
|
166
|
+
if callback:
|
|
167
|
+
callback()
|
|
168
|
+
self._on_dismiss()
|
|
169
|
+
event.prevent_default()
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
def update_progress(self, progress: float, text: str = "") -> None:
|
|
173
|
+
"""Update progress toast."""
|
|
174
|
+
self.toast.progress = progress
|
|
175
|
+
if text:
|
|
176
|
+
self.toast.progress_text = text
|
|
177
|
+
self.refresh()
|
|
178
|
+
|
|
179
|
+
def _get_spinner(self) -> str:
|
|
180
|
+
"""Get spinner character for progress."""
|
|
181
|
+
spinners = ["◐", "◓", "◑", "◒"]
|
|
182
|
+
return spinners[self._animation_frame % len(spinners)]
|
|
183
|
+
|
|
184
|
+
def render(self) -> RenderableType:
|
|
185
|
+
"""Render the toast."""
|
|
186
|
+
style = TOAST_STYLES.get(self.toast.type, TOAST_STYLES[ToastType.INFO])
|
|
187
|
+
|
|
188
|
+
content = Text()
|
|
189
|
+
|
|
190
|
+
# Icon
|
|
191
|
+
icon = style["icon"]
|
|
192
|
+
if self.toast.type == ToastType.PROGRESS and self.toast.progress < 1.0:
|
|
193
|
+
icon = self._get_spinner()
|
|
194
|
+
|
|
195
|
+
content.append(f" {icon} ", style=f"bold {style['color']}")
|
|
196
|
+
|
|
197
|
+
# Title
|
|
198
|
+
content.append(self.toast.title, style=f"bold {style['color']}")
|
|
199
|
+
|
|
200
|
+
# Message
|
|
201
|
+
if self.toast.message:
|
|
202
|
+
content.append(f"\n {self.toast.message}", style="#a1a1aa")
|
|
203
|
+
|
|
204
|
+
# Progress bar
|
|
205
|
+
if self.toast.type == ToastType.PROGRESS:
|
|
206
|
+
content.append("\n ")
|
|
207
|
+
|
|
208
|
+
bar_width = 30
|
|
209
|
+
filled = int(self.toast.progress * bar_width)
|
|
210
|
+
|
|
211
|
+
content.append("[", style="#3f3f46")
|
|
212
|
+
content.append("█" * filled, style=f"bold {style['color']}")
|
|
213
|
+
content.append("░" * (bar_width - filled), style="#27272a")
|
|
214
|
+
content.append("]", style="#3f3f46")
|
|
215
|
+
|
|
216
|
+
pct = int(self.toast.progress * 100)
|
|
217
|
+
content.append(f" {pct}%", style="#6b7280")
|
|
218
|
+
|
|
219
|
+
if self.toast.progress_text:
|
|
220
|
+
content.append(f"\n {self.toast.progress_text}", style="#6b7280")
|
|
221
|
+
|
|
222
|
+
# Actions
|
|
223
|
+
if self.toast.type == ToastType.ACTION and self.toast.actions:
|
|
224
|
+
content.append("\n ")
|
|
225
|
+
for i, action in enumerate(self.toast.actions):
|
|
226
|
+
content.append(f"[{i + 1}] ", style=f"bold {style['color']}")
|
|
227
|
+
content.append(f"{action['label']} ", style="#a1a1aa")
|
|
228
|
+
|
|
229
|
+
return Panel(
|
|
230
|
+
content,
|
|
231
|
+
border_style=style["border"],
|
|
232
|
+
padding=(0, 1),
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
class ToastContainer(Container):
|
|
237
|
+
"""
|
|
238
|
+
Container for managing multiple toast notifications.
|
|
239
|
+
|
|
240
|
+
Usage:
|
|
241
|
+
# In your App
|
|
242
|
+
def compose(self):
|
|
243
|
+
yield ToastContainer(id="toasts")
|
|
244
|
+
|
|
245
|
+
# Show toasts
|
|
246
|
+
toasts = self.query_one("#toasts", ToastContainer)
|
|
247
|
+
toasts.success("File saved!")
|
|
248
|
+
toasts.error("Connection failed", "Could not reach server")
|
|
249
|
+
|
|
250
|
+
# Progress toast
|
|
251
|
+
toast_id = toasts.progress("Uploading...", "Starting upload")
|
|
252
|
+
toasts.update_progress(toast_id, 0.5, "50% complete")
|
|
253
|
+
toasts.update_progress(toast_id, 1.0, "Done!")
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
DEFAULT_CSS = """
|
|
257
|
+
ToastContainer {
|
|
258
|
+
dock: bottom;
|
|
259
|
+
width: 100%;
|
|
260
|
+
height: auto;
|
|
261
|
+
max-height: 50%;
|
|
262
|
+
align: right bottom;
|
|
263
|
+
padding: 1;
|
|
264
|
+
layer: notification;
|
|
265
|
+
}
|
|
266
|
+
"""
|
|
267
|
+
|
|
268
|
+
def __init__(
|
|
269
|
+
self,
|
|
270
|
+
config: Optional[ToastConfig] = None,
|
|
271
|
+
**kwargs,
|
|
272
|
+
):
|
|
273
|
+
super().__init__(**kwargs)
|
|
274
|
+
self.config = config or ToastConfig()
|
|
275
|
+
self._toasts: Dict[str, Toast] = {}
|
|
276
|
+
self._widgets: Dict[str, ToastWidget] = {}
|
|
277
|
+
self._timer: Optional[Timer] = None
|
|
278
|
+
|
|
279
|
+
def on_mount(self) -> None:
|
|
280
|
+
"""Start cleanup timer."""
|
|
281
|
+
self._timer = self.set_interval(1.0, self._cleanup_expired)
|
|
282
|
+
|
|
283
|
+
def _cleanup_expired(self) -> None:
|
|
284
|
+
"""Remove expired toasts."""
|
|
285
|
+
expired = [tid for tid, toast in self._toasts.items() if toast.is_expired]
|
|
286
|
+
|
|
287
|
+
for tid in expired:
|
|
288
|
+
self._remove_toast(tid)
|
|
289
|
+
|
|
290
|
+
def _add_toast(self, toast: Toast) -> str:
|
|
291
|
+
"""Add a toast to the container."""
|
|
292
|
+
# Limit visible toasts
|
|
293
|
+
while len(self._toasts) >= self.config.max_visible:
|
|
294
|
+
oldest = min(self._toasts.values(), key=lambda t: t.created_at)
|
|
295
|
+
self._remove_toast(oldest.id)
|
|
296
|
+
|
|
297
|
+
self._toasts[toast.id] = toast
|
|
298
|
+
|
|
299
|
+
# Create widget
|
|
300
|
+
widget = ToastWidget(
|
|
301
|
+
toast,
|
|
302
|
+
on_dismiss=lambda: self._remove_toast(toast.id),
|
|
303
|
+
id=f"toast-{toast.id}",
|
|
304
|
+
)
|
|
305
|
+
self._widgets[toast.id] = widget
|
|
306
|
+
self.mount(widget)
|
|
307
|
+
|
|
308
|
+
return toast.id
|
|
309
|
+
|
|
310
|
+
def _remove_toast(self, toast_id: str) -> None:
|
|
311
|
+
"""Remove a toast."""
|
|
312
|
+
toast = self._toasts.pop(toast_id, None)
|
|
313
|
+
widget = self._widgets.pop(toast_id, None)
|
|
314
|
+
|
|
315
|
+
if widget:
|
|
316
|
+
widget.remove()
|
|
317
|
+
|
|
318
|
+
if toast:
|
|
319
|
+
toast.dismissed = True
|
|
320
|
+
|
|
321
|
+
def success(self, title: str, message: str = "", timeout: float = None) -> str:
|
|
322
|
+
"""Show a success toast."""
|
|
323
|
+
toast = Toast(
|
|
324
|
+
id=str(uuid4())[:8],
|
|
325
|
+
type=ToastType.SUCCESS,
|
|
326
|
+
title=title,
|
|
327
|
+
message=message,
|
|
328
|
+
timeout=timeout or self.config.default_timeout,
|
|
329
|
+
)
|
|
330
|
+
return self._add_toast(toast)
|
|
331
|
+
|
|
332
|
+
def warning(self, title: str, message: str = "", timeout: float = None) -> str:
|
|
333
|
+
"""Show a warning toast."""
|
|
334
|
+
toast = Toast(
|
|
335
|
+
id=str(uuid4())[:8],
|
|
336
|
+
type=ToastType.WARNING,
|
|
337
|
+
title=title,
|
|
338
|
+
message=message,
|
|
339
|
+
timeout=timeout or self.config.default_timeout,
|
|
340
|
+
)
|
|
341
|
+
return self._add_toast(toast)
|
|
342
|
+
|
|
343
|
+
def error(self, title: str, message: str = "", timeout: float = None) -> str:
|
|
344
|
+
"""Show an error toast."""
|
|
345
|
+
toast = Toast(
|
|
346
|
+
id=str(uuid4())[:8],
|
|
347
|
+
type=ToastType.ERROR,
|
|
348
|
+
title=title,
|
|
349
|
+
message=message,
|
|
350
|
+
timeout=timeout or self.config.error_timeout,
|
|
351
|
+
)
|
|
352
|
+
return self._add_toast(toast)
|
|
353
|
+
|
|
354
|
+
def info(self, title: str, message: str = "", timeout: float = None) -> str:
|
|
355
|
+
"""Show an info toast."""
|
|
356
|
+
toast = Toast(
|
|
357
|
+
id=str(uuid4())[:8],
|
|
358
|
+
type=ToastType.INFO,
|
|
359
|
+
title=title,
|
|
360
|
+
message=message,
|
|
361
|
+
timeout=timeout or self.config.default_timeout,
|
|
362
|
+
)
|
|
363
|
+
return self._add_toast(toast)
|
|
364
|
+
|
|
365
|
+
def progress(
|
|
366
|
+
self,
|
|
367
|
+
title: str,
|
|
368
|
+
message: str = "",
|
|
369
|
+
initial_progress: float = 0.0,
|
|
370
|
+
) -> str:
|
|
371
|
+
"""Show a progress toast."""
|
|
372
|
+
toast = Toast(
|
|
373
|
+
id=str(uuid4())[:8],
|
|
374
|
+
type=ToastType.PROGRESS,
|
|
375
|
+
title=title,
|
|
376
|
+
message=message,
|
|
377
|
+
progress=initial_progress,
|
|
378
|
+
timeout=self.config.progress_timeout,
|
|
379
|
+
pinned=True,
|
|
380
|
+
)
|
|
381
|
+
return self._add_toast(toast)
|
|
382
|
+
|
|
383
|
+
def update_progress(
|
|
384
|
+
self,
|
|
385
|
+
toast_id: str,
|
|
386
|
+
progress: float,
|
|
387
|
+
text: str = "",
|
|
388
|
+
) -> None:
|
|
389
|
+
"""Update a progress toast."""
|
|
390
|
+
toast = self._toasts.get(toast_id)
|
|
391
|
+
if toast and toast.type == ToastType.PROGRESS:
|
|
392
|
+
toast.progress = progress
|
|
393
|
+
if text:
|
|
394
|
+
toast.progress_text = text
|
|
395
|
+
|
|
396
|
+
# Auto-dismiss when complete
|
|
397
|
+
if progress >= 1.0:
|
|
398
|
+
toast.pinned = False
|
|
399
|
+
toast.timeout = 3.0
|
|
400
|
+
toast.created_at = datetime.now()
|
|
401
|
+
|
|
402
|
+
widget = self._widgets.get(toast_id)
|
|
403
|
+
if widget:
|
|
404
|
+
widget.refresh()
|
|
405
|
+
|
|
406
|
+
def action(
|
|
407
|
+
self,
|
|
408
|
+
title: str,
|
|
409
|
+
message: str = "",
|
|
410
|
+
actions: List[Dict] = None,
|
|
411
|
+
) -> str:
|
|
412
|
+
"""Show an action toast with buttons."""
|
|
413
|
+
toast = Toast(
|
|
414
|
+
id=str(uuid4())[:8],
|
|
415
|
+
type=ToastType.ACTION,
|
|
416
|
+
title=title,
|
|
417
|
+
message=message,
|
|
418
|
+
actions=actions or [],
|
|
419
|
+
pinned=True, # Don't auto-dismiss action toasts
|
|
420
|
+
timeout=30.0,
|
|
421
|
+
)
|
|
422
|
+
return self._add_toast(toast)
|
|
423
|
+
|
|
424
|
+
def dismiss(self, toast_id: str) -> None:
|
|
425
|
+
"""Manually dismiss a toast."""
|
|
426
|
+
self._remove_toast(toast_id)
|
|
427
|
+
|
|
428
|
+
def dismiss_all(self) -> None:
|
|
429
|
+
"""Dismiss all toasts."""
|
|
430
|
+
for toast_id in list(self._toasts.keys()):
|
|
431
|
+
self._remove_toast(toast_id)
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
# Convenience functions for module-level usage
|
|
435
|
+
_global_container: Optional[ToastContainer] = None
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def set_toast_container(container: ToastContainer) -> None:
|
|
439
|
+
"""Set the global toast container."""
|
|
440
|
+
global _global_container
|
|
441
|
+
_global_container = container
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def toast_success(title: str, message: str = "") -> Optional[str]:
|
|
445
|
+
"""Show a success toast."""
|
|
446
|
+
if _global_container:
|
|
447
|
+
return _global_container.success(title, message)
|
|
448
|
+
return None
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def toast_warning(title: str, message: str = "") -> Optional[str]:
|
|
452
|
+
"""Show a warning toast."""
|
|
453
|
+
if _global_container:
|
|
454
|
+
return _global_container.warning(title, message)
|
|
455
|
+
return None
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def toast_error(title: str, message: str = "") -> Optional[str]:
|
|
459
|
+
"""Show an error toast."""
|
|
460
|
+
if _global_container:
|
|
461
|
+
return _global_container.error(title, message)
|
|
462
|
+
return None
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def toast_info(title: str, message: str = "") -> Optional[str]:
|
|
466
|
+
"""Show an info toast."""
|
|
467
|
+
if _global_container:
|
|
468
|
+
return _global_container.info(title, message)
|
|
469
|
+
return None
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def toast_progress(title: str, message: str = "") -> Optional[str]:
|
|
473
|
+
"""Show a progress toast."""
|
|
474
|
+
if _global_container:
|
|
475
|
+
return _global_container.progress(title, message)
|
|
476
|
+
return None
|