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,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
|