search-api-webui 0.1.7__tar.gz → 0.1.9__tar.gz

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 (64) hide show
  1. {search_api_webui-0.1.7 → search_api_webui-0.1.9}/.gitignore +1 -1
  2. {search_api_webui-0.1.7 → search_api_webui-0.1.9}/PKG-INFO +75 -13
  3. search_api_webui-0.1.9/README.md +158 -0
  4. {search_api_webui-0.1.7 → search_api_webui-0.1.9}/pyproject.toml +8 -4
  5. {search_api_webui-0.1.7 → search_api_webui-0.1.9}/search_api_webui/__init__.py +0 -1
  6. search_api_webui-0.1.9/search_api_webui/app.py +311 -0
  7. {search_api_webui-0.1.7 → search_api_webui-0.1.9}/search_api_webui/providers/__init__.py +13 -7
  8. search_api_webui-0.1.9/search_api_webui/providers/base.py +138 -0
  9. {search_api_webui-0.1.7 → search_api_webui-0.1.9}/search_api_webui/providers/generic.py +128 -48
  10. {search_api_webui-0.1.7 → search_api_webui-0.1.9}/search_api_webui/providers/querit.py +46 -34
  11. search_api_webui-0.1.9/search_api_webui/providers.yaml +77 -0
  12. search_api_webui-0.1.9/search_api_webui/ruff.toml +27 -0
  13. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/altgraph-0.17.5.dist-info/LICENSE +18 -0
  14. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/bottle-0.13.4.dist-info/licenses/LICENSE +19 -0
  15. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/certifi-2026.1.4.dist-info/licenses/LICENSE +20 -0
  16. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/charset_normalizer-3.4.4.dist-info/licenses/LICENSE +21 -0
  17. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/flask/sansio/README.md +6 -0
  18. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/jmespath-1.1.0.dist-info/LICENSE +21 -0
  19. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/macholib-1.16.4.dist-info/LICENSE +21 -0
  20. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/packaging-26.0.dist-info/licenses/LICENSE +3 -0
  21. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/pip/_vendor/certifi/LICENSE +20 -0
  22. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/pip/_vendor/distro/LICENSE +202 -0
  23. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/pip/_vendor/packaging/LICENSE +3 -0
  24. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/pip/_vendor/pkg_resources/LICENSE +17 -0
  25. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/pip/_vendor/platformdirs/LICENSE +21 -0
  26. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/pip/_vendor/pygments/LICENSE +25 -0
  27. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/pip/_vendor/pyproject_hooks/LICENSE +21 -0
  28. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/pip/_vendor/requests/LICENSE +175 -0
  29. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/pip/_vendor/resolvelib/LICENSE +13 -0
  30. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/pip/_vendor/rich/LICENSE +19 -0
  31. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/pip/_vendor/tomli/LICENSE +21 -0
  32. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/pip/_vendor/tomli_w/LICENSE +21 -0
  33. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/pip/_vendor/truststore/LICENSE +21 -0
  34. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/pip-25.3.dist-info/licenses/src/pip/_vendor/certifi/LICENSE +20 -0
  35. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/pip-25.3.dist-info/licenses/src/pip/_vendor/distro/LICENSE +202 -0
  36. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/pip-25.3.dist-info/licenses/src/pip/_vendor/packaging/LICENSE +3 -0
  37. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/pip-25.3.dist-info/licenses/src/pip/_vendor/pkg_resources/LICENSE +17 -0
  38. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/pip-25.3.dist-info/licenses/src/pip/_vendor/platformdirs/LICENSE +21 -0
  39. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/pip-25.3.dist-info/licenses/src/pip/_vendor/pygments/LICENSE +25 -0
  40. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/pip-25.3.dist-info/licenses/src/pip/_vendor/pyproject_hooks/LICENSE +21 -0
  41. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/pip-25.3.dist-info/licenses/src/pip/_vendor/requests/LICENSE +175 -0
  42. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/pip-25.3.dist-info/licenses/src/pip/_vendor/resolvelib/LICENSE +13 -0
  43. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/pip-25.3.dist-info/licenses/src/pip/_vendor/rich/LICENSE +19 -0
  44. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/pip-25.3.dist-info/licenses/src/pip/_vendor/tomli/LICENSE +21 -0
  45. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/pip-25.3.dist-info/licenses/src/pip/_vendor/tomli_w/LICENSE +21 -0
  46. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/pip-25.3.dist-info/licenses/src/pip/_vendor/truststore/LICENSE +21 -0
  47. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/pyinstaller_hooks_contrib-2026.0.dist-info/licenses/LICENSE +521 -0
  48. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/pywebview-6.1.dist-info/licenses/LICENSE +29 -0
  49. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/pyyaml-6.0.3.dist-info/licenses/LICENSE +20 -0
  50. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/querit-0.1.2.dist-info/licenses/LICENSE +201 -0
  51. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/requests-2.32.5.dist-info/licenses/LICENSE +175 -0
  52. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/search_api_webui/static/AppIcon.icns +0 -0
  53. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/search_api_webui/static/assets/index-B3BapgR8.css +1 -0
  54. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/search_api_webui/static/assets/index-DqsQFaWm.js +196 -0
  55. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/search_api_webui/static/favicon.ico +0 -0
  56. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/search_api_webui/static/index.html +14 -0
  57. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/search_api_webui-0.1.7.dist-info/licenses/LICENSE +7 -0
  58. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/setuptools-65.5.0.dist-info/LICENSE +19 -0
  59. search_api_webui-0.1.9/venv-macos-build/lib/python3.11/site-packages/typing_extensions-4.15.0.dist-info/licenses/LICENSE +279 -0
  60. search_api_webui-0.1.7/README.md +0 -100
  61. search_api_webui-0.1.7/search_api_webui/app.py +0 -194
  62. search_api_webui-0.1.7/search_api_webui/providers/base.py +0 -45
  63. search_api_webui-0.1.7/search_api_webui/providers.yaml +0 -19
  64. {search_api_webui-0.1.7 → search_api_webui-0.1.9}/LICENSE +0 -0
@@ -8,7 +8,6 @@ venv/
8
8
  # Frontend / Node
9
9
  frontend/node_modules/
10
10
  frontend/dist/
11
- frontend/package-lock.json
12
11
  frontend/.DS_Store
13
12
  .DS_Store
14
13
 
@@ -24,6 +23,7 @@ user_config.json
24
23
  .co*/
25
24
  .tr*/
26
25
  .hi*/
26
+ .ba*/
27
27
 
28
28
  # Build artifacts
29
29
  build/
@@ -1,49 +1,71 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: search-api-webui
3
- Version: 0.1.7
4
- Summary: A Search API WebUI for Querit, You, and other search providers.
3
+ Version: 0.1.9
4
+ Summary: A Search API WebUI for testing and comparing Querit, You, and other search providers.
5
5
  Project-URL: Homepage, https://github.com/querit-ai/search-api-webui
6
6
  Project-URL: Repository, https://github.com/querit-ai/search-api-webui.git
7
7
  Project-URL: Issues, https://github.com/querit-ai/search-api-webui/issues
8
8
  Author: querit.ai
9
9
  License: MIT
10
10
  License-File: LICENSE
11
- Keywords: api,llm,querit,search,tool,webui
11
+ Keywords: api,arena,benchmark,llm,querit,search,tool,webui
12
12
  Classifier: Development Status :: 3 - Alpha
13
13
  Classifier: Framework :: Flask
14
14
  Classifier: License :: OSI Approved :: MIT License
15
15
  Classifier: Programming Language :: Python :: 3
16
- Requires-Python: >=3.4
16
+ Requires-Python: >=3.8
17
17
  Requires-Dist: flask-cors>=3.0.0
18
18
  Requires-Dist: flask>=1.1.0
19
19
  Requires-Dist: jmespath>=0.10.0
20
20
  Requires-Dist: pyyaml>=5.3
21
21
  Requires-Dist: querit
22
22
  Requires-Dist: requests>=2.22.0
23
+ Provides-Extra: build
24
+ Requires-Dist: pyinstaller>=6.0; extra == 'build'
25
+ Provides-Extra: webview
26
+ Requires-Dist: pywebview>=5.0; extra == 'webview'
23
27
  Description-Content-Type: text/markdown
24
28
 
25
29
  # Search API WebUI
26
30
 
27
- A lightweight, local WebUI for testing and visualizing Search APIs (Querit, You, etc.).
31
+ A lightweight, cross-platform WebUI and native Mac App for testing, comparing, and visualizing Search APIs (Querit, You, etc.).
28
32
 
29
- (images)
33
+ ![Screenshot](docs/images/screenshot.webp)
30
34
 
31
35
  ## Features
32
36
 
33
37
  * **Search**: Support for [Querit.ai](https://www.querit.ai/en/docs/reference/post), [You.com](https://docs.you.com/api-reference/search/v1-search), and generic Search APIs via configuration.
38
+ * **API Arena**: Compare two search providers side-by-side to benchmark latency, payload size, and result relevance.
34
39
  * **Performance Metrics**: Real-time display of request latency and payload size.
35
40
  * **Visual Rendering**: Renders standard search results (Title, Snippet, URL) in a clean card layout.
36
41
  * **Configurable**: Easy-to-edit providers.yaml to add or modify search providers.
37
42
  * **Secure**: API Keys are stored locally in your $HOME folder.
38
43
  ## Installation
39
44
 
40
- Use this method if you just want to run the tool without modifying the code.
45
+ ### macOS Installation
46
+
47
+ For macOS users, you can download the DMG installer from the GitHub Releases page:
48
+
49
+ 1. Visit the [Releases page](https://github.com/querit-ai/search-api-webui/releases)
50
+ 2. Download the appropriate DMG file for your Mac architecture:
51
+ - **Apple Silicon (M1/M2/M3)**: `SearchAPIWebUI-<version>-macOS-arm64.dmg`
52
+ - **Intel Macs**: `SearchAPIWebUI-<version>-macOS-x86_64.dmg`
53
+ 3. Open the DMG file and drag `SearchAPIWebUI` to your Applications folder
54
+ 4. Launch `SearchAPIWebUI` from Applications
55
+
56
+ **Note**: Since the application is not code-signed, macOS may block it on first launch. To allow it to run:
57
+ - Go to **System Settings** > **Privacy & Security**
58
+ - Look for the message about `SearchAPIWebUI` being blocked
59
+ - Click **Open Anyway** to allow the application to run
41
60
 
42
61
  ### Prerequisites
43
62
 
44
63
  Python 3.7+
64
+
45
65
  ### Install via Pip
46
66
 
67
+ Use this method if you just want to run the tool without modifying the code.
68
+
47
69
  ```
48
70
  pip install search-api-webui
49
71
  ```
@@ -63,36 +85,76 @@ Use this method if you want to contribute to the code or build from source.
63
85
  * Python 3.7+
64
86
  * Node.js & npm (for building the frontend)
65
87
 
66
- ### Setup Steps
88
+ ### Quick Start with Makefile
67
89
 
68
90
  **Clone the repository**
69
91
 
70
- ```
92
+ ```bash
71
93
  git clone https://github.com/querit-ai/search-api-webui.git
72
94
  cd search-api-webui
73
95
  ```
74
96
 
75
- **Build Frontend**
97
+ **Development Mode** (with hot reload)
98
+
99
+ ```bash
100
+ make dev
101
+ ```
102
+
103
+ This will:
104
+ - Start Flask backend on http://localhost:8889
105
+ - Start Vite frontend dev server on http://localhost:5173
106
+ - Automatically open your browser
107
+ - Enable hot module replacement for instant updates
108
+
109
+ **Build Python Wheel**
76
110
 
111
+ ```bash
112
+ make # or 'make all'
77
113
  ```
114
+
115
+ **Build macOS DMG** (macOS only)
116
+
117
+ ```bash
118
+ make dmg # Builds for your current architecture
119
+ ```
120
+
121
+ ### Manual Setup
122
+
123
+ If you prefer not to use Makefile:
124
+
125
+ **Build Frontend**
126
+
127
+ ```bash
78
128
  cd frontend
79
129
  npm install
80
130
  npm run build
81
- cd
131
+ cd ..
82
132
  ```
83
133
 
84
134
  **Install search-api-webui (Editable Mode)**
85
135
 
86
- ```
136
+ ```bash
87
137
  pip install -e .
88
138
  ```
89
139
 
90
140
  **Run the Server**
91
141
 
92
- ```
142
+ ```bash
93
143
  python -m search_api_webui.app
94
144
  ```
95
145
 
146
+ ### Available Make Commands
147
+
148
+ ```bash
149
+ make # Build Python wheel package (default)
150
+ make dev # Start development servers with hot reload
151
+ make dmg # Build macOS DMG for current architecture
152
+ make backend # Start backend server only
153
+ make frontend # Start frontend dev server only
154
+ make clean # Clean build artifacts
155
+ make help # Show all available commands
156
+ ```
157
+
96
158
  ## Configuration
97
159
 
98
160
  ### Add API Keys
@@ -0,0 +1,158 @@
1
+ # Search API WebUI
2
+
3
+ A lightweight, cross-platform WebUI and native Mac App for testing, comparing, and visualizing Search APIs (Querit, You, etc.).
4
+
5
+ ![Screenshot](docs/images/screenshot.webp)
6
+
7
+ ## Features
8
+
9
+ * **Search**: Support for [Querit.ai](https://www.querit.ai/en/docs/reference/post), [You.com](https://docs.you.com/api-reference/search/v1-search), and generic Search APIs via configuration.
10
+ * **API Arena**: Compare two search providers side-by-side to benchmark latency, payload size, and result relevance.
11
+ * **Performance Metrics**: Real-time display of request latency and payload size.
12
+ * **Visual Rendering**: Renders standard search results (Title, Snippet, URL) in a clean card layout.
13
+ * **Configurable**: Easy-to-edit providers.yaml to add or modify search providers.
14
+ * **Secure**: API Keys are stored locally in your $HOME folder.
15
+ ## Installation
16
+
17
+ ### macOS Installation
18
+
19
+ For macOS users, you can download the DMG installer from the GitHub Releases page:
20
+
21
+ 1. Visit the [Releases page](https://github.com/querit-ai/search-api-webui/releases)
22
+ 2. Download the appropriate DMG file for your Mac architecture:
23
+ - **Apple Silicon (M1/M2/M3)**: `SearchAPIWebUI-<version>-macOS-arm64.dmg`
24
+ - **Intel Macs**: `SearchAPIWebUI-<version>-macOS-x86_64.dmg`
25
+ 3. Open the DMG file and drag `SearchAPIWebUI` to your Applications folder
26
+ 4. Launch `SearchAPIWebUI` from Applications
27
+
28
+ **Note**: Since the application is not code-signed, macOS may block it on first launch. To allow it to run:
29
+ - Go to **System Settings** > **Privacy & Security**
30
+ - Look for the message about `SearchAPIWebUI` being blocked
31
+ - Click **Open Anyway** to allow the application to run
32
+
33
+ ### Prerequisites
34
+
35
+ Python 3.7+
36
+
37
+ ### Install via Pip
38
+
39
+ Use this method if you just want to run the tool without modifying the code.
40
+
41
+ ```
42
+ pip install search-api-webui
43
+ ```
44
+
45
+ ### Run the Server
46
+
47
+ ```
48
+ search-api-webui
49
+ ```
50
+
51
+ ## Development
52
+
53
+ Use this method if you want to contribute to the code or build from source.
54
+
55
+ ### Prerequisites
56
+
57
+ * Python 3.7+
58
+ * Node.js & npm (for building the frontend)
59
+
60
+ ### Quick Start with Makefile
61
+
62
+ **Clone the repository**
63
+
64
+ ```bash
65
+ git clone https://github.com/querit-ai/search-api-webui.git
66
+ cd search-api-webui
67
+ ```
68
+
69
+ **Development Mode** (with hot reload)
70
+
71
+ ```bash
72
+ make dev
73
+ ```
74
+
75
+ This will:
76
+ - Start Flask backend on http://localhost:8889
77
+ - Start Vite frontend dev server on http://localhost:5173
78
+ - Automatically open your browser
79
+ - Enable hot module replacement for instant updates
80
+
81
+ **Build Python Wheel**
82
+
83
+ ```bash
84
+ make # or 'make all'
85
+ ```
86
+
87
+ **Build macOS DMG** (macOS only)
88
+
89
+ ```bash
90
+ make dmg # Builds for your current architecture
91
+ ```
92
+
93
+ ### Manual Setup
94
+
95
+ If you prefer not to use Makefile:
96
+
97
+ **Build Frontend**
98
+
99
+ ```bash
100
+ cd frontend
101
+ npm install
102
+ npm run build
103
+ cd ..
104
+ ```
105
+
106
+ **Install search-api-webui (Editable Mode)**
107
+
108
+ ```bash
109
+ pip install -e .
110
+ ```
111
+
112
+ **Run the Server**
113
+
114
+ ```bash
115
+ python -m search_api_webui.app
116
+ ```
117
+
118
+ ### Available Make Commands
119
+
120
+ ```bash
121
+ make # Build Python wheel package (default)
122
+ make dev # Start development servers with hot reload
123
+ make dmg # Build macOS DMG for current architecture
124
+ make backend # Start backend server only
125
+ make frontend # Start frontend dev server only
126
+ make clean # Clean build artifacts
127
+ make help # Show all available commands
128
+ ```
129
+
130
+ ## Configuration
131
+
132
+ ### Add API Keys
133
+
134
+ Open the WebUI settings page (click the gear icon). Enter your API Key for the selected provider (e.g., Querit). Keys are saved locally in $HOME/.search-api-webui/config.json.
135
+
136
+ ### Add New Providers
137
+
138
+ Edit providers.yaml in the root directory to add custom API endpoints. The system uses JMESPath to map JSON responses to the UI.
139
+
140
+ ```
141
+ my_custom_search:
142
+ url: “https://api.example.com/search”
143
+ method: “GET”
144
+ headers:
145
+ Authorization: “Bearer {api_key}”
146
+ params:
147
+ q: “{query}”
148
+ response_mapping:
149
+ root_path: “data.items”
150
+ fields:
151
+ title: “title”
152
+ url: “link”
153
+ snippet: “snippet”
154
+ ```
155
+
156
+ ## License
157
+
158
+ MIT License. See LICENSE for details.
@@ -4,15 +4,15 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "search-api-webui"
7
- version = "0.1.7"
8
- description = "A Search API WebUI for Querit, You, and other search providers."
7
+ version = "0.1.9"
8
+ description = "A Search API WebUI for testing and comparing Querit, You, and other search providers."
9
9
  readme = "README.md"
10
- requires-python = ">=3.4"
10
+ requires-python = ">=3.8"
11
11
  license = { text = "MIT" }
12
12
  authors = [
13
13
  { name = "querit.ai" }
14
14
  ]
15
- keywords = ["search", "api", "webui", "querit", "llm", "tool"]
15
+ keywords = ["search", "api", "webui", "querit", "llm", "tool", "arena", "benchmark"]
16
16
  classifiers = [
17
17
  "Development Status :: 3 - Alpha",
18
18
  "License :: OSI Approved :: MIT License",
@@ -29,6 +29,10 @@ dependencies = [
29
29
  "querit"
30
30
  ]
31
31
 
32
+ [project.optional-dependencies]
33
+ webview = ["pywebview>=5.0"]
34
+ build = ["pyinstaller>=6.0"]
35
+
32
36
  [project.urls]
33
37
  Homepage = "https://github.com/querit-ai/search-api-webui"
34
38
  Repository = "https://github.com/querit-ai/search-api-webui.git"
@@ -17,4 +17,3 @@
17
17
  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18
18
  # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19
19
  # DEALINGS IN THE SOFTWARE.
20
-
@@ -0,0 +1,311 @@
1
+ # Copyright (c) 2026 QUERIT PRIVATE LIMITED
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to
5
+ # deal in the Software without restriction, including without limitation the
6
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7
+ # sell copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19
+ # DEALINGS IN THE SOFTWARE.
20
+
21
+ import json
22
+ import logging
23
+ import socket
24
+ import sys
25
+ import threading
26
+ import time
27
+ import webbrowser
28
+ from pathlib import Path
29
+
30
+ from flask import Flask, jsonify, request, send_from_directory
31
+ from flask_cors import CORS
32
+
33
+ from search_api_webui.providers import load_providers
34
+
35
+ try:
36
+ import webview
37
+ WEBVIEW_AVAILABLE = True
38
+ except ImportError:
39
+ WEBVIEW_AVAILABLE = False
40
+
41
+
42
+ # Configure logging
43
+ logging.basicConfig(
44
+ level=logging.INFO,
45
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
46
+ )
47
+ logger = logging.getLogger(__name__)
48
+
49
+
50
+ def get_resource_path(relative_path):
51
+ '''Get absolute path to resource, works for dev and for PyInstaller.'''
52
+ try:
53
+ # PyInstaller creates a temp folder and stores path in _MEIPASS
54
+ base_path = Path(sys._MEIPASS)
55
+ except Exception:
56
+ base_path = Path(__file__).resolve().parent
57
+
58
+ return base_path / relative_path
59
+
60
+
61
+ CURRENT_DIR = Path(__file__).resolve().parent
62
+
63
+ # Handle static folder for both dev and packaged app
64
+ if hasattr(sys, '_MEIPASS'):
65
+ # Running in PyInstaller bundle
66
+ STATIC_FOLDER = Path(sys._MEIPASS) / 'static'
67
+ else:
68
+ # Running in development
69
+ STATIC_FOLDER = CURRENT_DIR / 'static'
70
+ if not STATIC_FOLDER.exists():
71
+ DEV_FRONTEND_DIST = CURRENT_DIR.parent / 'frontend' / 'dist'
72
+ if DEV_FRONTEND_DIST.exists():
73
+ STATIC_FOLDER = DEV_FRONTEND_DIST
74
+
75
+ app = Flask(__name__, static_folder=str(STATIC_FOLDER))
76
+ CORS(app)
77
+
78
+ # Use get_resource_path for providers.yaml
79
+ PROVIDERS_YAML = get_resource_path('providers.yaml')
80
+ USER_CONFIG_DIR = Path.home() / '.search-api-webui'
81
+ USER_CONFIG_JSON = USER_CONFIG_DIR / 'config.json'
82
+
83
+ if not USER_CONFIG_DIR.exists():
84
+ USER_CONFIG_DIR.mkdir(parents=True, exist_ok=True)
85
+
86
+ if PROVIDERS_YAML.exists():
87
+ provider_map = load_providers(str(PROVIDERS_YAML))
88
+ else:
89
+ logger.error(f'Configuration file not found at {PROVIDERS_YAML}')
90
+ provider_map = {}
91
+
92
+
93
+ def get_stored_config():
94
+ if not USER_CONFIG_JSON.exists():
95
+ return {}
96
+ try:
97
+ with open(USER_CONFIG_JSON, encoding='utf-8') as f:
98
+ return json.load(f)
99
+ except Exception as e:
100
+ logger.error(f'Error reading config: {e}')
101
+ return {}
102
+
103
+
104
+ def save_stored_config(config_dict):
105
+ try:
106
+ with open(USER_CONFIG_JSON, 'w', encoding='utf-8') as f:
107
+ json.dump(config_dict, f, indent=2)
108
+ except Exception as e:
109
+ logger.error(f'Error saving config: {e}')
110
+
111
+
112
+ @app.route('/api/providers', methods=['GET'])
113
+ def get_providers_list():
114
+ stored_config = get_stored_config()
115
+ providers_info = []
116
+
117
+ for name, provider_instance in provider_map.items():
118
+ config_details = provider_instance.config
119
+
120
+ user_conf = stored_config.get(name, {})
121
+
122
+ if isinstance(user_conf, str):
123
+ user_conf = {'api_key': user_conf}
124
+
125
+ has_key = bool(user_conf.get('api_key'))
126
+
127
+ providers_info.append(
128
+ {
129
+ 'name': name,
130
+ 'has_key': has_key,
131
+ 'details': config_details,
132
+ 'user_settings': {
133
+ 'api_url': user_conf.get('api_url', ''),
134
+ 'limit': user_conf.get('limit', '10'),
135
+ 'language': user_conf.get('language'),
136
+ },
137
+ },
138
+ )
139
+ return jsonify(providers_info)
140
+
141
+
142
+ @app.route('/api/config', methods=['POST'])
143
+ def update_config():
144
+ data = request.json
145
+ provider_name = data.get('provider')
146
+
147
+ if not provider_name:
148
+ return jsonify({'error': 'Provider name is required'}), 400
149
+
150
+ api_key = data.get('api_key')
151
+
152
+ api_url = data.get('api_url', '').strip()
153
+ limit = data.get('limit', '10')
154
+ language = data.get('language')
155
+
156
+ all_config = get_stored_config()
157
+
158
+ if provider_name in all_config and isinstance(all_config[provider_name], str):
159
+ all_config[provider_name] = {'api_key': all_config[provider_name]}
160
+
161
+ # Initialize provider config if not exists
162
+ if provider_name not in all_config:
163
+ all_config[provider_name] = {}
164
+
165
+ # Update advanced settings, skip empty values
166
+ if api_url:
167
+ all_config[provider_name]['api_url'] = api_url
168
+ elif 'api_url' in all_config[provider_name]:
169
+ del all_config[provider_name]['api_url']
170
+
171
+ if limit:
172
+ all_config[provider_name]['limit'] = limit
173
+ elif 'limit' in all_config[provider_name]:
174
+ del all_config[provider_name]['limit']
175
+
176
+ if language:
177
+ all_config[provider_name]['language'] = language
178
+ elif 'language' in all_config[provider_name]:
179
+ del all_config[provider_name]['language']
180
+
181
+ # Only update api_key if explicitly provided
182
+ if api_key is not None:
183
+ all_config[provider_name]['api_key'] = api_key
184
+
185
+ # Clean up empty provider config
186
+ if not all_config[provider_name]:
187
+ del all_config[provider_name]
188
+
189
+ save_stored_config(all_config)
190
+ return jsonify({'status': 'success'})
191
+
192
+
193
+ @app.route('/api/search', methods=['POST'])
194
+ def search_api():
195
+ data = request.json
196
+ query = data.get('query')
197
+ provider_name = data.get('provider', 'querit')
198
+
199
+ api_key = data.get('api_key')
200
+
201
+ stored_config = get_stored_config()
202
+ provider_config = stored_config.get(provider_name, {})
203
+
204
+ if isinstance(provider_config, str):
205
+ provider_config = {'api_key': provider_config}
206
+
207
+ if not api_key:
208
+ api_key = provider_config.get('api_key')
209
+
210
+ if not api_key:
211
+ return (
212
+ jsonify({'error': f'API Key for {provider_name} is missing. Please configure it.'}),
213
+ 401,
214
+ )
215
+
216
+ provider = provider_map.get(provider_name)
217
+ if not provider:
218
+ return jsonify({'error': 'Provider not found'}), 404
219
+
220
+ search_kwargs = {
221
+ 'api_url': provider_config.get('api_url'),
222
+ 'limit': provider_config.get('limit'),
223
+ 'language': provider_config.get('language'),
224
+ }
225
+
226
+ result = provider.search(query, api_key, **search_kwargs)
227
+ return jsonify(result)
228
+
229
+
230
+ # Host React Frontend
231
+ @app.route('/', defaults={'path': ''})
232
+ @app.route('/<path:path>')
233
+ def serve(path):
234
+ if path != '' and (STATIC_FOLDER / path).exists():
235
+ return send_from_directory(str(STATIC_FOLDER), path)
236
+ else:
237
+ return send_from_directory(str(STATIC_FOLDER), 'index.html')
238
+
239
+
240
+ def wait_for_server_ready(host, port):
241
+ start_time = time.time()
242
+ while time.time() - start_time < 10:
243
+ try:
244
+ with socket.create_connection((host, port), timeout=1):
245
+ return True
246
+ except (OSError, ConnectionRefusedError):
247
+ time.sleep(0.1)
248
+ return False
249
+
250
+
251
+ def main():
252
+ import argparse
253
+
254
+ parser = argparse.ArgumentParser(description='Search API WebUI')
255
+ parser.add_argument('--port', type=int, default=8889, help='Port to run the server on')
256
+ parser.add_argument('--host', type=str, default='localhost', help='Host to run the server on')
257
+ parser.add_argument('-w', '--webview', action='store_true', help='Use webview to open the application')
258
+ args = parser.parse_args()
259
+
260
+ url = f'http://{args.host}:{args.port}'
261
+ logger.info('Starting Search API WebUI...')
262
+ logger.info(f' - Config Storage: {USER_CONFIG_JSON}')
263
+ logger.info(f' - Serving on: {url}')
264
+ if args.webview:
265
+ logger.info(' - Mode: webview')
266
+
267
+ if args.webview:
268
+ if not WEBVIEW_AVAILABLE:
269
+ logger.warning('webview library not installed. Falling back to webbrowser.')
270
+ # Start server in background thread and wait for it to be ready
271
+ server_thread = threading.Thread(
272
+ target=lambda: app.run(
273
+ host=args.host, port=args.port, use_reloader=False,
274
+ ),
275
+ daemon=True,
276
+ )
277
+ server_thread.start()
278
+ if wait_for_server_ready(args.host, args.port):
279
+ logger.info(f'Server is ready! Opening browser: {url}')
280
+ webbrowser.open(url)
281
+ else:
282
+ logger.error('Server took too long to start. Browser not opened.')
283
+ else:
284
+ # Start server in background thread and wait for it to be ready, then start webview
285
+ server_thread = threading.Thread(
286
+ target=lambda: app.run(
287
+ host=args.host, port=args.port, use_reloader=False,
288
+ ),
289
+ daemon=True,
290
+ )
291
+ server_thread.start()
292
+ if wait_for_server_ready(args.host, args.port):
293
+ logger.info('Server is ready! Using webview mode...')
294
+ webview.create_window('Search API WebUI', url, width=1200, height=800)
295
+ webview.start()
296
+ else:
297
+ logger.error('Server took too long to start. Webview not opened.')
298
+ else:
299
+ # Start a background thread to check server status and open the browser automatically
300
+ def open_browser():
301
+ if wait_for_server_ready(args.host, args.port):
302
+ logger.info(f'Server is ready! Opening browser: {url}')
303
+ webbrowser.open(url)
304
+ else:
305
+ logger.error('Server took too long to start. Browser not opened.')
306
+ threading.Thread(target=open_browser, daemon=True).start()
307
+ app.run(host=args.host, port=args.port)
308
+
309
+
310
+ if __name__ == '__main__':
311
+ main()