Files
HyunjunJeon af5fbfabec 문서 추가: Context Engineering 문서 추가 및 deepagents_sourcecode 한국어 번역
- Context_Engineering.md: 에이전트 컨텍스트 엔지니어링 개념 정리 문서 추가
- Context_Engineering_Research.ipynb: 연구 노트북 업데이트
- deepagents_sourcecode/: docstring과 주석을 한국어로 번역
2026-01-11 17:55:52 +09:00

236 lines
6.9 KiB
Python

"""deepagents-cli용 상태 표시줄(Status bar) 위젯입니다."""
from __future__ import annotations
from pathlib import Path
from typing import TYPE_CHECKING, Any
from textual.containers import Horizontal
from textual.css.query import NoMatches
from textual.reactive import reactive
from textual.widgets import Static
if TYPE_CHECKING:
from textual.app import ComposeResult
TOKENS_K_THRESHOLD = 1000
class StatusBar(Horizontal):
"""모드/자동 승인/작업 디렉토리 등을 표시하는 상태 표시줄입니다."""
DEFAULT_CSS = """
StatusBar {
height: 1;
dock: bottom;
background: $surface;
padding: 0 1;
}
StatusBar .status-mode {
width: auto;
padding: 0 1;
}
StatusBar .status-mode.normal {
display: none;
}
StatusBar .status-mode.bash {
background: #ff1493;
color: white;
text-style: bold;
}
StatusBar .status-mode.command {
background: #8b5cf6;
color: white;
}
StatusBar .status-auto-approve {
width: auto;
padding: 0 1;
}
StatusBar .status-auto-approve.on {
background: #10b981;
color: black;
}
StatusBar .status-auto-approve.off {
background: #f59e0b;
color: black;
}
StatusBar .status-message {
width: auto;
padding: 0 1;
color: $text-muted;
}
StatusBar .status-message.thinking {
color: $warning;
}
StatusBar .status-cwd {
width: 1fr;
text-align: right;
color: $text-muted;
}
StatusBar .status-tokens {
width: auto;
padding: 0 1;
color: $text-muted;
}
"""
mode: reactive[str] = reactive("normal", init=False)
status_message: reactive[str] = reactive("", init=False)
auto_approve: reactive[bool] = reactive(default=False, init=False)
cwd: reactive[str] = reactive("", init=False)
tokens: reactive[int] = reactive(0, init=False)
def __init__(self, cwd: str | Path | None = None, **kwargs: Any) -> None:
"""상태 표시줄을 초기화합니다.
Args:
cwd: Current working directory to display
**kwargs: Additional arguments passed to parent
"""
super().__init__(**kwargs)
# 초기 cwd를 저장(compose()에서 사용)
self._initial_cwd = str(cwd) if cwd else str(Path.cwd())
def compose(self) -> ComposeResult:
"""상태 표시줄 레이아웃을 구성합니다."""
yield Static("", classes="status-mode normal", id="mode-indicator")
yield Static(
"manual | shift+tab to cycle",
classes="status-auto-approve off",
id="auto-approve-indicator",
)
yield Static("", classes="status-message", id="status-message")
yield Static("", classes="status-tokens", id="tokens-display")
# CWD shown in welcome banner, not pinned in status bar
def on_mount(self) -> None:
"""마운트(on_mount) 이후 reactive 값을 설정해 watcher가 안전하게 동작하도록 합니다."""
self.cwd = self._initial_cwd
def watch_mode(self, mode: str) -> None:
"""모드(mode) 변경 시 표시를 갱신합니다."""
try:
indicator = self.query_one("#mode-indicator", Static)
except NoMatches:
return
indicator.remove_class("normal", "bash", "command")
if mode == "bash":
indicator.update("BASH")
indicator.add_class("bash")
elif mode == "command":
indicator.update("CMD")
indicator.add_class("command")
else:
indicator.update("")
indicator.add_class("normal")
def watch_auto_approve(self, new_value: bool) -> None: # noqa: FBT001
"""auto-approve 상태 변경 시 표시를 갱신합니다."""
try:
indicator = self.query_one("#auto-approve-indicator", Static)
except NoMatches:
return
indicator.remove_class("on", "off")
if new_value:
indicator.update("auto | shift+tab to cycle")
indicator.add_class("on")
else:
indicator.update("manual | shift+tab to cycle")
indicator.add_class("off")
def watch_cwd(self, new_value: str) -> None:
"""작업 디렉토리(cwd) 변경 시 표시를 갱신합니다."""
try:
display = self.query_one("#cwd-display", Static)
except NoMatches:
return
display.update(self._format_cwd(new_value))
def watch_status_message(self, new_value: str) -> None:
"""상태 메시지(status message) 변경 시 표시를 갱신합니다."""
try:
msg_widget = self.query_one("#status-message", Static)
except NoMatches:
return
msg_widget.remove_class("thinking")
if new_value:
msg_widget.update(new_value)
if "thinking" in new_value.lower() or "executing" in new_value.lower():
msg_widget.add_class("thinking")
else:
msg_widget.update("")
def _format_cwd(self, cwd_path: str = "") -> str:
"""표시용으로 현재 작업 디렉토리를 포맷팅합니다."""
path = Path(cwd_path or self.cwd or self._initial_cwd)
try:
# 홈 디렉토리는 ~로 표시 시도
home = Path.home()
if path.is_relative_to(home):
return "~/" + str(path.relative_to(home))
except (ValueError, RuntimeError):
pass
return str(path)
def set_mode(self, mode: str) -> None:
"""현재 입력 모드를 설정합니다.
Args:
mode: One of "normal", "bash", or "command"
"""
self.mode = mode
def set_auto_approve(self, *, enabled: bool) -> None:
"""auto-approve 상태를 설정합니다.
Args:
enabled: Whether auto-approve is enabled
"""
self.auto_approve = enabled
def set_status_message(self, message: str) -> None:
"""상태 메시지를 설정합니다.
Args:
message: Status message to display (empty string to clear)
"""
self.status_message = message
def watch_tokens(self, new_value: int) -> None:
"""토큰 수 변경 시 표시를 갱신합니다."""
try:
display = self.query_one("#tokens-display", Static)
except NoMatches:
return
if new_value > 0:
# 천 단위는 K suffix로 표시
if new_value >= TOKENS_K_THRESHOLD:
display.update(f"{new_value / TOKENS_K_THRESHOLD:.1f}K tokens")
else:
display.update(f"{new_value} tokens")
else:
display.update("")
def set_tokens(self, count: int) -> None:
"""토큰 수를 설정합니다.
Args:
count: Current context token count
"""
self.tokens = count