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.
Files changed (288) hide show
  1. superqode/__init__.py +33 -0
  2. superqode/acp/__init__.py +23 -0
  3. superqode/acp/client.py +913 -0
  4. superqode/acp/permission_screen.py +457 -0
  5. superqode/acp/types.py +480 -0
  6. superqode/acp_discovery.py +856 -0
  7. superqode/agent/__init__.py +22 -0
  8. superqode/agent/edit_strategies.py +334 -0
  9. superqode/agent/loop.py +892 -0
  10. superqode/agent/qe_report_templates.py +39 -0
  11. superqode/agent/system_prompts.py +353 -0
  12. superqode/agent_output.py +721 -0
  13. superqode/agent_stream.py +953 -0
  14. superqode/agents/__init__.py +59 -0
  15. superqode/agents/acp_registry.py +305 -0
  16. superqode/agents/client.py +249 -0
  17. superqode/agents/data/augmentcode.com.toml +51 -0
  18. superqode/agents/data/cagent.dev.toml +51 -0
  19. superqode/agents/data/claude.com.toml +60 -0
  20. superqode/agents/data/codeassistant.dev.toml +51 -0
  21. superqode/agents/data/codex.openai.com.toml +57 -0
  22. superqode/agents/data/fastagent.ai.toml +66 -0
  23. superqode/agents/data/geminicli.com.toml +77 -0
  24. superqode/agents/data/goose.block.xyz.toml +54 -0
  25. superqode/agents/data/junie.jetbrains.com.toml +56 -0
  26. superqode/agents/data/kimi.moonshot.cn.toml +57 -0
  27. superqode/agents/data/llmlingagent.dev.toml +51 -0
  28. superqode/agents/data/molt.bot.toml +49 -0
  29. superqode/agents/data/opencode.ai.toml +60 -0
  30. superqode/agents/data/stakpak.dev.toml +51 -0
  31. superqode/agents/data/vtcode.dev.toml +51 -0
  32. superqode/agents/discovery.py +266 -0
  33. superqode/agents/messaging.py +160 -0
  34. superqode/agents/persona.py +166 -0
  35. superqode/agents/registry.py +421 -0
  36. superqode/agents/schema.py +72 -0
  37. superqode/agents/unified.py +367 -0
  38. superqode/app/__init__.py +111 -0
  39. superqode/app/constants.py +314 -0
  40. superqode/app/css.py +366 -0
  41. superqode/app/models.py +118 -0
  42. superqode/app/suggester.py +125 -0
  43. superqode/app/widgets.py +1591 -0
  44. superqode/app_enhanced.py +399 -0
  45. superqode/app_main.py +17187 -0
  46. superqode/approval.py +312 -0
  47. superqode/atomic.py +296 -0
  48. superqode/commands/__init__.py +1 -0
  49. superqode/commands/acp.py +965 -0
  50. superqode/commands/agents.py +180 -0
  51. superqode/commands/auth.py +278 -0
  52. superqode/commands/config.py +374 -0
  53. superqode/commands/init.py +826 -0
  54. superqode/commands/providers.py +819 -0
  55. superqode/commands/qe.py +1145 -0
  56. superqode/commands/roles.py +380 -0
  57. superqode/commands/serve.py +172 -0
  58. superqode/commands/suggestions.py +127 -0
  59. superqode/commands/superqe.py +460 -0
  60. superqode/config/__init__.py +51 -0
  61. superqode/config/loader.py +812 -0
  62. superqode/config/schema.py +498 -0
  63. superqode/core/__init__.py +111 -0
  64. superqode/core/roles.py +281 -0
  65. superqode/danger.py +386 -0
  66. superqode/data/superqode-template.yaml +1522 -0
  67. superqode/design_system.py +1080 -0
  68. superqode/dialogs/__init__.py +6 -0
  69. superqode/dialogs/base.py +39 -0
  70. superqode/dialogs/model.py +130 -0
  71. superqode/dialogs/provider.py +870 -0
  72. superqode/diff_view.py +919 -0
  73. superqode/enterprise.py +21 -0
  74. superqode/evaluation/__init__.py +25 -0
  75. superqode/evaluation/adapters.py +93 -0
  76. superqode/evaluation/behaviors.py +89 -0
  77. superqode/evaluation/engine.py +209 -0
  78. superqode/evaluation/scenarios.py +96 -0
  79. superqode/execution/__init__.py +36 -0
  80. superqode/execution/linter.py +538 -0
  81. superqode/execution/modes.py +347 -0
  82. superqode/execution/resolver.py +283 -0
  83. superqode/execution/runner.py +642 -0
  84. superqode/file_explorer.py +811 -0
  85. superqode/file_viewer.py +471 -0
  86. superqode/flash.py +183 -0
  87. superqode/guidance/__init__.py +58 -0
  88. superqode/guidance/config.py +203 -0
  89. superqode/guidance/prompts.py +71 -0
  90. superqode/harness/__init__.py +54 -0
  91. superqode/harness/accelerator.py +291 -0
  92. superqode/harness/config.py +319 -0
  93. superqode/harness/validator.py +147 -0
  94. superqode/history.py +279 -0
  95. superqode/integrations/superopt_runner.py +124 -0
  96. superqode/logging/__init__.py +49 -0
  97. superqode/logging/adapters.py +219 -0
  98. superqode/logging/formatter.py +923 -0
  99. superqode/logging/integration.py +341 -0
  100. superqode/logging/sinks.py +170 -0
  101. superqode/logging/unified_log.py +417 -0
  102. superqode/lsp/__init__.py +26 -0
  103. superqode/lsp/client.py +544 -0
  104. superqode/main.py +1069 -0
  105. superqode/mcp/__init__.py +89 -0
  106. superqode/mcp/auth_storage.py +380 -0
  107. superqode/mcp/client.py +1236 -0
  108. superqode/mcp/config.py +319 -0
  109. superqode/mcp/integration.py +337 -0
  110. superqode/mcp/oauth.py +436 -0
  111. superqode/mcp/oauth_callback.py +385 -0
  112. superqode/mcp/types.py +290 -0
  113. superqode/memory/__init__.py +31 -0
  114. superqode/memory/feedback.py +342 -0
  115. superqode/memory/store.py +522 -0
  116. superqode/notifications.py +369 -0
  117. superqode/optimization/__init__.py +5 -0
  118. superqode/optimization/config.py +33 -0
  119. superqode/permissions/__init__.py +25 -0
  120. superqode/permissions/rules.py +488 -0
  121. superqode/plan.py +323 -0
  122. superqode/providers/__init__.py +33 -0
  123. superqode/providers/gateway/__init__.py +165 -0
  124. superqode/providers/gateway/base.py +228 -0
  125. superqode/providers/gateway/litellm_gateway.py +1170 -0
  126. superqode/providers/gateway/openresponses_gateway.py +436 -0
  127. superqode/providers/health.py +297 -0
  128. superqode/providers/huggingface/__init__.py +74 -0
  129. superqode/providers/huggingface/downloader.py +472 -0
  130. superqode/providers/huggingface/endpoints.py +442 -0
  131. superqode/providers/huggingface/hub.py +531 -0
  132. superqode/providers/huggingface/inference.py +394 -0
  133. superqode/providers/huggingface/transformers_runner.py +516 -0
  134. superqode/providers/local/__init__.py +100 -0
  135. superqode/providers/local/base.py +438 -0
  136. superqode/providers/local/discovery.py +418 -0
  137. superqode/providers/local/lmstudio.py +256 -0
  138. superqode/providers/local/mlx.py +457 -0
  139. superqode/providers/local/ollama.py +486 -0
  140. superqode/providers/local/sglang.py +268 -0
  141. superqode/providers/local/tgi.py +260 -0
  142. superqode/providers/local/tool_support.py +477 -0
  143. superqode/providers/local/vllm.py +258 -0
  144. superqode/providers/manager.py +1338 -0
  145. superqode/providers/models.py +1016 -0
  146. superqode/providers/models_dev.py +578 -0
  147. superqode/providers/openresponses/__init__.py +87 -0
  148. superqode/providers/openresponses/converters/__init__.py +17 -0
  149. superqode/providers/openresponses/converters/messages.py +343 -0
  150. superqode/providers/openresponses/converters/tools.py +268 -0
  151. superqode/providers/openresponses/schema/__init__.py +56 -0
  152. superqode/providers/openresponses/schema/models.py +585 -0
  153. superqode/providers/openresponses/streaming/__init__.py +5 -0
  154. superqode/providers/openresponses/streaming/parser.py +338 -0
  155. superqode/providers/openresponses/tools/__init__.py +21 -0
  156. superqode/providers/openresponses/tools/apply_patch.py +352 -0
  157. superqode/providers/openresponses/tools/code_interpreter.py +290 -0
  158. superqode/providers/openresponses/tools/file_search.py +333 -0
  159. superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
  160. superqode/providers/registry.py +716 -0
  161. superqode/providers/usage.py +332 -0
  162. superqode/pure_mode.py +384 -0
  163. superqode/qr/__init__.py +23 -0
  164. superqode/qr/dashboard.py +781 -0
  165. superqode/qr/generator.py +1018 -0
  166. superqode/qr/templates.py +135 -0
  167. superqode/safety/__init__.py +41 -0
  168. superqode/safety/sandbox.py +413 -0
  169. superqode/safety/warnings.py +256 -0
  170. superqode/server/__init__.py +33 -0
  171. superqode/server/lsp_server.py +775 -0
  172. superqode/server/web.py +250 -0
  173. superqode/session/__init__.py +25 -0
  174. superqode/session/persistence.py +580 -0
  175. superqode/session/sharing.py +477 -0
  176. superqode/session.py +475 -0
  177. superqode/sidebar.py +2991 -0
  178. superqode/stream_view.py +648 -0
  179. superqode/styles/__init__.py +3 -0
  180. superqode/superqe/__init__.py +184 -0
  181. superqode/superqe/acp_runner.py +1064 -0
  182. superqode/superqe/constitution/__init__.py +62 -0
  183. superqode/superqe/constitution/evaluator.py +308 -0
  184. superqode/superqe/constitution/loader.py +432 -0
  185. superqode/superqe/constitution/schema.py +250 -0
  186. superqode/superqe/events.py +591 -0
  187. superqode/superqe/frameworks/__init__.py +65 -0
  188. superqode/superqe/frameworks/base.py +234 -0
  189. superqode/superqe/frameworks/e2e.py +263 -0
  190. superqode/superqe/frameworks/executor.py +237 -0
  191. superqode/superqe/frameworks/javascript.py +409 -0
  192. superqode/superqe/frameworks/python.py +373 -0
  193. superqode/superqe/frameworks/registry.py +92 -0
  194. superqode/superqe/mcp_tools/__init__.py +47 -0
  195. superqode/superqe/mcp_tools/core_tools.py +418 -0
  196. superqode/superqe/mcp_tools/registry.py +230 -0
  197. superqode/superqe/mcp_tools/testing_tools.py +167 -0
  198. superqode/superqe/noise.py +89 -0
  199. superqode/superqe/orchestrator.py +778 -0
  200. superqode/superqe/roles.py +609 -0
  201. superqode/superqe/session.py +713 -0
  202. superqode/superqe/skills/__init__.py +57 -0
  203. superqode/superqe/skills/base.py +106 -0
  204. superqode/superqe/skills/core_skills.py +899 -0
  205. superqode/superqe/skills/registry.py +90 -0
  206. superqode/superqe/verifier.py +101 -0
  207. superqode/superqe_cli.py +76 -0
  208. superqode/tool_call.py +358 -0
  209. superqode/tools/__init__.py +93 -0
  210. superqode/tools/agent_tools.py +496 -0
  211. superqode/tools/base.py +324 -0
  212. superqode/tools/batch_tool.py +133 -0
  213. superqode/tools/diagnostics.py +311 -0
  214. superqode/tools/edit_tools.py +653 -0
  215. superqode/tools/enhanced_base.py +515 -0
  216. superqode/tools/file_tools.py +269 -0
  217. superqode/tools/file_tracking.py +45 -0
  218. superqode/tools/lsp_tools.py +610 -0
  219. superqode/tools/network_tools.py +350 -0
  220. superqode/tools/permissions.py +400 -0
  221. superqode/tools/question_tool.py +324 -0
  222. superqode/tools/search_tools.py +598 -0
  223. superqode/tools/shell_tools.py +259 -0
  224. superqode/tools/todo_tools.py +121 -0
  225. superqode/tools/validation.py +80 -0
  226. superqode/tools/web_tools.py +639 -0
  227. superqode/tui.py +1152 -0
  228. superqode/tui_integration.py +875 -0
  229. superqode/tui_widgets/__init__.py +27 -0
  230. superqode/tui_widgets/widgets/__init__.py +18 -0
  231. superqode/tui_widgets/widgets/progress.py +185 -0
  232. superqode/tui_widgets/widgets/tool_display.py +188 -0
  233. superqode/undo_manager.py +574 -0
  234. superqode/utils/__init__.py +5 -0
  235. superqode/utils/error_handling.py +323 -0
  236. superqode/utils/fuzzy.py +257 -0
  237. superqode/widgets/__init__.py +477 -0
  238. superqode/widgets/agent_collab.py +390 -0
  239. superqode/widgets/agent_store.py +936 -0
  240. superqode/widgets/agent_switcher.py +395 -0
  241. superqode/widgets/animation_manager.py +284 -0
  242. superqode/widgets/code_context.py +356 -0
  243. superqode/widgets/command_palette.py +412 -0
  244. superqode/widgets/connection_status.py +537 -0
  245. superqode/widgets/conversation_history.py +470 -0
  246. superqode/widgets/diff_indicator.py +155 -0
  247. superqode/widgets/enhanced_status_bar.py +385 -0
  248. superqode/widgets/enhanced_toast.py +476 -0
  249. superqode/widgets/file_browser.py +809 -0
  250. superqode/widgets/file_reference.py +585 -0
  251. superqode/widgets/issue_timeline.py +340 -0
  252. superqode/widgets/leader_key.py +264 -0
  253. superqode/widgets/mode_switcher.py +445 -0
  254. superqode/widgets/model_picker.py +234 -0
  255. superqode/widgets/permission_preview.py +1205 -0
  256. superqode/widgets/prompt.py +358 -0
  257. superqode/widgets/provider_connect.py +725 -0
  258. superqode/widgets/pty_shell.py +587 -0
  259. superqode/widgets/qe_dashboard.py +321 -0
  260. superqode/widgets/resizable_sidebar.py +377 -0
  261. superqode/widgets/response_changes.py +218 -0
  262. superqode/widgets/response_display.py +528 -0
  263. superqode/widgets/rich_tool_display.py +613 -0
  264. superqode/widgets/sidebar_panels.py +1180 -0
  265. superqode/widgets/slash_complete.py +356 -0
  266. superqode/widgets/split_view.py +612 -0
  267. superqode/widgets/status_bar.py +273 -0
  268. superqode/widgets/superqode_display.py +786 -0
  269. superqode/widgets/thinking_display.py +815 -0
  270. superqode/widgets/throbber.py +87 -0
  271. superqode/widgets/toast.py +206 -0
  272. superqode/widgets/unified_output.py +1073 -0
  273. superqode/workspace/__init__.py +75 -0
  274. superqode/workspace/artifacts.py +472 -0
  275. superqode/workspace/coordinator.py +353 -0
  276. superqode/workspace/diff_tracker.py +429 -0
  277. superqode/workspace/git_guard.py +373 -0
  278. superqode/workspace/git_snapshot.py +526 -0
  279. superqode/workspace/manager.py +750 -0
  280. superqode/workspace/snapshot.py +357 -0
  281. superqode/workspace/watcher.py +535 -0
  282. superqode/workspace/worktree.py +440 -0
  283. superqode-0.1.5.dist-info/METADATA +204 -0
  284. superqode-0.1.5.dist-info/RECORD +288 -0
  285. superqode-0.1.5.dist-info/WHEEL +5 -0
  286. superqode-0.1.5.dist-info/entry_points.txt +3 -0
  287. superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
  288. superqode-0.1.5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,528 @@
1
+ """
2
+ Response Display Widget - Beautiful Agent Response Rendering.
3
+
4
+ Renders agent responses with:
5
+ - Rich markdown formatting
6
+ - Syntax-highlighted code blocks with copy button
7
+ - Collapsible sections
8
+ - Inline diffs
9
+ - Structured data display (tables, lists)
10
+ - Beautiful typography and spacing
11
+
12
+ Makes agent responses a joy to read.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import re
18
+ from dataclasses import dataclass, field
19
+ from datetime import datetime
20
+ from enum import Enum
21
+ from typing import Any, Dict, List, Optional, Tuple
22
+
23
+ from rich.console import RenderableType, Group
24
+ from rich.markdown import Markdown
25
+ from rich.panel import Panel
26
+ from rich.syntax import Syntax
27
+ from rich.table import Table
28
+ from rich.text import Text
29
+ from rich.box import ROUNDED, SIMPLE, HEAVY
30
+ from textual.reactive import reactive
31
+ from textual.widgets import Static
32
+ from textual.containers import Container, Vertical, Horizontal
33
+ from textual import events
34
+
35
+
36
+ class ResponseState(Enum):
37
+ """State of response rendering."""
38
+
39
+ STREAMING = "streaming"
40
+ COMPLETE = "complete"
41
+ ERROR = "error"
42
+
43
+
44
+ @dataclass
45
+ class CodeBlock:
46
+ """A code block in the response."""
47
+
48
+ code: str
49
+ language: str = "text"
50
+ filename: str = ""
51
+ start_line: int = 1
52
+
53
+
54
+ @dataclass
55
+ class ParsedResponse:
56
+ """Parsed response with structured content."""
57
+
58
+ raw_text: str
59
+ paragraphs: List[str] = field(default_factory=list)
60
+ code_blocks: List[CodeBlock] = field(default_factory=list)
61
+ bullet_lists: List[List[str]] = field(default_factory=list)
62
+ numbered_lists: List[List[str]] = field(default_factory=list)
63
+ headers: List[Tuple[int, str]] = field(default_factory=list) # (level, text)
64
+
65
+ @classmethod
66
+ def parse(cls, text: str) -> "ParsedResponse":
67
+ """Parse markdown text into structured content."""
68
+ result = cls(raw_text=text)
69
+
70
+ # Extract code blocks first (to avoid parsing their content)
71
+ code_pattern = r"```(\w*)\n(.*?)```"
72
+ text_without_code = text
73
+
74
+ for match in re.finditer(code_pattern, text, re.DOTALL):
75
+ lang = match.group(1) or "text"
76
+ code = match.group(2).strip()
77
+ result.code_blocks.append(CodeBlock(code=code, language=lang))
78
+
79
+ text_without_code = re.sub(code_pattern, "<<<CODE_BLOCK>>>", text, flags=re.DOTALL)
80
+
81
+ # Extract headers
82
+ for match in re.finditer(r"^(#{1,6})\s+(.+)$", text_without_code, re.MULTILINE):
83
+ level = len(match.group(1))
84
+ header_text = match.group(2).strip()
85
+ result.headers.append((level, header_text))
86
+
87
+ # Extract bullet lists
88
+ current_list = []
89
+ in_list = False
90
+
91
+ for line in text_without_code.split("\n"):
92
+ bullet_match = re.match(r"^\s*[-*•]\s+(.+)$", line)
93
+ if bullet_match:
94
+ in_list = True
95
+ current_list.append(bullet_match.group(1).strip())
96
+ elif in_list and line.strip():
97
+ # Continue list item
98
+ current_list[-1] += " " + line.strip()
99
+ elif in_list and not line.strip():
100
+ # End of list
101
+ if current_list:
102
+ result.bullet_lists.append(current_list)
103
+ current_list = []
104
+ in_list = False
105
+
106
+ if current_list:
107
+ result.bullet_lists.append(current_list)
108
+
109
+ # Extract paragraphs (non-list, non-header content)
110
+ lines = text_without_code.split("\n")
111
+ current_para = []
112
+
113
+ for line in lines:
114
+ stripped = line.strip()
115
+
116
+ # Skip code block placeholders
117
+ if "<<<CODE_BLOCK>>>" in stripped:
118
+ continue
119
+
120
+ # Skip headers
121
+ if re.match(r"^#{1,6}\s+", stripped):
122
+ if current_para:
123
+ result.paragraphs.append(" ".join(current_para))
124
+ current_para = []
125
+ continue
126
+
127
+ # Skip list items
128
+ if re.match(r"^\s*[-*•\d.]\s+", stripped):
129
+ continue
130
+
131
+ if stripped:
132
+ current_para.append(stripped)
133
+ elif current_para:
134
+ result.paragraphs.append(" ".join(current_para))
135
+ current_para = []
136
+
137
+ if current_para:
138
+ result.paragraphs.append(" ".join(current_para))
139
+
140
+ return result
141
+
142
+
143
+ # Language icons for code blocks
144
+ LANG_ICONS = {
145
+ "python": "🐍",
146
+ "javascript": "📜",
147
+ "typescript": "💠",
148
+ "rust": "🦀",
149
+ "go": "🐹",
150
+ "java": "☕",
151
+ "ruby": "💎",
152
+ "bash": "💻",
153
+ "shell": "💻",
154
+ "sh": "💻",
155
+ "sql": "🗄️",
156
+ "html": "🌐",
157
+ "css": "🎨",
158
+ "json": "📋",
159
+ "yaml": "📝",
160
+ "yml": "📝",
161
+ "markdown": "📄",
162
+ "md": "📄",
163
+ "c": "⚙️",
164
+ "cpp": "⚙️",
165
+ "csharp": "⚙️",
166
+ "text": "📄",
167
+ }
168
+
169
+
170
+ class CodeBlockWidget(Static):
171
+ """Widget for displaying a syntax-highlighted code block."""
172
+
173
+ DEFAULT_CSS = """
174
+ CodeBlockWidget {
175
+ height: auto;
176
+ margin: 1 0;
177
+ border: solid #27272a;
178
+ background: #0a0a0a;
179
+ }
180
+
181
+ CodeBlockWidget .code-header {
182
+ height: 1;
183
+ background: #1a1a1a;
184
+ padding: 0 1;
185
+ }
186
+
187
+ CodeBlockWidget .code-content {
188
+ height: auto;
189
+ padding: 0 1;
190
+ overflow-x: auto;
191
+ }
192
+ """
193
+
194
+ def __init__(self, block: CodeBlock, **kwargs):
195
+ super().__init__(**kwargs)
196
+ self.block = block
197
+
198
+ def render(self) -> RenderableType:
199
+ # Header with language badge
200
+ icon = LANG_ICONS.get(self.block.language.lower(), "📄")
201
+
202
+ header = Text()
203
+ header.append(f" {icon} ", style="#22c55e")
204
+ header.append(self.block.language.upper(), style="bold #22c55e")
205
+
206
+ if self.block.filename:
207
+ header.append(f" {self.block.filename}", style="#6b7280")
208
+
209
+ # Syntax highlighted code
210
+ syntax = Syntax(
211
+ self.block.code,
212
+ self.block.language,
213
+ theme="monokai",
214
+ line_numbers=True,
215
+ word_wrap=True,
216
+ background_color="#000000",
217
+ start_line=self.block.start_line,
218
+ )
219
+
220
+ return Panel(
221
+ syntax,
222
+ title=header,
223
+ title_align="left",
224
+ border_style="#27272a",
225
+ box=ROUNDED,
226
+ padding=(0, 0),
227
+ )
228
+
229
+
230
+ class ResponseDisplay(Container):
231
+ """
232
+ Beautiful agent response display.
233
+
234
+ Features:
235
+ - Streaming text with cursor
236
+ - Rich markdown rendering
237
+ - Syntax highlighted code blocks
238
+ - Structured lists and headers
239
+ - Agent avatar and name
240
+ """
241
+
242
+ DEFAULT_CSS = """
243
+ ResponseDisplay {
244
+ height: auto;
245
+ border: solid #a855f7;
246
+ background: #0d0a15;
247
+ padding: 1;
248
+ margin: 0 0 1 0;
249
+ }
250
+
251
+ ResponseDisplay.streaming {
252
+ border: solid #fbbf24;
253
+ }
254
+
255
+ ResponseDisplay .response-header {
256
+ height: 2;
257
+ margin-bottom: 1;
258
+ }
259
+
260
+ ResponseDisplay .response-content {
261
+ height: auto;
262
+ }
263
+
264
+ ResponseDisplay .response-footer {
265
+ height: 1;
266
+ margin-top: 1;
267
+ }
268
+ """
269
+
270
+ state: reactive[ResponseState] = reactive(ResponseState.STREAMING)
271
+
272
+ def __init__(
273
+ self,
274
+ agent_name: str = "Agent",
275
+ model_name: str = "",
276
+ **kwargs,
277
+ ):
278
+ super().__init__(**kwargs)
279
+ self.agent_name = agent_name
280
+ self.model_name = model_name
281
+ self._text = ""
282
+ self._parsed: Optional[ParsedResponse] = None
283
+ self._start_time = datetime.now()
284
+ self._end_time: Optional[datetime] = None
285
+ self._token_count = 0
286
+
287
+ def on_mount(self) -> None:
288
+ """Initialize."""
289
+ self._update_display()
290
+
291
+ def append_text(self, text: str) -> None:
292
+ """Append streaming text."""
293
+ self._text += text
294
+ self._parsed = None # Invalidate parsed cache
295
+ self.state = ResponseState.STREAMING
296
+ self.add_class("streaming")
297
+ self._update_display()
298
+
299
+ def set_text(self, text: str) -> None:
300
+ """Set complete text."""
301
+ self._text = text
302
+ self._parsed = None
303
+ self._update_display()
304
+
305
+ def complete(self, token_count: int = 0) -> None:
306
+ """Mark response as complete."""
307
+ self._end_time = datetime.now()
308
+ self._token_count = token_count
309
+ self.state = ResponseState.COMPLETE
310
+ self.remove_class("streaming")
311
+ self._update_display()
312
+
313
+ def set_error(self, error: str) -> None:
314
+ """Set error state."""
315
+ self._text = f"Error: {error}"
316
+ self.state = ResponseState.ERROR
317
+ self.remove_class("streaming")
318
+ self._update_display()
319
+
320
+ @property
321
+ def duration(self) -> float:
322
+ """Get duration in seconds."""
323
+ end = self._end_time or datetime.now()
324
+ return (end - self._start_time).total_seconds()
325
+
326
+ def _get_parsed(self) -> ParsedResponse:
327
+ """Get or create parsed response."""
328
+ if self._parsed is None:
329
+ self._parsed = ParsedResponse.parse(self._text)
330
+ return self._parsed
331
+
332
+ def _render_header(self) -> Text:
333
+ """Render response header."""
334
+ text = Text()
335
+
336
+ # Agent avatar and name
337
+ text.append("🤖 ", style="bold #a855f7")
338
+ text.append(self.agent_name, style="bold #e4e4e7")
339
+
340
+ # Model name
341
+ if self.model_name:
342
+ text.append(f" ({self.model_name})", style="#6b7280")
343
+
344
+ # Streaming indicator
345
+ if self.state == ResponseState.STREAMING:
346
+ text.append(" ● ", style="bold #fbbf24")
347
+ text.append("Generating...", style="italic #fbbf24")
348
+
349
+ return text
350
+
351
+ def _render_content(self) -> List[RenderableType]:
352
+ """Render response content."""
353
+ elements = []
354
+ parsed = self._get_parsed()
355
+
356
+ # Render headers and paragraphs in order
357
+ code_block_idx = 0
358
+
359
+ for header_level, header_text in parsed.headers:
360
+ # Header styling based on level
361
+ styles = {
362
+ 1: ("bold #e4e4e7", "═" * 40),
363
+ 2: ("bold #a1a1aa", "─" * 30),
364
+ 3: ("bold #71717a", ""),
365
+ }
366
+ style, underline = styles.get(header_level, ("", ""))
367
+
368
+ header = Text()
369
+ header.append("\n" + header_text + "\n", style=style)
370
+ if underline:
371
+ header.append(underline + "\n", style="#27272a")
372
+
373
+ elements.append(header)
374
+
375
+ # Paragraphs
376
+ for para in parsed.paragraphs:
377
+ # Word wrap
378
+ import textwrap
379
+
380
+ wrapped = textwrap.fill(para, width=80)
381
+ elements.append(Text(wrapped + "\n\n", style="#e4e4e7"))
382
+
383
+ # Bullet lists
384
+ for bullet_list in parsed.bullet_lists:
385
+ list_text = Text()
386
+ for i, item in enumerate(bullet_list):
387
+ colors = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b", "#22c55e"]
388
+ color = colors[i % len(colors)]
389
+ list_text.append(" ◆ ", style=f"bold {color}")
390
+ list_text.append(item + "\n", style="#e4e4e7")
391
+ list_text.append("\n")
392
+ elements.append(list_text)
393
+
394
+ # Code blocks
395
+ for block in parsed.code_blocks:
396
+ elements.append(self._render_code_block(block))
397
+
398
+ # If no structured content, render as plain text
399
+ if not elements and self._text:
400
+ elements.append(Text(self._text, style="#e4e4e7"))
401
+
402
+ # Streaming cursor
403
+ if self.state == ResponseState.STREAMING:
404
+ cursor = Text("▌", style="bold #fbbf24")
405
+ elements.append(cursor)
406
+
407
+ return elements
408
+
409
+ def _render_code_block(self, block: CodeBlock) -> Panel:
410
+ """Render a code block."""
411
+ icon = LANG_ICONS.get(block.language.lower(), "📄")
412
+
413
+ syntax = Syntax(
414
+ block.code,
415
+ block.language,
416
+ theme="monokai",
417
+ line_numbers=len(block.code.splitlines()) > 5,
418
+ background_color="#000000",
419
+ )
420
+
421
+ title = Text()
422
+ title.append(f" {icon} ", style="#22c55e")
423
+ title.append(block.language.upper(), style="bold #22c55e")
424
+
425
+ return Panel(
426
+ syntax,
427
+ title=title,
428
+ title_align="left",
429
+ border_style="#22c55e",
430
+ box=ROUNDED,
431
+ padding=(0, 1),
432
+ )
433
+
434
+ def _render_footer(self) -> Text:
435
+ """Render response footer."""
436
+ text = Text()
437
+
438
+ if self.state == ResponseState.COMPLETE:
439
+ text.append("✓ ", style="#22c55e")
440
+ text.append(f"{self.duration:.1f}s", style="#6b7280")
441
+
442
+ if self._token_count:
443
+ text.append(f" │ {self._token_count} tokens", style="#6b7280")
444
+
445
+ elif self.state == ResponseState.ERROR:
446
+ text.append("✗ Error", style="#ef4444")
447
+
448
+ return text
449
+
450
+ def _update_display(self) -> None:
451
+ """Update the display."""
452
+ try:
453
+ header = self.query_one(".response-header", Static)
454
+ content = self.query_one(".response-content", Container)
455
+ footer = self.query_one(".response-footer", Static)
456
+ except Exception:
457
+ return
458
+
459
+ header.update(self._render_header())
460
+
461
+ # Clear and re-render content
462
+ content.remove_children()
463
+ for element in self._render_content():
464
+ if isinstance(element, (Text, str)):
465
+ content.mount(Static(element))
466
+ else:
467
+ content.mount(Static(element))
468
+
469
+ footer.update(self._render_footer())
470
+
471
+ def clear(self) -> None:
472
+ """Clear the response."""
473
+ self._text = ""
474
+ self._parsed = None
475
+ self._start_time = datetime.now()
476
+ self._end_time = None
477
+ self._token_count = 0
478
+ self.state = ResponseState.STREAMING
479
+ self._update_display()
480
+
481
+ def compose(self):
482
+ """Compose the display."""
483
+ yield Static("", classes="response-header")
484
+ with Container(classes="response-content"):
485
+ pass
486
+ yield Static("", classes="response-footer")
487
+
488
+
489
+ class StreamingText(Static):
490
+ """Simple streaming text display with cursor."""
491
+
492
+ DEFAULT_CSS = """
493
+ StreamingText {
494
+ height: auto;
495
+ }
496
+ """
497
+
498
+ streaming: reactive[bool] = reactive(False)
499
+
500
+ def __init__(self, **kwargs):
501
+ super().__init__(**kwargs)
502
+ self._text = ""
503
+
504
+ def append(self, text: str) -> None:
505
+ """Append text."""
506
+ self._text += text
507
+ self.streaming = True
508
+ self.refresh()
509
+
510
+ def complete(self) -> None:
511
+ """Mark as complete."""
512
+ self.streaming = False
513
+ self.refresh()
514
+
515
+ def clear(self) -> None:
516
+ """Clear text."""
517
+ self._text = ""
518
+ self.streaming = False
519
+ self.refresh()
520
+
521
+ def render(self) -> Text:
522
+ result = Text()
523
+ result.append(self._text, style="#e4e4e7")
524
+
525
+ if self.streaming:
526
+ result.append("▌", style="bold #fbbf24")
527
+
528
+ return result