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,725 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SuperQode Provider Connect Widget - Interactive provider/model picker.
|
|
3
|
+
|
|
4
|
+
Interactive connection flow with fuzzy search.
|
|
5
|
+
|
|
6
|
+
Features:
|
|
7
|
+
- Fuzzy search for providers
|
|
8
|
+
- Model picker with pricing info
|
|
9
|
+
- Recent history
|
|
10
|
+
- Favorites support
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
:connect # Interactive picker
|
|
14
|
+
:connect anthropic # Pick provider, then model
|
|
15
|
+
:connect anthropic claude-sonnet-4 # Direct connect
|
|
16
|
+
:connect - # Switch to previous
|
|
17
|
+
:connect ! # Show history
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import os
|
|
23
|
+
from dataclasses import dataclass, field
|
|
24
|
+
from datetime import datetime
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Callable, Dict, List, Optional, Tuple, TYPE_CHECKING
|
|
27
|
+
|
|
28
|
+
from rich.text import Text
|
|
29
|
+
|
|
30
|
+
from textual.widgets import Static, Input, OptionList
|
|
31
|
+
from textual.containers import Container, Vertical, Horizontal
|
|
32
|
+
from textual.reactive import reactive
|
|
33
|
+
from textual.message import Message
|
|
34
|
+
from textual import on
|
|
35
|
+
from textual.binding import Binding
|
|
36
|
+
|
|
37
|
+
if TYPE_CHECKING:
|
|
38
|
+
from textual.app import App
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# ============================================================================
|
|
42
|
+
# DESIGN
|
|
43
|
+
# ============================================================================
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
from superqode.design_system import COLORS as SQ_COLORS
|
|
47
|
+
except ImportError:
|
|
48
|
+
|
|
49
|
+
class SQ_COLORS:
|
|
50
|
+
primary = "#7c3aed"
|
|
51
|
+
primary_light = "#a855f7"
|
|
52
|
+
success = "#10b981"
|
|
53
|
+
warning = "#f59e0b"
|
|
54
|
+
error = "#f43f5e"
|
|
55
|
+
info = "#06b6d4"
|
|
56
|
+
text_primary = "#fafafa"
|
|
57
|
+
text_secondary = "#e4e4e7"
|
|
58
|
+
text_muted = "#a1a1aa"
|
|
59
|
+
text_dim = "#71717a"
|
|
60
|
+
text_ghost = "#52525b"
|
|
61
|
+
bg_elevated = "#0a0a0a"
|
|
62
|
+
border_default = "#27272a"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# ============================================================================
|
|
66
|
+
# PROVIDER CONNECT WIDGET
|
|
67
|
+
# ============================================================================
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class ProviderConnectWidget(Container):
|
|
71
|
+
"""
|
|
72
|
+
Interactive provider connection widget with search.
|
|
73
|
+
|
|
74
|
+
Two-step flow:
|
|
75
|
+
1. Search and select provider
|
|
76
|
+
2. Search and select model
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
DEFAULT_CSS = """
|
|
80
|
+
ProviderConnectWidget {
|
|
81
|
+
height: auto;
|
|
82
|
+
max-height: 20;
|
|
83
|
+
background: #0a0a0a;
|
|
84
|
+
border: round #7c3aed;
|
|
85
|
+
padding: 1;
|
|
86
|
+
margin: 1 2;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
ProviderConnectWidget .header {
|
|
90
|
+
height: 1;
|
|
91
|
+
color: #a855f7;
|
|
92
|
+
text-style: bold;
|
|
93
|
+
margin-bottom: 1;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
ProviderConnectWidget Input {
|
|
97
|
+
width: 100%;
|
|
98
|
+
background: #050505;
|
|
99
|
+
border: solid #27272a;
|
|
100
|
+
margin-bottom: 1;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
ProviderConnectWidget Input:focus {
|
|
104
|
+
border: solid #7c3aed;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
ProviderConnectWidget OptionList {
|
|
108
|
+
height: auto;
|
|
109
|
+
max-height: 12;
|
|
110
|
+
background: #050505;
|
|
111
|
+
border: none;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
ProviderConnectWidget OptionList > .option-list--option {
|
|
115
|
+
padding: 0 1;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
ProviderConnectWidget OptionList > .option-list--option-highlighted {
|
|
119
|
+
background: #7c3aed40;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
ProviderConnectWidget .hint {
|
|
123
|
+
height: 1;
|
|
124
|
+
color: #52525b;
|
|
125
|
+
margin-top: 1;
|
|
126
|
+
}
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
class ProviderSelected(Message):
|
|
130
|
+
"""Posted when provider and model are selected."""
|
|
131
|
+
|
|
132
|
+
def __init__(self, provider_id: str, model: str, provider_name: str) -> None:
|
|
133
|
+
self.provider_id = provider_id
|
|
134
|
+
self.model = model
|
|
135
|
+
self.provider_name = provider_name
|
|
136
|
+
super().__init__()
|
|
137
|
+
|
|
138
|
+
class Cancelled(Message):
|
|
139
|
+
"""Posted when selection is cancelled."""
|
|
140
|
+
|
|
141
|
+
pass
|
|
142
|
+
|
|
143
|
+
# State
|
|
144
|
+
step: reactive[str] = reactive("provider") # "provider" or "model"
|
|
145
|
+
|
|
146
|
+
def __init__(self, **kwargs):
|
|
147
|
+
super().__init__(**kwargs)
|
|
148
|
+
self._providers: List[Tuple[str, str, bool]] = [] # (id, name, configured)
|
|
149
|
+
self._models: List[str] = []
|
|
150
|
+
self._model_info: Dict = {} # Model ID -> ModelInfo
|
|
151
|
+
self._selected_provider: Optional[str] = None
|
|
152
|
+
self._selected_provider_name: str = ""
|
|
153
|
+
self._search_query: str = ""
|
|
154
|
+
|
|
155
|
+
def compose(self):
|
|
156
|
+
"""Compose the widget."""
|
|
157
|
+
yield Static("◈ Connect to Provider", classes="header", id="connect-header")
|
|
158
|
+
yield Input(placeholder="Search providers...", id="connect-search")
|
|
159
|
+
yield OptionList(id="connect-options")
|
|
160
|
+
yield Static("Type number to select • Scroll with mouse • Esc Cancel", classes="hint")
|
|
161
|
+
|
|
162
|
+
def on_mount(self) -> None:
|
|
163
|
+
"""Load providers on mount."""
|
|
164
|
+
self._load_providers()
|
|
165
|
+
self._update_options()
|
|
166
|
+
self.query_one("#connect-search", Input).focus()
|
|
167
|
+
|
|
168
|
+
def _load_providers(self) -> None:
|
|
169
|
+
"""Load providers from registry."""
|
|
170
|
+
try:
|
|
171
|
+
from superqode.providers.registry import PROVIDERS, ProviderTier
|
|
172
|
+
|
|
173
|
+
self._providers = []
|
|
174
|
+
|
|
175
|
+
# Sort by tier then name
|
|
176
|
+
tier_order = {
|
|
177
|
+
ProviderTier.TIER1: 0,
|
|
178
|
+
ProviderTier.TIER2: 1,
|
|
179
|
+
ProviderTier.FREE: 2,
|
|
180
|
+
ProviderTier.LOCAL: 3,
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
sorted_providers = sorted(
|
|
184
|
+
PROVIDERS.items(), key=lambda x: (tier_order.get(x[1].tier, 99), x[1].name)
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
for provider_id, provider_def in sorted_providers:
|
|
188
|
+
# Check if configured
|
|
189
|
+
configured = False
|
|
190
|
+
if not provider_def.env_vars:
|
|
191
|
+
configured = True # Local provider
|
|
192
|
+
else:
|
|
193
|
+
for env_var in provider_def.env_vars:
|
|
194
|
+
if os.environ.get(env_var):
|
|
195
|
+
configured = True
|
|
196
|
+
break
|
|
197
|
+
|
|
198
|
+
self._providers.append((provider_id, provider_def.name, configured))
|
|
199
|
+
|
|
200
|
+
except Exception:
|
|
201
|
+
pass
|
|
202
|
+
|
|
203
|
+
def _update_options(self) -> None:
|
|
204
|
+
"""Update option list based on current step and search."""
|
|
205
|
+
try:
|
|
206
|
+
options = self.query_one("#connect-options", OptionList)
|
|
207
|
+
options.clear_options()
|
|
208
|
+
|
|
209
|
+
if self.step == "provider":
|
|
210
|
+
self._update_provider_options(options)
|
|
211
|
+
else:
|
|
212
|
+
self._update_model_options(options)
|
|
213
|
+
except Exception:
|
|
214
|
+
pass
|
|
215
|
+
|
|
216
|
+
def _update_provider_options(self, options: OptionList) -> None:
|
|
217
|
+
"""Update provider options with search filter."""
|
|
218
|
+
from superqode.utils.fuzzy import fuzzy_search
|
|
219
|
+
|
|
220
|
+
query = self._search_query.lower()
|
|
221
|
+
|
|
222
|
+
# Filter and score providers
|
|
223
|
+
if query:
|
|
224
|
+
matches = fuzzy_search.search(
|
|
225
|
+
query, [f"{p[0]} {p[1]}" for p in self._providers], max_results=15
|
|
226
|
+
)
|
|
227
|
+
matched_ids = {m.text.split()[0] for m in matches}
|
|
228
|
+
filtered = [p for p in self._providers if p[0] in matched_ids]
|
|
229
|
+
else:
|
|
230
|
+
filtered = self._providers[:15]
|
|
231
|
+
|
|
232
|
+
for provider_id, provider_name, configured in filtered:
|
|
233
|
+
status = "✓" if configured else "○"
|
|
234
|
+
status_style = SQ_COLORS.success if configured else SQ_COLORS.text_ghost
|
|
235
|
+
|
|
236
|
+
text = Text()
|
|
237
|
+
text.append(f"{status} ", style=status_style)
|
|
238
|
+
text.append(f"{provider_id}", style=SQ_COLORS.text_secondary)
|
|
239
|
+
text.append(f" {provider_name}", style=SQ_COLORS.text_dim)
|
|
240
|
+
|
|
241
|
+
options.add_option(text)
|
|
242
|
+
|
|
243
|
+
def _update_model_options(self, options: OptionList) -> None:
|
|
244
|
+
"""Update model options with search filter, pricing info, and local/HF badges."""
|
|
245
|
+
from superqode.utils.fuzzy import fuzzy_search
|
|
246
|
+
|
|
247
|
+
query = self._search_query.lower()
|
|
248
|
+
model_info = getattr(self, "_model_info", {})
|
|
249
|
+
local_models = getattr(self, "_local_models", {}) # model_id -> LocalModel
|
|
250
|
+
|
|
251
|
+
if query:
|
|
252
|
+
matches = fuzzy_search.search(query, self._models, max_results=15)
|
|
253
|
+
filtered = [m.text for m in matches]
|
|
254
|
+
else:
|
|
255
|
+
filtered = self._models[:15]
|
|
256
|
+
|
|
257
|
+
for model_id in filtered:
|
|
258
|
+
text = Text()
|
|
259
|
+
text.append(" ", style="")
|
|
260
|
+
|
|
261
|
+
# Check if this is a local model
|
|
262
|
+
local_model = local_models.get(model_id)
|
|
263
|
+
if local_model:
|
|
264
|
+
# Local model display with running status and tool support
|
|
265
|
+
if local_model.running:
|
|
266
|
+
text.append("● ", style=SQ_COLORS.success)
|
|
267
|
+
else:
|
|
268
|
+
text.append("○ ", style=SQ_COLORS.text_ghost)
|
|
269
|
+
|
|
270
|
+
text.append(f"{local_model.name}", style=SQ_COLORS.text_secondary)
|
|
271
|
+
text.append(f"\n {model_id}", style=SQ_COLORS.text_dim)
|
|
272
|
+
text.append(f"\n ", style="")
|
|
273
|
+
|
|
274
|
+
# Size and quantization
|
|
275
|
+
if local_model.size_display != "unknown":
|
|
276
|
+
text.append(f"{local_model.size_display}", style=SQ_COLORS.info)
|
|
277
|
+
if local_model.quantization != "unknown":
|
|
278
|
+
text.append(f" • {local_model.quantization}", style=SQ_COLORS.text_ghost)
|
|
279
|
+
|
|
280
|
+
# Local model badges
|
|
281
|
+
badges = []
|
|
282
|
+
if local_model.running:
|
|
283
|
+
badges.append("[running]")
|
|
284
|
+
if local_model.supports_tools:
|
|
285
|
+
badges.append("🔧")
|
|
286
|
+
if local_model.supports_vision:
|
|
287
|
+
badges.append("👁️")
|
|
288
|
+
if badges:
|
|
289
|
+
text.append(f" {' '.join(badges)}", style=SQ_COLORS.success)
|
|
290
|
+
else:
|
|
291
|
+
# Regular model (cloud API)
|
|
292
|
+
info = model_info.get(model_id)
|
|
293
|
+
if info:
|
|
294
|
+
text.append(f"{info.name}", style=SQ_COLORS.text_secondary)
|
|
295
|
+
text.append(f"\n {model_id}", style=SQ_COLORS.text_dim)
|
|
296
|
+
text.append(f"\n ", style="")
|
|
297
|
+
text.append(f"{info.price_display}", style=SQ_COLORS.success)
|
|
298
|
+
text.append(f" • {info.context_display} ctx", style=SQ_COLORS.text_ghost)
|
|
299
|
+
|
|
300
|
+
# Capability badges
|
|
301
|
+
badges = []
|
|
302
|
+
if info.supports_tools:
|
|
303
|
+
badges.append("🔧")
|
|
304
|
+
if info.supports_vision:
|
|
305
|
+
badges.append("👁️")
|
|
306
|
+
if info.supports_reasoning:
|
|
307
|
+
badges.append("🧠")
|
|
308
|
+
if badges:
|
|
309
|
+
text.append(f" {' '.join(badges)}", style="")
|
|
310
|
+
else:
|
|
311
|
+
text.append(model_id, style=SQ_COLORS.text_secondary)
|
|
312
|
+
|
|
313
|
+
options.add_option(text)
|
|
314
|
+
|
|
315
|
+
def watch_step(self, step: str) -> None:
|
|
316
|
+
"""Update UI when step changes."""
|
|
317
|
+
try:
|
|
318
|
+
header = self.query_one("#connect-header", Static)
|
|
319
|
+
search_input = self.query_one("#connect-search", Input)
|
|
320
|
+
|
|
321
|
+
if step == "provider":
|
|
322
|
+
header.update("◈ Connect to Provider")
|
|
323
|
+
search_input.placeholder = "Search providers..."
|
|
324
|
+
else:
|
|
325
|
+
header.update(f"◈ {self._selected_provider_name} - Select Model")
|
|
326
|
+
search_input.placeholder = "Search models..."
|
|
327
|
+
|
|
328
|
+
search_input.value = ""
|
|
329
|
+
self._search_query = ""
|
|
330
|
+
self._update_options()
|
|
331
|
+
search_input.focus()
|
|
332
|
+
except Exception:
|
|
333
|
+
pass
|
|
334
|
+
|
|
335
|
+
@on(Input.Changed, "#connect-search")
|
|
336
|
+
def _on_search_changed(self, event: Input.Changed) -> None:
|
|
337
|
+
"""Handle search input change."""
|
|
338
|
+
self._search_query = event.value
|
|
339
|
+
self._update_options()
|
|
340
|
+
|
|
341
|
+
@on(Input.Submitted, "#connect-search")
|
|
342
|
+
def _on_search_submitted(self, event: Input.Submitted) -> None:
|
|
343
|
+
"""Handle enter on search - select first option."""
|
|
344
|
+
try:
|
|
345
|
+
options = self.query_one("#connect-options", OptionList)
|
|
346
|
+
if options.option_count > 0:
|
|
347
|
+
options.highlighted = 0
|
|
348
|
+
self._select_current()
|
|
349
|
+
except Exception:
|
|
350
|
+
pass
|
|
351
|
+
|
|
352
|
+
@on(OptionList.OptionSelected)
|
|
353
|
+
def _on_option_selected(self, event: OptionList.OptionSelected) -> None:
|
|
354
|
+
"""Handle option selection."""
|
|
355
|
+
event.stop()
|
|
356
|
+
self._select_at_index(event.option_index)
|
|
357
|
+
|
|
358
|
+
def _select_current(self) -> None:
|
|
359
|
+
"""Select the currently highlighted option."""
|
|
360
|
+
try:
|
|
361
|
+
options = self.query_one("#connect-options", OptionList)
|
|
362
|
+
if options.highlighted is not None:
|
|
363
|
+
self._select_at_index(options.highlighted)
|
|
364
|
+
except Exception:
|
|
365
|
+
pass
|
|
366
|
+
|
|
367
|
+
def _select_at_index(self, index: int) -> None:
|
|
368
|
+
"""Select option at index."""
|
|
369
|
+
if self.step == "provider":
|
|
370
|
+
self._select_provider(index)
|
|
371
|
+
else:
|
|
372
|
+
self._select_model(index)
|
|
373
|
+
|
|
374
|
+
def _select_provider(self, index: int) -> None:
|
|
375
|
+
"""Handle provider selection."""
|
|
376
|
+
# Get filtered list
|
|
377
|
+
query = self._search_query.lower()
|
|
378
|
+
if query:
|
|
379
|
+
from superqode.utils.fuzzy import fuzzy_search
|
|
380
|
+
|
|
381
|
+
matches = fuzzy_search.search(
|
|
382
|
+
query, [f"{p[0]} {p[1]}" for p in self._providers], max_results=15
|
|
383
|
+
)
|
|
384
|
+
matched_ids = [m.text.split()[0] for m in matches]
|
|
385
|
+
filtered = [p for p in self._providers if p[0] in matched_ids]
|
|
386
|
+
else:
|
|
387
|
+
filtered = self._providers[:15]
|
|
388
|
+
|
|
389
|
+
if 0 <= index < len(filtered):
|
|
390
|
+
provider_id, provider_name, _ = filtered[index]
|
|
391
|
+
self._selected_provider = provider_id
|
|
392
|
+
self._selected_provider_name = provider_name
|
|
393
|
+
|
|
394
|
+
# Load models for this provider
|
|
395
|
+
self._load_models(provider_id)
|
|
396
|
+
|
|
397
|
+
# Switch to model selection
|
|
398
|
+
self.step = "model"
|
|
399
|
+
|
|
400
|
+
def _load_models(self, provider_id: str) -> None:
|
|
401
|
+
"""Load models for a provider, with special handling for local providers and HuggingFace."""
|
|
402
|
+
try:
|
|
403
|
+
from superqode.providers.models import get_models_for_provider
|
|
404
|
+
from superqode.providers.registry import PROVIDERS, ProviderCategory
|
|
405
|
+
|
|
406
|
+
provider_def = PROVIDERS.get(provider_id)
|
|
407
|
+
self._local_models = {}
|
|
408
|
+
|
|
409
|
+
# Check if this is a local provider
|
|
410
|
+
if provider_def and provider_def.category == ProviderCategory.LOCAL:
|
|
411
|
+
# Load models from local provider
|
|
412
|
+
import asyncio
|
|
413
|
+
|
|
414
|
+
asyncio.get_event_loop().run_until_complete(self._load_local_models(provider_id))
|
|
415
|
+
return
|
|
416
|
+
|
|
417
|
+
# Check if this is HuggingFace
|
|
418
|
+
if provider_id == "huggingface":
|
|
419
|
+
self._load_hf_models()
|
|
420
|
+
return
|
|
421
|
+
|
|
422
|
+
# Try getting models from the database (includes live models.dev data)
|
|
423
|
+
db_models = get_models_for_provider(provider_id)
|
|
424
|
+
|
|
425
|
+
if db_models:
|
|
426
|
+
# Store both model IDs and their info
|
|
427
|
+
self._model_info = db_models
|
|
428
|
+
self._models = list(db_models.keys())
|
|
429
|
+
else:
|
|
430
|
+
# Fall back to registry example models
|
|
431
|
+
if provider_def:
|
|
432
|
+
self._models = list(provider_def.example_models)
|
|
433
|
+
self._model_info = {}
|
|
434
|
+
else:
|
|
435
|
+
self._models = []
|
|
436
|
+
self._model_info = {}
|
|
437
|
+
except Exception:
|
|
438
|
+
self._models = []
|
|
439
|
+
self._model_info = {}
|
|
440
|
+
self._local_models = {}
|
|
441
|
+
|
|
442
|
+
async def _load_local_models(self, provider_id: str) -> None:
|
|
443
|
+
"""Load models from a local provider asynchronously."""
|
|
444
|
+
try:
|
|
445
|
+
from superqode.providers.local import (
|
|
446
|
+
OllamaClient,
|
|
447
|
+
LMStudioClient,
|
|
448
|
+
VLLMClient,
|
|
449
|
+
SGLangClient,
|
|
450
|
+
MLXClient,
|
|
451
|
+
TGIClient,
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
# Map provider ID to client class
|
|
455
|
+
client_map = {
|
|
456
|
+
"ollama": OllamaClient,
|
|
457
|
+
"lmstudio": LMStudioClient,
|
|
458
|
+
"vllm": VLLMClient,
|
|
459
|
+
"sglang": SGLangClient,
|
|
460
|
+
"mlx": MLXClient,
|
|
461
|
+
"tgi": TGIClient,
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
client_class = client_map.get(provider_id)
|
|
465
|
+
if not client_class:
|
|
466
|
+
self._models = []
|
|
467
|
+
self._model_info = {}
|
|
468
|
+
return
|
|
469
|
+
|
|
470
|
+
client = client_class()
|
|
471
|
+
if await client.is_available():
|
|
472
|
+
models = await client.list_models()
|
|
473
|
+
|
|
474
|
+
self._models = [m.id for m in models]
|
|
475
|
+
self._local_models = {m.id: m for m in models}
|
|
476
|
+
self._model_info = {}
|
|
477
|
+
else:
|
|
478
|
+
self._models = []
|
|
479
|
+
self._local_models = {}
|
|
480
|
+
self._model_info = {}
|
|
481
|
+
|
|
482
|
+
except Exception:
|
|
483
|
+
self._models = []
|
|
484
|
+
self._local_models = {}
|
|
485
|
+
self._model_info = {}
|
|
486
|
+
|
|
487
|
+
def _load_hf_models(self) -> None:
|
|
488
|
+
"""Load recommended HuggingFace models."""
|
|
489
|
+
try:
|
|
490
|
+
from superqode.providers.huggingface import RECOMMENDED_MODELS
|
|
491
|
+
|
|
492
|
+
# Combine all recommended models
|
|
493
|
+
all_models = []
|
|
494
|
+
for category_models in RECOMMENDED_MODELS.values():
|
|
495
|
+
all_models.extend(category_models)
|
|
496
|
+
|
|
497
|
+
# Remove duplicates while preserving order
|
|
498
|
+
seen = set()
|
|
499
|
+
unique = []
|
|
500
|
+
for m in all_models:
|
|
501
|
+
if m not in seen:
|
|
502
|
+
seen.add(m)
|
|
503
|
+
unique.append(m)
|
|
504
|
+
|
|
505
|
+
self._models = unique
|
|
506
|
+
self._model_info = {}
|
|
507
|
+
self._local_models = {}
|
|
508
|
+
|
|
509
|
+
except Exception:
|
|
510
|
+
self._models = []
|
|
511
|
+
self._model_info = {}
|
|
512
|
+
self._local_models = {}
|
|
513
|
+
|
|
514
|
+
def _select_model(self, index: int) -> None:
|
|
515
|
+
"""Handle model selection."""
|
|
516
|
+
query = self._search_query.lower()
|
|
517
|
+
if query:
|
|
518
|
+
from superqode.utils.fuzzy import fuzzy_search
|
|
519
|
+
|
|
520
|
+
matches = fuzzy_search.search(query, self._models, max_results=15)
|
|
521
|
+
filtered = [m.text for m in matches]
|
|
522
|
+
else:
|
|
523
|
+
filtered = self._models[:15]
|
|
524
|
+
|
|
525
|
+
if 0 <= index < len(filtered):
|
|
526
|
+
model = filtered[index]
|
|
527
|
+
self.post_message(
|
|
528
|
+
self.ProviderSelected(self._selected_provider, model, self._selected_provider_name)
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
def on_key(self, event) -> None:
|
|
532
|
+
"""Handle key events."""
|
|
533
|
+
if event.key == "escape":
|
|
534
|
+
if self.step == "model":
|
|
535
|
+
# Go back to provider selection
|
|
536
|
+
self.step = "provider"
|
|
537
|
+
else:
|
|
538
|
+
self.post_message(self.Cancelled())
|
|
539
|
+
event.stop()
|
|
540
|
+
elif event.key == "up":
|
|
541
|
+
try:
|
|
542
|
+
self.query_one("#connect-options", OptionList).action_cursor_up()
|
|
543
|
+
except Exception:
|
|
544
|
+
pass
|
|
545
|
+
event.stop()
|
|
546
|
+
elif event.key == "down":
|
|
547
|
+
try:
|
|
548
|
+
self.query_one("#connect-options", OptionList).action_cursor_down()
|
|
549
|
+
except Exception:
|
|
550
|
+
pass
|
|
551
|
+
event.stop()
|
|
552
|
+
elif event.key == "enter":
|
|
553
|
+
self._select_current()
|
|
554
|
+
event.stop()
|
|
555
|
+
|
|
556
|
+
def set_provider(self, provider_id: str) -> None:
|
|
557
|
+
"""Pre-select a provider and go to model selection."""
|
|
558
|
+
try:
|
|
559
|
+
from superqode.providers.registry import PROVIDERS
|
|
560
|
+
|
|
561
|
+
provider_def = PROVIDERS.get(provider_id)
|
|
562
|
+
if provider_def:
|
|
563
|
+
self._selected_provider = provider_id
|
|
564
|
+
self._selected_provider_name = provider_def.name
|
|
565
|
+
self._load_models(provider_id)
|
|
566
|
+
self.step = "model"
|
|
567
|
+
except Exception:
|
|
568
|
+
pass
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
# ============================================================================
|
|
572
|
+
# INLINE PROVIDER PICKER (for log display)
|
|
573
|
+
# ============================================================================
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def render_provider_list(
|
|
577
|
+
providers: List[Tuple[str, str, bool]],
|
|
578
|
+
selected_index: int = -1,
|
|
579
|
+
max_items: int = 10,
|
|
580
|
+
) -> Text:
|
|
581
|
+
"""
|
|
582
|
+
Render a provider list for inline display.
|
|
583
|
+
|
|
584
|
+
Args:
|
|
585
|
+
providers: List of (id, name, configured) tuples
|
|
586
|
+
selected_index: Currently selected index (-1 for none)
|
|
587
|
+
max_items: Maximum items to show
|
|
588
|
+
|
|
589
|
+
Returns:
|
|
590
|
+
Rich Text object
|
|
591
|
+
"""
|
|
592
|
+
text = Text()
|
|
593
|
+
text.append("◈ Select Provider\n\n", style=f"bold {SQ_COLORS.primary}")
|
|
594
|
+
|
|
595
|
+
for i, (provider_id, provider_name, configured) in enumerate(providers[:max_items]):
|
|
596
|
+
is_selected = i == selected_index
|
|
597
|
+
|
|
598
|
+
# Selection marker
|
|
599
|
+
if is_selected:
|
|
600
|
+
text.append("▸ ", style=f"bold {SQ_COLORS.primary}")
|
|
601
|
+
else:
|
|
602
|
+
text.append(" ", style="")
|
|
603
|
+
|
|
604
|
+
# Status indicator
|
|
605
|
+
status = "✓" if configured else "○"
|
|
606
|
+
status_style = SQ_COLORS.success if configured else SQ_COLORS.text_ghost
|
|
607
|
+
text.append(f"{status} ", style=status_style)
|
|
608
|
+
|
|
609
|
+
# Provider info
|
|
610
|
+
text.append(f"[{i + 1}] ", style=SQ_COLORS.text_dim)
|
|
611
|
+
text.append(
|
|
612
|
+
f"{provider_id}",
|
|
613
|
+
style=SQ_COLORS.text_secondary if is_selected else SQ_COLORS.text_muted,
|
|
614
|
+
)
|
|
615
|
+
text.append(f" {provider_name}\n", style=SQ_COLORS.text_dim)
|
|
616
|
+
|
|
617
|
+
if len(providers) > max_items:
|
|
618
|
+
text.append(f"\n ... and {len(providers) - max_items} more\n", style=SQ_COLORS.text_ghost)
|
|
619
|
+
|
|
620
|
+
text.append("\n Type number to select • Scroll to see more: ", style=SQ_COLORS.text_dim)
|
|
621
|
+
|
|
622
|
+
return text
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
def render_model_list(
|
|
626
|
+
provider_name: str,
|
|
627
|
+
models: List[str],
|
|
628
|
+
selected_index: int = -1,
|
|
629
|
+
max_items: int = 10,
|
|
630
|
+
model_info: Optional[Dict] = None,
|
|
631
|
+
local_models: Optional[Dict] = None,
|
|
632
|
+
) -> Text:
|
|
633
|
+
"""
|
|
634
|
+
Render a model list for inline display.
|
|
635
|
+
|
|
636
|
+
Args:
|
|
637
|
+
provider_name: Name of the provider
|
|
638
|
+
models: List of model IDs
|
|
639
|
+
selected_index: Currently selected index (-1 for none)
|
|
640
|
+
max_items: Maximum items to show
|
|
641
|
+
model_info: Optional dict of model_id -> ModelInfo for pricing/features
|
|
642
|
+
local_models: Optional dict of model_id -> LocalModel for local providers
|
|
643
|
+
"""
|
|
644
|
+
text = Text()
|
|
645
|
+
text.append(f"◈ {provider_name} Models\n\n", style=f"bold {SQ_COLORS.primary}")
|
|
646
|
+
|
|
647
|
+
for i, model_id in enumerate(models[:max_items]):
|
|
648
|
+
is_selected = i == selected_index
|
|
649
|
+
|
|
650
|
+
if is_selected:
|
|
651
|
+
text.append("▸ ", style=f"bold {SQ_COLORS.primary}")
|
|
652
|
+
else:
|
|
653
|
+
text.append(" ", style="")
|
|
654
|
+
|
|
655
|
+
text.append(f"[{i + 1}] ", style=SQ_COLORS.text_dim)
|
|
656
|
+
|
|
657
|
+
# Check for local model first
|
|
658
|
+
local_model = local_models.get(model_id) if local_models else None
|
|
659
|
+
if local_model:
|
|
660
|
+
# Running status indicator
|
|
661
|
+
if local_model.running:
|
|
662
|
+
text.append("● ", style=SQ_COLORS.success)
|
|
663
|
+
else:
|
|
664
|
+
text.append("○ ", style=SQ_COLORS.text_ghost)
|
|
665
|
+
|
|
666
|
+
text.append(
|
|
667
|
+
f"{local_model.name}\n",
|
|
668
|
+
style=SQ_COLORS.text_secondary if is_selected else SQ_COLORS.text_muted,
|
|
669
|
+
)
|
|
670
|
+
text.append(f" {model_id}\n", style=SQ_COLORS.text_ghost)
|
|
671
|
+
|
|
672
|
+
# Local model details
|
|
673
|
+
details = []
|
|
674
|
+
if local_model.size_display != "unknown":
|
|
675
|
+
details.append(local_model.size_display)
|
|
676
|
+
if local_model.quantization != "unknown":
|
|
677
|
+
details.append(local_model.quantization)
|
|
678
|
+
if local_model.supports_tools:
|
|
679
|
+
details.append("🔧 tools")
|
|
680
|
+
if local_model.running:
|
|
681
|
+
details.append("[running]")
|
|
682
|
+
|
|
683
|
+
if details:
|
|
684
|
+
text.append(f" {' • '.join(details)}\n", style=SQ_COLORS.text_ghost)
|
|
685
|
+
else:
|
|
686
|
+
# Cloud model with pricing info
|
|
687
|
+
info = model_info.get(model_id) if model_info else None
|
|
688
|
+
if info:
|
|
689
|
+
text.append(
|
|
690
|
+
f"{info.name}\n",
|
|
691
|
+
style=SQ_COLORS.text_secondary if is_selected else SQ_COLORS.text_muted,
|
|
692
|
+
)
|
|
693
|
+
text.append(f" {model_id}\n", style=SQ_COLORS.text_ghost)
|
|
694
|
+
text.append(f" {info.price_display}", style=SQ_COLORS.success)
|
|
695
|
+
text.append(f" • {info.context_display} ctx", style=SQ_COLORS.text_ghost)
|
|
696
|
+
|
|
697
|
+
# Capability badges
|
|
698
|
+
badges = []
|
|
699
|
+
if info.supports_tools:
|
|
700
|
+
badges.append("🔧")
|
|
701
|
+
if info.supports_vision:
|
|
702
|
+
badges.append("👁️")
|
|
703
|
+
if badges:
|
|
704
|
+
text.append(f" {' '.join(badges)}", style="")
|
|
705
|
+
text.append("\n", style="")
|
|
706
|
+
else:
|
|
707
|
+
text.append(
|
|
708
|
+
f"{model_id}\n",
|
|
709
|
+
style=SQ_COLORS.text_secondary if is_selected else SQ_COLORS.text_muted,
|
|
710
|
+
)
|
|
711
|
+
|
|
712
|
+
text.append("\n Type number to select • Scroll to see more: ", style=SQ_COLORS.text_dim)
|
|
713
|
+
|
|
714
|
+
return text
|
|
715
|
+
|
|
716
|
+
|
|
717
|
+
# ============================================================================
|
|
718
|
+
# EXPORTS
|
|
719
|
+
# ============================================================================
|
|
720
|
+
|
|
721
|
+
__all__ = [
|
|
722
|
+
"ProviderConnectWidget",
|
|
723
|
+
"render_provider_list",
|
|
724
|
+
"render_model_list",
|
|
725
|
+
]
|