shotgun-sh 0.2.23.dev1__py3-none-any.whl → 0.2.29.dev2__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.
Potentially problematic release.
This version of shotgun-sh might be problematic. Click here for more details.
- shotgun/agents/agent_manager.py +3 -3
- shotgun/agents/common.py +1 -1
- shotgun/agents/config/manager.py +36 -21
- shotgun/agents/config/models.py +30 -0
- shotgun/agents/config/provider.py +27 -14
- shotgun/agents/context_analyzer/analyzer.py +6 -2
- shotgun/agents/conversation/__init__.py +18 -0
- shotgun/agents/conversation/filters.py +164 -0
- shotgun/agents/conversation/history/chunking.py +278 -0
- shotgun/agents/{history → conversation/history}/compaction.py +27 -1
- shotgun/agents/{history → conversation/history}/constants.py +5 -0
- shotgun/agents/conversation/history/file_content_deduplication.py +216 -0
- shotgun/agents/{history → conversation/history}/history_processors.py +267 -3
- shotgun/agents/{conversation_manager.py → conversation/manager.py} +1 -1
- shotgun/agents/{conversation_history.py → conversation/models.py} +8 -94
- shotgun/agents/tools/web_search/openai.py +1 -1
- shotgun/cli/clear.py +1 -1
- shotgun/cli/compact.py +5 -3
- shotgun/cli/context.py +1 -1
- shotgun/cli/spec/__init__.py +5 -0
- shotgun/cli/spec/backup.py +81 -0
- shotgun/cli/spec/commands.py +130 -0
- shotgun/cli/spec/models.py +30 -0
- shotgun/cli/spec/pull_service.py +165 -0
- shotgun/codebase/core/ingestor.py +153 -7
- shotgun/codebase/models.py +2 -0
- shotgun/exceptions.py +5 -3
- shotgun/main.py +2 -0
- shotgun/posthog_telemetry.py +1 -1
- shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +3 -3
- shotgun/prompts/agents/partials/interactive_mode.j2 +3 -3
- shotgun/prompts/agents/research.j2 +0 -3
- shotgun/prompts/history/chunk_summarization.j2 +34 -0
- shotgun/prompts/history/combine_summaries.j2 +53 -0
- shotgun/shotgun_web/__init__.py +67 -1
- shotgun/shotgun_web/client.py +42 -1
- shotgun/shotgun_web/constants.py +46 -0
- shotgun/shotgun_web/exceptions.py +29 -0
- shotgun/shotgun_web/models.py +390 -0
- shotgun/shotgun_web/shared_specs/__init__.py +32 -0
- shotgun/shotgun_web/shared_specs/file_scanner.py +175 -0
- shotgun/shotgun_web/shared_specs/hasher.py +83 -0
- shotgun/shotgun_web/shared_specs/models.py +71 -0
- shotgun/shotgun_web/shared_specs/upload_pipeline.py +291 -0
- shotgun/shotgun_web/shared_specs/utils.py +34 -0
- shotgun/shotgun_web/specs_client.py +703 -0
- shotgun/shotgun_web/supabase_client.py +31 -0
- shotgun/tui/app.py +39 -0
- shotgun/tui/containers.py +1 -1
- shotgun/tui/layout.py +5 -0
- shotgun/tui/screens/chat/chat_screen.py +212 -16
- shotgun/tui/screens/chat/codebase_index_prompt_screen.py +147 -19
- shotgun/tui/screens/chat_screen/command_providers.py +10 -0
- shotgun/tui/screens/chat_screen/history/chat_history.py +0 -36
- shotgun/tui/screens/confirmation_dialog.py +40 -0
- shotgun/tui/screens/model_picker.py +7 -1
- shotgun/tui/screens/onboarding.py +149 -0
- shotgun/tui/screens/pipx_migration.py +46 -0
- shotgun/tui/screens/provider_config.py +41 -0
- shotgun/tui/screens/shared_specs/__init__.py +21 -0
- shotgun/tui/screens/shared_specs/create_spec_dialog.py +273 -0
- shotgun/tui/screens/shared_specs/models.py +56 -0
- shotgun/tui/screens/shared_specs/share_specs_dialog.py +390 -0
- shotgun/tui/screens/shared_specs/upload_progress_screen.py +452 -0
- shotgun/tui/screens/shotgun_auth.py +60 -6
- shotgun/tui/screens/spec_pull.py +286 -0
- shotgun/tui/screens/welcome.py +91 -0
- shotgun/tui/services/conversation_service.py +5 -2
- shotgun/tui/widgets/widget_coordinator.py +1 -1
- {shotgun_sh-0.2.23.dev1.dist-info → shotgun_sh-0.2.29.dev2.dist-info}/METADATA +1 -1
- {shotgun_sh-0.2.23.dev1.dist-info → shotgun_sh-0.2.29.dev2.dist-info}/RECORD +86 -59
- {shotgun_sh-0.2.23.dev1.dist-info → shotgun_sh-0.2.29.dev2.dist-info}/WHEEL +1 -1
- /shotgun/agents/{history → conversation/history}/__init__.py +0 -0
- /shotgun/agents/{history → conversation/history}/context_extraction.py +0 -0
- /shotgun/agents/{history → conversation/history}/history_building.py +0 -0
- /shotgun/agents/{history → conversation/history}/message_utils.py +0 -0
- /shotgun/agents/{history → conversation/history}/token_counting/__init__.py +0 -0
- /shotgun/agents/{history → conversation/history}/token_counting/anthropic.py +0 -0
- /shotgun/agents/{history → conversation/history}/token_counting/base.py +0 -0
- /shotgun/agents/{history → conversation/history}/token_counting/openai.py +0 -0
- /shotgun/agents/{history → conversation/history}/token_counting/sentencepiece_counter.py +0 -0
- /shotgun/agents/{history → conversation/history}/token_counting/tokenizer_cache.py +0 -0
- /shotgun/agents/{history → conversation/history}/token_counting/utils.py +0 -0
- /shotgun/agents/{history → conversation/history}/token_estimation.py +0 -0
- {shotgun_sh-0.2.23.dev1.dist-info → shotgun_sh-0.2.29.dev2.dist-info}/entry_points.txt +0 -0
- {shotgun_sh-0.2.23.dev1.dist-info → shotgun_sh-0.2.29.dev2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"""Screen showing download progress for pulling specs."""
|
|
2
|
+
|
|
3
|
+
from textual import on, work
|
|
4
|
+
from textual.app import ComposeResult
|
|
5
|
+
from textual.containers import Container, Horizontal
|
|
6
|
+
from textual.events import Resize
|
|
7
|
+
from textual.screen import ModalScreen
|
|
8
|
+
from textual.widgets import Button, Label, ProgressBar, Static
|
|
9
|
+
from textual.worker import Worker, get_current_worker
|
|
10
|
+
|
|
11
|
+
from shotgun.cli.spec.pull_service import (
|
|
12
|
+
CancelledError,
|
|
13
|
+
PullProgress,
|
|
14
|
+
SpecPullService,
|
|
15
|
+
)
|
|
16
|
+
from shotgun.logging_config import get_logger
|
|
17
|
+
from shotgun.shotgun_web.exceptions import (
|
|
18
|
+
ForbiddenError,
|
|
19
|
+
NotFoundError,
|
|
20
|
+
UnauthorizedError,
|
|
21
|
+
)
|
|
22
|
+
from shotgun.tui.layout import COMPACT_HEIGHT_THRESHOLD
|
|
23
|
+
from shotgun.utils.file_system_utils import get_shotgun_base_path
|
|
24
|
+
|
|
25
|
+
logger = get_logger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SpecPullScreen(ModalScreen[bool]):
|
|
29
|
+
"""Screen to pull a spec version with progress display.
|
|
30
|
+
|
|
31
|
+
Returns True if pull was successful, False otherwise.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
DEFAULT_CSS = """
|
|
35
|
+
SpecPullScreen {
|
|
36
|
+
align: center middle;
|
|
37
|
+
background: rgba(0, 0, 0, 0.0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
SpecPullScreen > #dialog-container {
|
|
41
|
+
width: 80%;
|
|
42
|
+
max-width: 90;
|
|
43
|
+
height: auto;
|
|
44
|
+
border: wide $primary;
|
|
45
|
+
padding: 1 2;
|
|
46
|
+
layout: vertical;
|
|
47
|
+
background: $surface;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
#dialog-title {
|
|
51
|
+
text-style: bold;
|
|
52
|
+
color: $text-accent;
|
|
53
|
+
padding-bottom: 1;
|
|
54
|
+
text-align: center;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
#phase-label {
|
|
58
|
+
padding: 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
#progress-bar {
|
|
62
|
+
width: 100%;
|
|
63
|
+
padding: 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
#file-label {
|
|
67
|
+
color: $text-muted;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#error-label {
|
|
71
|
+
color: $error;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
#success-label {
|
|
75
|
+
color: $success;
|
|
76
|
+
text-style: bold;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
#dialog-buttons {
|
|
80
|
+
layout: horizontal;
|
|
81
|
+
align-horizontal: center;
|
|
82
|
+
height: auto;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
#dialog-buttons Button {
|
|
86
|
+
margin: 0 1;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* Hide elements initially */
|
|
90
|
+
#success-label {
|
|
91
|
+
display: none;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
#error-label {
|
|
95
|
+
display: none;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* Compact styles for short terminals */
|
|
99
|
+
SpecPullScreen.compact #dialog-container {
|
|
100
|
+
padding: 0 2;
|
|
101
|
+
max-height: 98%;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
SpecPullScreen.compact #dialog-title {
|
|
105
|
+
padding-bottom: 0;
|
|
106
|
+
}
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
BINDINGS = [
|
|
110
|
+
("escape", "cancel", "Cancel"),
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
def __init__(self, version_id: str) -> None:
|
|
114
|
+
"""Initialize the screen.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
version_id: Version UUID to pull.
|
|
118
|
+
"""
|
|
119
|
+
super().__init__()
|
|
120
|
+
self.version_id = version_id
|
|
121
|
+
self._success = False
|
|
122
|
+
self._download_worker: Worker[None] | None = None
|
|
123
|
+
self._cancelled = False
|
|
124
|
+
|
|
125
|
+
def compose(self) -> ComposeResult:
|
|
126
|
+
"""Compose the screen widgets."""
|
|
127
|
+
with Container(id="dialog-container"):
|
|
128
|
+
yield Label("Pulling spec from cloud", id="dialog-title")
|
|
129
|
+
|
|
130
|
+
# Progress section
|
|
131
|
+
yield Static("Fetching version info...", id="phase-label")
|
|
132
|
+
yield ProgressBar(total=100, id="progress-bar")
|
|
133
|
+
yield Static("", id="file-label")
|
|
134
|
+
|
|
135
|
+
# Error section (hidden by default)
|
|
136
|
+
yield Static("", id="error-label")
|
|
137
|
+
|
|
138
|
+
# Success section (hidden by default)
|
|
139
|
+
yield Static("Spec pulled successfully!", id="success-label")
|
|
140
|
+
|
|
141
|
+
# Buttons
|
|
142
|
+
with Horizontal(id="dialog-buttons"):
|
|
143
|
+
yield Button("Cancel", id="cancel-btn")
|
|
144
|
+
yield Button("Continue", variant="primary", id="done-btn")
|
|
145
|
+
|
|
146
|
+
def on_mount(self) -> None:
|
|
147
|
+
"""Start the download when screen is mounted."""
|
|
148
|
+
# Hide done button initially
|
|
149
|
+
self.query_one("#done-btn", Button).display = False
|
|
150
|
+
|
|
151
|
+
# Apply compact layout if starting in a short terminal
|
|
152
|
+
self._apply_compact_layout(self.app.size.height < COMPACT_HEIGHT_THRESHOLD)
|
|
153
|
+
|
|
154
|
+
# Start the download
|
|
155
|
+
self._start_download()
|
|
156
|
+
|
|
157
|
+
@on(Resize)
|
|
158
|
+
def handle_resize(self, event: Resize) -> None:
|
|
159
|
+
"""Adjust layout based on terminal height."""
|
|
160
|
+
self._apply_compact_layout(event.size.height < COMPACT_HEIGHT_THRESHOLD)
|
|
161
|
+
|
|
162
|
+
def _apply_compact_layout(self, compact: bool) -> None:
|
|
163
|
+
"""Apply or remove compact layout class for short terminals."""
|
|
164
|
+
if compact:
|
|
165
|
+
self.add_class("compact")
|
|
166
|
+
else:
|
|
167
|
+
self.remove_class("compact")
|
|
168
|
+
|
|
169
|
+
@work(exclusive=True)
|
|
170
|
+
async def _start_download(self) -> None:
|
|
171
|
+
"""Run the download pipeline."""
|
|
172
|
+
worker = get_current_worker()
|
|
173
|
+
self._download_worker = worker
|
|
174
|
+
|
|
175
|
+
shotgun_dir = get_shotgun_base_path()
|
|
176
|
+
service = SpecPullService()
|
|
177
|
+
|
|
178
|
+
def on_progress(p: PullProgress) -> None:
|
|
179
|
+
pct = 0.0
|
|
180
|
+
if p.total_files and p.file_index is not None:
|
|
181
|
+
pct = ((p.file_index + 1) / p.total_files) * 100
|
|
182
|
+
self._update_phase(p.phase, progress=pct, current_file=p.current_file)
|
|
183
|
+
|
|
184
|
+
try:
|
|
185
|
+
result = await service.pull_version(
|
|
186
|
+
version_id=self.version_id,
|
|
187
|
+
shotgun_dir=shotgun_dir,
|
|
188
|
+
on_progress=on_progress,
|
|
189
|
+
is_cancelled=lambda: worker.is_cancelled,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
if result.success:
|
|
193
|
+
self._update_title(f"Pulled: {result.spec_name}")
|
|
194
|
+
self._success = True
|
|
195
|
+
self._show_success()
|
|
196
|
+
else:
|
|
197
|
+
self._show_error(result.error or "Unknown error")
|
|
198
|
+
|
|
199
|
+
except CancelledError:
|
|
200
|
+
self._cancelled = True
|
|
201
|
+
self._show_cancelled()
|
|
202
|
+
except UnauthorizedError:
|
|
203
|
+
self._show_error("Not authenticated. Please try again.")
|
|
204
|
+
except NotFoundError:
|
|
205
|
+
self._show_error(f"Version not found: {self.version_id}")
|
|
206
|
+
except ForbiddenError:
|
|
207
|
+
self._show_error("You don't have access to this spec.")
|
|
208
|
+
except Exception as e:
|
|
209
|
+
logger.exception(f"Download failed: {type(e).__name__}: {e}")
|
|
210
|
+
error_msg = str(e) if str(e) else type(e).__name__
|
|
211
|
+
self._show_error(error_msg)
|
|
212
|
+
|
|
213
|
+
def _update_title(self, title: str) -> None:
|
|
214
|
+
"""Update the dialog title."""
|
|
215
|
+
self.query_one("#dialog-title", Label).update(title)
|
|
216
|
+
|
|
217
|
+
def _update_phase(
|
|
218
|
+
self,
|
|
219
|
+
phase_text: str,
|
|
220
|
+
progress: float = 0,
|
|
221
|
+
current_file: str | None = None,
|
|
222
|
+
) -> None:
|
|
223
|
+
"""Update the progress UI."""
|
|
224
|
+
self.query_one("#phase-label", Static).update(phase_text)
|
|
225
|
+
self.query_one("#progress-bar", ProgressBar).update(progress=progress)
|
|
226
|
+
|
|
227
|
+
file_label = self.query_one("#file-label", Static)
|
|
228
|
+
if current_file:
|
|
229
|
+
file_label.update(f"Current: {current_file}")
|
|
230
|
+
else:
|
|
231
|
+
file_label.update("")
|
|
232
|
+
|
|
233
|
+
def _show_success(self) -> None:
|
|
234
|
+
"""Show success state."""
|
|
235
|
+
self.query_one("#phase-label", Static).update("Download complete!")
|
|
236
|
+
self.query_one("#progress-bar", ProgressBar).update(progress=100)
|
|
237
|
+
self.query_one("#success-label", Static).display = True
|
|
238
|
+
self.query_one("#cancel-btn", Button).display = False
|
|
239
|
+
self.query_one("#done-btn", Button).display = True
|
|
240
|
+
|
|
241
|
+
def _show_error(self, error: str) -> None:
|
|
242
|
+
"""Show error state."""
|
|
243
|
+
error_label = self.query_one("#error-label", Static)
|
|
244
|
+
error_label.update(f"Error: {error}")
|
|
245
|
+
error_label.display = True
|
|
246
|
+
self.query_one("#cancel-btn", Button).display = False
|
|
247
|
+
self.query_one("#done-btn", Button).display = True
|
|
248
|
+
self.query_one("#done-btn", Button).label = "Close"
|
|
249
|
+
|
|
250
|
+
def _show_cancelled(self) -> None:
|
|
251
|
+
"""Show cancelled state."""
|
|
252
|
+
self.query_one("#phase-label", Static).update("Download cancelled")
|
|
253
|
+
self.query_one("#cancel-btn", Button).display = False
|
|
254
|
+
self.query_one("#done-btn", Button).display = True
|
|
255
|
+
self.query_one("#done-btn", Button).label = "Close"
|
|
256
|
+
|
|
257
|
+
@on(Button.Pressed, "#cancel-btn")
|
|
258
|
+
def _on_cancel(self, event: Button.Pressed) -> None:
|
|
259
|
+
"""Handle cancel button."""
|
|
260
|
+
event.stop()
|
|
261
|
+
self._cancel_download()
|
|
262
|
+
|
|
263
|
+
@on(Button.Pressed, "#done-btn")
|
|
264
|
+
def _on_done(self, event: Button.Pressed) -> None:
|
|
265
|
+
"""Handle done button."""
|
|
266
|
+
event.stop()
|
|
267
|
+
self.dismiss(self._success)
|
|
268
|
+
|
|
269
|
+
def action_cancel(self) -> None:
|
|
270
|
+
"""Handle escape key."""
|
|
271
|
+
if (
|
|
272
|
+
self._success
|
|
273
|
+
or self._cancelled
|
|
274
|
+
or self.query_one("#error-label", Static).display
|
|
275
|
+
):
|
|
276
|
+
# Already finished, just dismiss
|
|
277
|
+
self.dismiss(self._success)
|
|
278
|
+
else:
|
|
279
|
+
# Download in progress, cancel it
|
|
280
|
+
self._cancel_download()
|
|
281
|
+
|
|
282
|
+
def _cancel_download(self) -> None:
|
|
283
|
+
"""Cancel the download."""
|
|
284
|
+
if self._download_worker and not self._download_worker.is_cancelled:
|
|
285
|
+
self._cancelled = True
|
|
286
|
+
self._download_worker.cancel()
|
shotgun/tui/screens/welcome.py
CHANGED
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import webbrowser
|
|
5
6
|
from typing import TYPE_CHECKING, cast
|
|
6
7
|
|
|
7
8
|
from textual import on
|
|
8
9
|
from textual.app import ComposeResult
|
|
9
10
|
from textual.containers import Container, Horizontal, Vertical
|
|
11
|
+
from textual.events import Resize
|
|
10
12
|
from textual.screen import Screen
|
|
11
13
|
from textual.widgets import Button, Markdown, Static
|
|
12
14
|
|
|
15
|
+
from shotgun.tui.layout import TINY_HEIGHT_THRESHOLD
|
|
16
|
+
|
|
13
17
|
if TYPE_CHECKING:
|
|
14
18
|
from ..app import ShotgunApp
|
|
15
19
|
|
|
@@ -100,6 +104,54 @@ class WelcomeScreen(Screen[None]):
|
|
|
100
104
|
color: $warning;
|
|
101
105
|
padding: 0 0 1 0;
|
|
102
106
|
}
|
|
107
|
+
|
|
108
|
+
/* Tiny screen fallback */
|
|
109
|
+
#tiny-welcome-container {
|
|
110
|
+
display: none;
|
|
111
|
+
width: 100%;
|
|
112
|
+
height: auto;
|
|
113
|
+
padding: 0;
|
|
114
|
+
align: center middle;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
#tiny-welcome-message {
|
|
118
|
+
text-align: center;
|
|
119
|
+
padding: 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
#tiny-welcome-link {
|
|
123
|
+
text-align: center;
|
|
124
|
+
padding: 0;
|
|
125
|
+
color: $accent;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
#tiny-welcome-buttons {
|
|
129
|
+
width: auto;
|
|
130
|
+
height: auto;
|
|
131
|
+
padding: 0;
|
|
132
|
+
align: center middle;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
#tiny-welcome-buttons Button {
|
|
136
|
+
margin: 0 1;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* Tiny mode - hide full welcome, show minimal */
|
|
140
|
+
WelcomeScreen.tiny #titlebox {
|
|
141
|
+
display: none;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
WelcomeScreen.tiny #options-container {
|
|
145
|
+
display: none;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
WelcomeScreen.tiny #migration-warning {
|
|
149
|
+
display: none;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
WelcomeScreen.tiny #tiny-welcome-container {
|
|
153
|
+
display: block;
|
|
154
|
+
}
|
|
103
155
|
"""
|
|
104
156
|
|
|
105
157
|
BINDINGS = [
|
|
@@ -107,6 +159,24 @@ class WelcomeScreen(Screen[None]):
|
|
|
107
159
|
]
|
|
108
160
|
|
|
109
161
|
def compose(self) -> ComposeResult:
|
|
162
|
+
# Tiny screen fallback
|
|
163
|
+
with Container(id="tiny-welcome-container"):
|
|
164
|
+
yield Static(
|
|
165
|
+
"Welcome to Shotgun",
|
|
166
|
+
id="tiny-welcome-message",
|
|
167
|
+
)
|
|
168
|
+
yield Static(
|
|
169
|
+
"[@click=screen.open_usage_guide]View setup instructions[/]",
|
|
170
|
+
id="tiny-welcome-link",
|
|
171
|
+
markup=True,
|
|
172
|
+
)
|
|
173
|
+
with Horizontal(id="tiny-welcome-buttons"):
|
|
174
|
+
yield Button(
|
|
175
|
+
"Shotgun Account", id="tiny-shotgun-button", variant="primary"
|
|
176
|
+
)
|
|
177
|
+
yield Button("BYOK", id="tiny-byok-button", variant="success")
|
|
178
|
+
|
|
179
|
+
# Full welcome screen
|
|
110
180
|
with Vertical(id="titlebox"):
|
|
111
181
|
yield Static("Welcome to Shotgun", id="welcome-title")
|
|
112
182
|
yield Static(
|
|
@@ -168,10 +238,29 @@ class WelcomeScreen(Screen[None]):
|
|
|
168
238
|
|
|
169
239
|
def on_mount(self) -> None:
|
|
170
240
|
"""Focus the first button on mount."""
|
|
241
|
+
self._apply_layout_for_height(self.app.size.height)
|
|
171
242
|
self.query_one("#shotgun-button", Button).focus()
|
|
172
243
|
# Update BYOK button text asynchronously
|
|
173
244
|
self.run_worker(self._update_byok_button_text(), exclusive=False)
|
|
174
245
|
|
|
246
|
+
@on(Resize)
|
|
247
|
+
def handle_resize(self, event: Resize) -> None:
|
|
248
|
+
"""Adjust layout based on terminal height."""
|
|
249
|
+
self._apply_layout_for_height(event.size.height)
|
|
250
|
+
|
|
251
|
+
def _apply_layout_for_height(self, height: int) -> None:
|
|
252
|
+
"""Apply appropriate layout based on terminal height."""
|
|
253
|
+
if height < TINY_HEIGHT_THRESHOLD:
|
|
254
|
+
self.add_class("tiny")
|
|
255
|
+
else:
|
|
256
|
+
self.remove_class("tiny")
|
|
257
|
+
|
|
258
|
+
def action_open_usage_guide(self) -> None:
|
|
259
|
+
"""Open the usage guide in browser."""
|
|
260
|
+
webbrowser.open(
|
|
261
|
+
"https://github.com/shotgun-sh/shotgun?tab=readme-ov-file#-usage"
|
|
262
|
+
)
|
|
263
|
+
|
|
175
264
|
async def _update_byok_button_text(self) -> None:
|
|
176
265
|
"""Update BYOK button text based on whether user has existing providers."""
|
|
177
266
|
byok_button = self.query_one("#byok-button", Button)
|
|
@@ -180,11 +269,13 @@ class WelcomeScreen(Screen[None]):
|
|
|
180
269
|
byok_button.label = "I'll stick with my BYOK setup"
|
|
181
270
|
|
|
182
271
|
@on(Button.Pressed, "#shotgun-button")
|
|
272
|
+
@on(Button.Pressed, "#tiny-shotgun-button")
|
|
183
273
|
def _on_shotgun_pressed(self) -> None:
|
|
184
274
|
"""Handle Shotgun Account button press."""
|
|
185
275
|
self.run_worker(self._start_shotgun_auth(), exclusive=True)
|
|
186
276
|
|
|
187
277
|
@on(Button.Pressed, "#byok-button")
|
|
278
|
+
@on(Button.Pressed, "#tiny-byok-button")
|
|
188
279
|
def _on_byok_pressed(self) -> None:
|
|
189
280
|
"""Handle BYOK button press."""
|
|
190
281
|
self.run_worker(self._start_byok_config(), exclusive=True)
|
|
@@ -10,8 +10,11 @@ from typing import TYPE_CHECKING
|
|
|
10
10
|
|
|
11
11
|
import aiofiles.os
|
|
12
12
|
|
|
13
|
-
from shotgun.agents.
|
|
14
|
-
|
|
13
|
+
from shotgun.agents.conversation import (
|
|
14
|
+
ConversationHistory,
|
|
15
|
+
ConversationManager,
|
|
16
|
+
ConversationState,
|
|
17
|
+
)
|
|
15
18
|
from shotgun.agents.models import AgentType
|
|
16
19
|
|
|
17
20
|
if TYPE_CHECKING:
|
|
@@ -24,8 +24,8 @@ from shotgun.tui.screens.chat_screen.history.chat_history import ChatHistory
|
|
|
24
24
|
|
|
25
25
|
if TYPE_CHECKING:
|
|
26
26
|
from shotgun.agents.context_analyzer.models import ContextAnalysis
|
|
27
|
-
from shotgun.agents.conversation_history import HintMessage
|
|
28
27
|
from shotgun.tui.screens.chat import ChatScreen
|
|
28
|
+
from shotgun.tui.screens.chat_screen.hint_message import HintMessage
|
|
29
29
|
|
|
30
30
|
logger = logging.getLogger(__name__)
|
|
31
31
|
|