Files
APIClient-Agent/app/ui/search_dialog.py
Anand Shukla 01662f7e0e Initial release — APIClient - Agent v2.0.0
AI-first API testing desktop client built with Python + PyQt6.

Features:
- Multi-tab HTTP request editor with params/headers/body/auth/tests
- KeyValueTable with per-row enable/disable checkboxes and 36px rows
- Format JSON button, syntax highlighting, pre-request & test scripts
- Collections, environments, history, import/export (Postman v2.1, cURL)
- OpenAPI 3.x / Swagger 2.0 local parser (no AI tokens)
- EKIKA Odoo API Framework generator — JSON-API, REST JSON, GraphQL,
  Custom REST JSON with all auth types (instant, no AI tokens)
- Persistent AI chat sidebar (Claude-powered co-pilot) with streaming,
  context-aware suggestions, and one-click Apply to request editor
- AI collection generator from any docs URL or pasted spec
- WebSocket client, Mock server, Collection runner, Code generator
- Dark/light theme engine (global QSS, object-name selectors)
- SSL error detection with actionable hints
- MIT License

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 17:38:57 +05:30

134 lines
5.6 KiB
Python

"""APIClient - Agent — Request Search Dialog."""
from PyQt6.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QLineEdit,
QPushButton, QListWidget, QListWidgetItem, QLabel, QWidget
)
from PyQt6.QtCore import Qt, pyqtSignal
from PyQt6.QtGui import QBrush, QColor
from app.ui.theme import Colors, method_color
from app.core import storage
from app.models import HttpRequest
class SearchDialog(QDialog):
request_selected = pyqtSignal(object)
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Search Requests")
self.setMinimumSize(580, 460)
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
# ── Header ────────────────────────────────────────────────────────────
header = QWidget()
header.setObjectName("panelHeader")
header.setFixedHeight(48)
hl = QHBoxLayout(header)
hl.setContentsMargins(16, 0, 16, 0)
title = QLabel("Search Requests")
title.setObjectName("panelTitle")
hl.addWidget(title)
layout.addWidget(header)
# ── Search bar ────────────────────────────────────────────────────────
search_bar = QWidget()
search_bar.setObjectName("urlBarStrip")
sl = QHBoxLayout(search_bar)
sl.setContentsMargins(16, 10, 16, 10)
self.search_input = QLineEdit()
self.search_input.setObjectName("urlBar")
self.search_input.setPlaceholderText("Search by name or URL…")
self.search_input.textChanged.connect(self._search)
sl.addWidget(self.search_input)
layout.addWidget(search_bar)
# ── Results ───────────────────────────────────────────────────────────
body = QWidget()
body.setObjectName("panelBody")
bl = QVBoxLayout(body)
bl.setContentsMargins(16, 8, 16, 8)
bl.setSpacing(6)
self.count_label = QLabel("")
self.count_label.setObjectName("hintText")
bl.addWidget(self.count_label)
self.results_list = QListWidget()
self.results_list.itemDoubleClicked.connect(self._on_selected)
bl.addWidget(self.results_list)
layout.addWidget(body, 1)
# ── Footer ────────────────────────────────────────────────────────────
footer = QWidget()
footer.setObjectName("panelFooter")
footer.setFixedHeight(52)
fl = QHBoxLayout(footer)
fl.setContentsMargins(16, 0, 16, 0)
open_btn = QPushButton("Open in Tab")
open_btn.setObjectName("accent")
open_btn.setFixedWidth(120)
open_btn.clicked.connect(self._on_selected_btn)
close_btn = QPushButton("Close")
close_btn.setFixedWidth(80)
close_btn.clicked.connect(self.reject)
fl.addWidget(open_btn)
fl.addStretch()
fl.addWidget(close_btn)
layout.addWidget(footer)
self.search_input.setFocus()
self._search("")
# ── Logic ─────────────────────────────────────────────────────────────────
def _search(self, query: str):
self.results_list.clear()
results = storage.search_requests(query) if query.strip() else []
count = len(results)
self.count_label.setText(
f"{count} result{'s' if count != 1 else ''}"
if query.strip() else "Type to search…"
)
for r in results:
method = r.get("method", "GET")
name = r.get("name") or r.get("url", "")
col_name = r.get("collection_name", "")
label = f"[{col_name}] {method} {name}" if col_name else f"{method} {name}"
item = QListWidgetItem(label)
item.setForeground(QBrush(QColor(method_color(method))))
item.setData(Qt.ItemDataRole.UserRole, r)
self.results_list.addItem(item)
def _build_request(self, r: dict) -> HttpRequest:
return HttpRequest(
method = r.get("method", "GET"),
url = r.get("url", ""),
headers = r.get("headers") or {},
params = r.get("params") or {},
body = r.get("body") or "",
body_type = r.get("body_type") or "raw",
content_type = r.get("content_type") or "",
auth_type = r.get("auth_type") or "none",
auth_data = r.get("auth_data") or {},
name = r.get("name") or "",
id = r.get("id"),
timeout = r.get("timeout") or 30,
ssl_verify = bool(r.get("ssl_verify", 1)),
)
def _on_selected(self, item: QListWidgetItem = None):
if item is None:
item = self.results_list.currentItem()
if not item:
return
r = item.data(Qt.ItemDataRole.UserRole)
self.request_selected.emit(self._build_request(r))
self.accept()
def _on_selected_btn(self):
self._on_selected(self.results_list.currentItem())