sa-assistant 0.2.0__py3-none-any.whl → 0.2.2__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.
- {sa_assistant-0.2.0.dist-info → sa_assistant-0.2.2.dist-info}/METADATA +1 -1
- {sa_assistant-0.2.0.dist-info → sa_assistant-0.2.2.dist-info}/RECORD +7 -6
- tui/app.py +182 -65
- tui/styles.tcss +170 -0
- {sa_assistant-0.2.0.dist-info → sa_assistant-0.2.2.dist-info}/WHEEL +0 -0
- {sa_assistant-0.2.0.dist-info → sa_assistant-0.2.2.dist-info}/entry_points.txt +0 -0
- {sa_assistant-0.2.0.dist-info → sa_assistant-0.2.2.dist-info}/top_level.txt +0 -0
|
@@ -19,9 +19,10 @@ model/load.py,sha256=HTD4RCqoyTwASGBC3AaMEhFVrXwu3bN_URcxLbNSzVk,3036
|
|
|
19
19
|
prompts/__init__.py,sha256=gwn1ncE7d-zuoe1pRI5uwElTQQvO8cJgwZqQEJ6oPYs,1518
|
|
20
20
|
prompts/system_prompts.py,sha256=DynDO1Kjwv_fZUm1nGvSTFGH-MEmS5J9w5U01Csn368,16241
|
|
21
21
|
tui/__init__.py,sha256=AhmFoE3tpm8UY9zYeHnlFRgYh7EDREZ7-W8Ke1RozkI,147
|
|
22
|
-
tui/app.py,sha256=
|
|
23
|
-
|
|
24
|
-
sa_assistant-0.2.
|
|
25
|
-
sa_assistant-0.2.
|
|
26
|
-
sa_assistant-0.2.
|
|
27
|
-
sa_assistant-0.2.
|
|
22
|
+
tui/app.py,sha256=m1FlmMsRRGAxy3I-iBICMsMnyYSksNEmXTxrhhKQpL8,12433
|
|
23
|
+
tui/styles.tcss,sha256=ZiaaFYteHG8QqQHlNoA3l_CX9Lj--Etmv3o1kPxFZXo,2790
|
|
24
|
+
sa_assistant-0.2.2.dist-info/METADATA,sha256=aql71QFB825xZ2yTFzyBNZc2_or_W_yoqs3-XivR03U,3065
|
|
25
|
+
sa_assistant-0.2.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
26
|
+
sa_assistant-0.2.2.dist-info/entry_points.txt,sha256=TeU6i2gSZtrDvfygfvLvplx5Cb3-Z5eqdMwdzG9_eM0,78
|
|
27
|
+
sa_assistant-0.2.2.dist-info/top_level.txt,sha256=g9-TL1R8z-SyVwHtV25DDxAxOiWCgU1ql21VYgzvERc,52
|
|
28
|
+
sa_assistant-0.2.2.dist-info/RECORD,,
|
tui/app.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""SA Assistant TUI Application -
|
|
1
|
+
"""SA Assistant TUI Application - OpenCode-style Terminal User Interface."""
|
|
2
2
|
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
from pathlib import Path
|
|
@@ -7,8 +7,8 @@ from typing import Optional
|
|
|
7
7
|
from textual import work
|
|
8
8
|
from textual.app import App, ComposeResult
|
|
9
9
|
from textual.binding import Binding
|
|
10
|
-
from textual.containers import ScrollableContainer, Vertical
|
|
11
|
-
from textual.widgets import Footer,
|
|
10
|
+
from textual.containers import ScrollableContainer, Vertical, Horizontal
|
|
11
|
+
from textual.widgets import Footer, Input, Static, RichLog, Collapsible
|
|
12
12
|
from textual.worker import Worker
|
|
13
13
|
from rich.text import Text
|
|
14
14
|
from rich.markdown import Markdown
|
|
@@ -26,30 +26,34 @@ from agentskills import discover_skills, generate_skills_prompt, create_skill_to
|
|
|
26
26
|
|
|
27
27
|
CSS_PATH = Path(__file__).parent / "styles.tcss"
|
|
28
28
|
SKILLS_DIR = Path(__file__).parent.parent / "skills"
|
|
29
|
+
VERSION = "0.2.2"
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
class SessionHeader(Static):
|
|
32
|
-
"""Top header showing session info
|
|
33
|
+
"""Top header showing session info in OpenCode style.
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
Format: # New session - {timestamp} {tokens} {percentage} (${cost}) v{version}
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self):
|
|
35
39
|
super().__init__()
|
|
36
40
|
self.session_start = datetime.now()
|
|
37
|
-
self.session_id =
|
|
41
|
+
self.session_id = self.session_start.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
|
|
38
42
|
self.token_count = 0
|
|
43
|
+
self.max_tokens = 200000 # Claude context window
|
|
39
44
|
self.cost = 0.0
|
|
40
45
|
|
|
41
|
-
def compose(self) -> ComposeResult:
|
|
42
|
-
yield Static(id="session-info")
|
|
43
|
-
|
|
44
46
|
def on_mount(self) -> None:
|
|
45
47
|
self._update_display()
|
|
46
48
|
|
|
47
49
|
def _update_display(self) -> None:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
f"#
|
|
51
|
-
f"[dim]{self.token_count:,}
|
|
50
|
+
percentage = int((self.token_count / self.max_tokens) * 100) if self.max_tokens > 0 else 0
|
|
51
|
+
header_text = (
|
|
52
|
+
f"[bold]# New session[/bold] - {self.session_id} "
|
|
53
|
+
f"[dim]{self.token_count:,}[/dim] {percentage}% "
|
|
54
|
+
f"[dim](${self.cost:.2f})[/dim] v{VERSION}"
|
|
52
55
|
)
|
|
56
|
+
self.update(header_text)
|
|
53
57
|
|
|
54
58
|
def update_stats(self, tokens: int = 0, cost: float = 0.0) -> None:
|
|
55
59
|
self.token_count += tokens
|
|
@@ -58,7 +62,10 @@ class SessionHeader(Static):
|
|
|
58
62
|
|
|
59
63
|
|
|
60
64
|
class AgentIndicator(Static):
|
|
61
|
-
"""Bottom indicator showing current agent and model.
|
|
65
|
+
"""Bottom indicator showing current agent and model.
|
|
66
|
+
|
|
67
|
+
Format: ■ SA Assistant · Claude Sonnet
|
|
68
|
+
"""
|
|
62
69
|
|
|
63
70
|
def __init__(self, agent_name: str = "SA Assistant", model_name: str = "Claude Sonnet"):
|
|
64
71
|
super().__init__()
|
|
@@ -69,7 +76,7 @@ class AgentIndicator(Static):
|
|
|
69
76
|
self._update_display()
|
|
70
77
|
|
|
71
78
|
def _update_display(self) -> None:
|
|
72
|
-
self.update(f"[
|
|
79
|
+
self.update(f"[#FF9900]■[/#FF9900] {self.agent_name} · [dim]{self.model_name}[/dim]")
|
|
73
80
|
|
|
74
81
|
def set_agent(self, name: str, model: str = None) -> None:
|
|
75
82
|
self.agent_name = name
|
|
@@ -78,54 +85,133 @@ class AgentIndicator(Static):
|
|
|
78
85
|
self._update_display()
|
|
79
86
|
|
|
80
87
|
|
|
81
|
-
class
|
|
82
|
-
"""
|
|
88
|
+
class HotkeyBar(Static):
|
|
89
|
+
"""Bottom hotkey bar showing available shortcuts.
|
|
90
|
+
|
|
91
|
+
Format: esc interrupt ctrl+l clear ctrl+t theme ctrl+c quit
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
def on_mount(self) -> None:
|
|
95
|
+
hotkeys = (
|
|
96
|
+
"[dim]esc[/dim] interrupt "
|
|
97
|
+
"[dim]ctrl+l[/dim] clear "
|
|
98
|
+
"[dim]ctrl+t[/dim] theme "
|
|
99
|
+
"[dim]ctrl+c[/dim] quit"
|
|
100
|
+
)
|
|
101
|
+
self.update(hotkeys)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class MessagePanel(Vertical):
|
|
105
|
+
"""A bordered panel for displaying a message with OpenCode styling.
|
|
83
106
|
|
|
84
|
-
|
|
85
|
-
|
|
107
|
+
Each message has a header line (# Title) and content area.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
def __init__(
|
|
111
|
+
self,
|
|
112
|
+
title: str,
|
|
113
|
+
content: str = "",
|
|
114
|
+
panel_type: str = "default",
|
|
115
|
+
collapsible: bool = False,
|
|
116
|
+
collapsed: bool = False,
|
|
117
|
+
):
|
|
118
|
+
super().__init__()
|
|
119
|
+
self.title = title
|
|
86
120
|
self._content = content
|
|
121
|
+
self.panel_type = panel_type
|
|
122
|
+
self.collapsible = collapsible
|
|
123
|
+
self.collapsed = collapsed
|
|
87
124
|
self._log: Optional[RichLog] = None
|
|
125
|
+
self.add_class(f"panel-{panel_type}")
|
|
88
126
|
|
|
89
127
|
def compose(self) -> ComposeResult:
|
|
90
|
-
|
|
91
|
-
|
|
128
|
+
# Title header
|
|
129
|
+
title_class = "panel-title"
|
|
130
|
+
if self.collapsible:
|
|
131
|
+
title_class += " collapsible-title"
|
|
132
|
+
collapse_indicator = "▶ " if self.collapsed else "▼ "
|
|
133
|
+
yield Static(
|
|
134
|
+
f"[bold]{collapse_indicator}# {self.title}[/bold]",
|
|
135
|
+
id="panel-header",
|
|
136
|
+
classes=title_class,
|
|
137
|
+
)
|
|
138
|
+
else:
|
|
139
|
+
yield Static(f"[bold]# {self.title}[/bold]", id="panel-header", classes=title_class)
|
|
140
|
+
|
|
141
|
+
# Content area
|
|
142
|
+
self._log = RichLog(highlight=True, markup=True, wrap=True, id="panel-content")
|
|
143
|
+
if not self.collapsed:
|
|
144
|
+
yield self._log
|
|
92
145
|
|
|
93
146
|
def on_mount(self) -> None:
|
|
94
147
|
if self._content and self._log:
|
|
95
148
|
self._log.write(self._content)
|
|
96
149
|
|
|
97
150
|
def append(self, text: str) -> None:
|
|
151
|
+
"""Append text to the content area."""
|
|
98
152
|
if self._log:
|
|
99
153
|
self._log.write(text)
|
|
100
154
|
|
|
101
155
|
def update_content(self, text: str) -> None:
|
|
156
|
+
"""Replace all content."""
|
|
102
157
|
if self._log:
|
|
103
158
|
self._log.clear()
|
|
104
159
|
self._log.write(text)
|
|
105
160
|
|
|
161
|
+
def toggle_collapse(self) -> None:
|
|
162
|
+
"""Toggle collapsed state."""
|
|
163
|
+
if not self.collapsible:
|
|
164
|
+
return
|
|
165
|
+
self.collapsed = not self.collapsed
|
|
166
|
+
header = self.query_one("#panel-header", Static)
|
|
167
|
+
collapse_indicator = "▶ " if self.collapsed else "▼ "
|
|
168
|
+
header.update(f"[bold]{collapse_indicator}# {self.title}[/bold]")
|
|
169
|
+
|
|
170
|
+
if self.collapsed:
|
|
171
|
+
if self._log:
|
|
172
|
+
self._log.remove()
|
|
173
|
+
self._log = None
|
|
174
|
+
else:
|
|
175
|
+
self._log = RichLog(highlight=True, markup=True, wrap=True, id="panel-content")
|
|
176
|
+
self.mount(self._log)
|
|
177
|
+
if self._content:
|
|
178
|
+
self._log.write(self._content)
|
|
179
|
+
|
|
106
180
|
|
|
107
181
|
class ChatContainer(ScrollableContainer):
|
|
108
182
|
"""Main scrollable container for chat messages."""
|
|
109
183
|
|
|
110
184
|
def __init__(self):
|
|
111
185
|
super().__init__()
|
|
112
|
-
self.
|
|
113
|
-
|
|
114
|
-
def add_user_message(self, text: str) ->
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
self.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
186
|
+
self._panels: list[MessagePanel] = []
|
|
187
|
+
|
|
188
|
+
def add_user_message(self, text: str) -> MessagePanel:
|
|
189
|
+
"""Add a user message panel."""
|
|
190
|
+
panel = MessagePanel(
|
|
191
|
+
title="You",
|
|
192
|
+
content=text,
|
|
193
|
+
panel_type="user",
|
|
194
|
+
collapsible=False,
|
|
195
|
+
)
|
|
196
|
+
self._panels.append(panel)
|
|
197
|
+
self.mount(panel)
|
|
198
|
+
panel.scroll_visible()
|
|
199
|
+
return panel
|
|
200
|
+
|
|
201
|
+
def add_assistant_message(self, title: str = "SA Assistant") -> MessagePanel:
|
|
202
|
+
"""Add an assistant message panel."""
|
|
203
|
+
panel = MessagePanel(
|
|
204
|
+
title=title,
|
|
205
|
+
panel_type="assistant",
|
|
206
|
+
collapsible=False,
|
|
207
|
+
)
|
|
208
|
+
self._panels.append(panel)
|
|
209
|
+
self.mount(panel)
|
|
210
|
+
panel.scroll_visible()
|
|
211
|
+
return panel
|
|
212
|
+
|
|
213
|
+
def add_tool_panel(self, tool_name: str, collapsed: bool = True) -> MessagePanel:
|
|
214
|
+
"""Add a tool execution panel (collapsible)."""
|
|
129
215
|
emoji_map = {
|
|
130
216
|
"consult_guru": "🧙",
|
|
131
217
|
"consult_specialist": "🔬",
|
|
@@ -133,37 +219,54 @@ class ChatContainer(ScrollableContainer):
|
|
|
133
219
|
"file_write": "💾",
|
|
134
220
|
"shell": "🖥️",
|
|
135
221
|
"skill": "📚",
|
|
222
|
+
"image_reader": "🖼️",
|
|
136
223
|
}
|
|
137
224
|
emoji = emoji_map.get(tool_name, "🔧")
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
225
|
+
panel = MessagePanel(
|
|
226
|
+
title=f"{emoji} {tool_name}",
|
|
227
|
+
panel_type="tool",
|
|
228
|
+
collapsible=True,
|
|
229
|
+
collapsed=collapsed,
|
|
230
|
+
)
|
|
231
|
+
self._panels.append(panel)
|
|
232
|
+
self.mount(panel)
|
|
233
|
+
return panel
|
|
234
|
+
|
|
235
|
+
def add_thinking_panel(self) -> MessagePanel:
|
|
236
|
+
"""Add a thinking/reasoning panel (collapsible)."""
|
|
237
|
+
panel = MessagePanel(
|
|
238
|
+
title="💭 Thinking",
|
|
239
|
+
panel_type="thinking",
|
|
240
|
+
collapsible=True,
|
|
241
|
+
collapsed=True,
|
|
242
|
+
)
|
|
243
|
+
self._panels.append(panel)
|
|
244
|
+
self.mount(panel)
|
|
245
|
+
return panel
|
|
142
246
|
|
|
143
247
|
|
|
144
248
|
class SAAssistantApp(App):
|
|
145
|
-
"""SA Assistant Textual TUI Application."""
|
|
249
|
+
"""SA Assistant Textual TUI Application - OpenCode Style."""
|
|
146
250
|
|
|
147
251
|
CSS_PATH = "styles.tcss"
|
|
148
|
-
|
|
149
252
|
TITLE = "SA Assistant"
|
|
150
|
-
SUB_TITLE = "AWS Solutions Architect Agent"
|
|
151
253
|
|
|
152
254
|
BINDINGS = [
|
|
153
|
-
Binding("escape", "cancel", "
|
|
154
|
-
Binding("ctrl+c", "quit", "Quit", show=
|
|
155
|
-
Binding("ctrl+l", "clear", "Clear", show=
|
|
156
|
-
Binding("ctrl+t", "toggle_dark", "Theme", show=
|
|
255
|
+
Binding("escape", "cancel", "Interrupt", show=False),
|
|
256
|
+
Binding("ctrl+c", "quit", "Quit", show=False),
|
|
257
|
+
Binding("ctrl+l", "clear", "Clear", show=False),
|
|
258
|
+
Binding("ctrl+t", "toggle_dark", "Theme", show=False),
|
|
157
259
|
]
|
|
158
260
|
|
|
159
261
|
def __init__(self):
|
|
160
262
|
super().__init__()
|
|
161
263
|
self._agent: Optional[Agent] = None
|
|
162
|
-
self.
|
|
264
|
+
self._current_panel: Optional[MessagePanel] = None
|
|
163
265
|
self._response_buffer = ""
|
|
164
266
|
self._init_agent()
|
|
165
267
|
|
|
166
268
|
def _init_agent(self) -> None:
|
|
269
|
+
"""Initialize the Strands agent."""
|
|
167
270
|
discovered_skills = discover_skills(SKILLS_DIR)
|
|
168
271
|
skills_prompt = generate_skills_prompt(discovered_skills)
|
|
169
272
|
full_prompt = ORCHESTRATOR_PROMPT + skills_prompt
|
|
@@ -194,73 +297,87 @@ class SAAssistantApp(App):
|
|
|
194
297
|
)
|
|
195
298
|
|
|
196
299
|
def _streaming_callback(self, **kwargs) -> None:
|
|
300
|
+
"""Handle streaming responses from the agent."""
|
|
197
301
|
if "data" in kwargs:
|
|
198
302
|
self._response_buffer += kwargs["data"]
|
|
199
|
-
if self.
|
|
200
|
-
self.call_from_thread(self.
|
|
303
|
+
if self._current_panel:
|
|
304
|
+
self.call_from_thread(self._current_panel.update_content, self._response_buffer)
|
|
201
305
|
|
|
202
306
|
if "current_tool_use" in kwargs:
|
|
203
307
|
tool_use = kwargs["current_tool_use"]
|
|
204
308
|
if isinstance(tool_use, dict):
|
|
205
309
|
tool_name = tool_use.get("name", "tool")
|
|
206
|
-
self.call_from_thread(self.
|
|
310
|
+
self.call_from_thread(self._add_tool_panel, tool_name)
|
|
207
311
|
|
|
208
|
-
def
|
|
312
|
+
def _add_tool_panel(self, tool_name: str) -> None:
|
|
313
|
+
"""Add a tool execution panel to the chat."""
|
|
209
314
|
chat = self.query_one(ChatContainer)
|
|
210
|
-
chat.
|
|
315
|
+
chat.add_tool_panel(tool_name, collapsed=True)
|
|
211
316
|
|
|
212
317
|
def compose(self) -> ComposeResult:
|
|
213
|
-
|
|
318
|
+
"""Compose the UI layout."""
|
|
214
319
|
yield SessionHeader()
|
|
215
320
|
yield ChatContainer()
|
|
216
321
|
yield AgentIndicator()
|
|
217
|
-
yield Input(placeholder="Type your message... (Enter to send)")
|
|
218
|
-
yield
|
|
322
|
+
yield Input(placeholder="Type your message... (Enter to send)", id="input-field")
|
|
323
|
+
yield HotkeyBar()
|
|
219
324
|
|
|
220
325
|
def on_mount(self) -> None:
|
|
221
|
-
|
|
326
|
+
"""Focus the input field on mount."""
|
|
327
|
+
self.query_one("#input-field", Input).focus()
|
|
222
328
|
|
|
223
329
|
async def on_input_submitted(self, event: Input.Submitted) -> None:
|
|
330
|
+
"""Handle user input submission."""
|
|
224
331
|
user_input = event.value.strip()
|
|
225
332
|
if not user_input:
|
|
226
333
|
return
|
|
227
334
|
|
|
335
|
+
# Clear input
|
|
228
336
|
event.input.value = ""
|
|
229
337
|
|
|
338
|
+
# Handle exit commands
|
|
230
339
|
if user_input.lower() in ("exit", "quit", "종료"):
|
|
231
340
|
self.exit()
|
|
232
341
|
return
|
|
233
342
|
|
|
343
|
+
# Add user message to chat
|
|
234
344
|
chat = self.query_one(ChatContainer)
|
|
235
345
|
chat.add_user_message(user_input)
|
|
236
346
|
|
|
237
|
-
|
|
347
|
+
# Prepare for assistant response
|
|
348
|
+
self._current_panel = chat.add_assistant_message()
|
|
238
349
|
self._response_buffer = ""
|
|
239
350
|
|
|
351
|
+
# Run agent in background thread
|
|
240
352
|
self.run_agent(user_input)
|
|
241
353
|
|
|
242
354
|
@work(thread=True)
|
|
243
355
|
def run_agent(self, prompt: str) -> None:
|
|
356
|
+
"""Run the agent in a background thread."""
|
|
244
357
|
try:
|
|
245
358
|
self._agent(prompt)
|
|
246
359
|
except Exception as e:
|
|
247
|
-
if self.
|
|
248
|
-
self.call_from_thread(
|
|
249
|
-
self._current_section.update_content, f"[red]Error: {e}[/red]"
|
|
250
|
-
)
|
|
360
|
+
if self._current_panel:
|
|
361
|
+
self.call_from_thread(self._current_panel.update_content, f"[red]Error: {e}[/red]")
|
|
251
362
|
|
|
252
363
|
def action_cancel(self) -> None:
|
|
364
|
+
"""Cancel running operations."""
|
|
253
365
|
self.workers.cancel_all()
|
|
254
366
|
|
|
255
367
|
def action_clear(self) -> None:
|
|
368
|
+
"""Clear the chat container."""
|
|
256
369
|
chat = self.query_one(ChatContainer)
|
|
257
|
-
chat.
|
|
370
|
+
for panel in chat._panels:
|
|
371
|
+
panel.remove()
|
|
372
|
+
chat._panels.clear()
|
|
258
373
|
|
|
259
374
|
def action_toggle_dark(self) -> None:
|
|
375
|
+
"""Toggle dark/light theme."""
|
|
260
376
|
self.dark = not self.dark
|
|
261
377
|
|
|
262
378
|
|
|
263
379
|
def run_app() -> None:
|
|
380
|
+
"""Entry point for the TUI application."""
|
|
264
381
|
app = SAAssistantApp()
|
|
265
382
|
app.run()
|
|
266
383
|
|
tui/styles.tcss
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/* SA Assistant TUI - OpenCode Style Theme */
|
|
2
|
+
|
|
3
|
+
$aws-orange: #FF9900;
|
|
4
|
+
$aws-dark: #232F3E;
|
|
5
|
+
$aws-blue: #1A73E8;
|
|
6
|
+
$border-color: #3D4F5F;
|
|
7
|
+
$panel-bg: #1E1E1E;
|
|
8
|
+
|
|
9
|
+
/* ===== Screen ===== */
|
|
10
|
+
Screen {
|
|
11
|
+
background: $surface;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/* ===== Session Header ===== */
|
|
15
|
+
SessionHeader {
|
|
16
|
+
dock: top;
|
|
17
|
+
height: 3;
|
|
18
|
+
background: $surface-darken-1;
|
|
19
|
+
border-bottom: solid $border-color;
|
|
20
|
+
padding: 1 2;
|
|
21
|
+
color: $text;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* ===== Agent Indicator ===== */
|
|
25
|
+
AgentIndicator {
|
|
26
|
+
dock: bottom;
|
|
27
|
+
height: 3;
|
|
28
|
+
background: $surface-darken-2;
|
|
29
|
+
border-top: solid $border-color;
|
|
30
|
+
padding: 1 2;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* ===== Hotkey Bar ===== */
|
|
34
|
+
HotkeyBar {
|
|
35
|
+
dock: bottom;
|
|
36
|
+
height: 1;
|
|
37
|
+
background: $aws-dark;
|
|
38
|
+
padding: 0 2;
|
|
39
|
+
color: $text-muted;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* ===== Input Field ===== */
|
|
43
|
+
Input {
|
|
44
|
+
dock: bottom;
|
|
45
|
+
margin: 0 1;
|
|
46
|
+
border: tall $border-color;
|
|
47
|
+
background: $surface-darken-1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
Input:focus {
|
|
51
|
+
border: tall $aws-orange;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* ===== Chat Container ===== */
|
|
55
|
+
ChatContainer {
|
|
56
|
+
height: 1fr;
|
|
57
|
+
padding: 0 1;
|
|
58
|
+
overflow-y: auto;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* ===== Message Panels ===== */
|
|
62
|
+
MessagePanel {
|
|
63
|
+
margin: 1 0;
|
|
64
|
+
border: round $border-color;
|
|
65
|
+
background: $panel-bg;
|
|
66
|
+
height: auto;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Panel Title Header */
|
|
70
|
+
.panel-title {
|
|
71
|
+
background: $surface-darken-2;
|
|
72
|
+
padding: 0 1;
|
|
73
|
+
height: 1;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.panel-title:hover {
|
|
77
|
+
background: $surface-darken-1;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.collapsible-title {
|
|
81
|
+
text-style: bold;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.collapsible-title:hover {
|
|
85
|
+
background: $primary-darken-2;
|
|
86
|
+
cursor: pointer;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* Panel Content */
|
|
90
|
+
#panel-content {
|
|
91
|
+
height: auto;
|
|
92
|
+
max-height: 40;
|
|
93
|
+
padding: 1 2;
|
|
94
|
+
background: $surface;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* ===== Panel Type Variations ===== */
|
|
98
|
+
|
|
99
|
+
/* User messages - green accent */
|
|
100
|
+
.panel-user {
|
|
101
|
+
border: round green;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.panel-user .panel-title {
|
|
105
|
+
color: green;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* Assistant messages - orange accent */
|
|
109
|
+
.panel-assistant {
|
|
110
|
+
border: round $aws-orange;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.panel-assistant .panel-title {
|
|
114
|
+
color: $aws-orange;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* Tool panels - cyan accent, dashed border */
|
|
118
|
+
.panel-tool {
|
|
119
|
+
margin-left: 2;
|
|
120
|
+
border: dashed cyan;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.panel-tool .panel-title {
|
|
124
|
+
color: cyan;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.panel-tool #panel-content {
|
|
128
|
+
max-height: 20;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* Thinking panels - dim purple */
|
|
132
|
+
.panel-thinking {
|
|
133
|
+
margin-left: 2;
|
|
134
|
+
border: dashed magenta 50%;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.panel-thinking .panel-title {
|
|
138
|
+
color: magenta 50%;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.panel-thinking #panel-content {
|
|
142
|
+
max-height: 15;
|
|
143
|
+
color: $text-muted;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* ===== RichLog Styling ===== */
|
|
147
|
+
RichLog {
|
|
148
|
+
height: auto;
|
|
149
|
+
padding: 1 2;
|
|
150
|
+
background: $surface;
|
|
151
|
+
scrollbar-size: 1 1;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/* ===== Scrollbar ===== */
|
|
155
|
+
ChatContainer {
|
|
156
|
+
scrollbar-size: 1 1;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* ===== Dark Mode Adjustments ===== */
|
|
160
|
+
Screen.-dark-mode {
|
|
161
|
+
background: #0D1117;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
Screen.-dark-mode MessagePanel {
|
|
165
|
+
background: #161B22;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
Screen.-dark-mode .panel-title {
|
|
169
|
+
background: #21262D;
|
|
170
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|