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>
This commit is contained in:
133
app/ui/search_dialog.py
Normal file
133
app/ui/search_dialog.py
Normal file
@@ -0,0 +1,133 @@
|
||||
"""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())
|
||||
Reference in New Issue
Block a user