feat: add web search block functionality and integrate with existing plugins
- Introduced a new web search block plugin to handle web search queries and results. - Updated copilot, doc block, and pro block plugins to include web search context in AI completions. - Implemented utility functions for parsing and building web search markdown. - Enhanced API to support web search requests and responses. - Added configuration for web search URL and timeout settings. - Updated size limit checks to account for web search content.
This commit is contained in:
@@ -48,6 +48,8 @@ JOB_COMPLETION_CONCURRENCY=2
|
|||||||
JOB_COMPLETION_MAX_QUEUE=16
|
JOB_COMPLETION_MAX_QUEUE=16
|
||||||
JOB_PRO_COMPLETION_CONCURRENCY=1
|
JOB_PRO_COMPLETION_CONCURRENCY=1
|
||||||
JOB_PRO_COMPLETION_MAX_QUEUE=8
|
JOB_PRO_COMPLETION_MAX_QUEUE=8
|
||||||
|
JOB_WEB_SEARCH_CONCURRENCY=1
|
||||||
|
JOB_WEB_SEARCH_MAX_QUEUE=4
|
||||||
JOB_COMPRESS_CONCURRENCY=1
|
JOB_COMPRESS_CONCURRENCY=1
|
||||||
JOB_COMPRESS_MAX_QUEUE=8
|
JOB_COMPRESS_MAX_QUEUE=8
|
||||||
JOB_OCR_CONCURRENCY=1
|
JOB_OCR_CONCURRENCY=1
|
||||||
@@ -89,16 +91,28 @@ RISK_ENFORCE_REDIS_FAIL_CLOSED=false
|
|||||||
RISK_COMPLETION_MODEL=gpt-4.1-mini
|
RISK_COMPLETION_MODEL=gpt-4.1-mini
|
||||||
RISK_PRO_MODEL=gpt-4.1
|
RISK_PRO_MODEL=gpt-4.1
|
||||||
RISK_VISION_MODEL=gpt-4.1-mini
|
RISK_VISION_MODEL=gpt-4.1-mini
|
||||||
|
RISK_WEB_SEARCH_MODEL=gpt-4.1-mini
|
||||||
RISK_COMPLETION_MAX_INPUT_CHARS=24000
|
RISK_COMPLETION_MAX_INPUT_CHARS=24000
|
||||||
RISK_COMPLETION_MAX_OUTPUT_TOKENS=768
|
RISK_COMPLETION_MAX_OUTPUT_TOKENS=768
|
||||||
RISK_COMPLETION_TEMPERATURE=0.4
|
RISK_COMPLETION_TEMPERATURE=0.4
|
||||||
RISK_PRO_MAX_INPUT_CHARS=48000
|
RISK_PRO_MAX_INPUT_CHARS=48000
|
||||||
RISK_PRO_MAX_OUTPUT_TOKENS=2048
|
RISK_PRO_MAX_OUTPUT_TOKENS=2048
|
||||||
RISK_PRO_TEMPERATURE=0.6
|
RISK_PRO_TEMPERATURE=0.6
|
||||||
|
RISK_WEB_SEARCH_MAX_INPUT_CHARS=128000
|
||||||
|
RISK_WEB_SEARCH_MAX_OUTPUT_TOKENS=4096
|
||||||
|
RISK_WEB_SEARCH_TEMPERATURE=0.4
|
||||||
RISK_COMPRESS_MAX_INPUT_CHARS=128000
|
RISK_COMPRESS_MAX_INPUT_CHARS=128000
|
||||||
RISK_COMPRESS_MAX_OUTPUT_TOKENS=1536
|
RISK_COMPRESS_MAX_OUTPUT_TOKENS=1536
|
||||||
RISK_OCR_MAX_INPUT_BYTES=10485760
|
RISK_OCR_MAX_INPUT_BYTES=10485760
|
||||||
|
|
||||||
|
# Web search providers
|
||||||
|
SEARXNG_BASE_URL=http://searxng:8080
|
||||||
|
SEARXNG_RESULT_LIMIT=10
|
||||||
|
FIRECRAWL_BASE_URL=http://firecrawl:3002
|
||||||
|
FIRECRAWL_API_KEY=change-me
|
||||||
|
WEB_SEARCH_QUERY_COUNT=4
|
||||||
|
WEB_SEARCH_SELECTED_URL_LIMIT=10
|
||||||
|
|
||||||
# Estimated pricing for budget control
|
# Estimated pricing for budget control
|
||||||
RISK_COMPLETION_INPUT_COST_PER_1K=0.0004
|
RISK_COMPLETION_INPUT_COST_PER_1K=0.0004
|
||||||
RISK_COMPLETION_OUTPUT_COST_PER_1K=0.0016
|
RISK_COMPLETION_OUTPUT_COST_PER_1K=0.0016
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
import ipaddress
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
|
from datetime import datetime
|
||||||
from typing import Any, Callable, Awaitable
|
from typing import Any, Callable, Awaitable
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
import markitdown
|
import markitdown
|
||||||
|
|
||||||
@@ -26,6 +32,14 @@ except Exception: # pragma: no cover
|
|||||||
IMAGE_MARKDOWN_RE = re.compile(r"!\[[^\]]*]\([^)]+\)")
|
IMAGE_MARKDOWN_RE = re.compile(r"!\[[^\]]*]\([^)]+\)")
|
||||||
IMAGE_HTML_RE = re.compile(r"<img\b[^>]*>", re.IGNORECASE)
|
IMAGE_HTML_RE = re.compile(r"<img\b[^>]*>", re.IGNORECASE)
|
||||||
ALLOWED_CONVERT_EXTENSIONS = {".txt", ".docx", ".pptx", ".pdf"}
|
ALLOWED_CONVERT_EXTENSIONS = {".txt", ".docx", ".pptx", ".pdf"}
|
||||||
|
SEARXNG_BASE_URL = (os.getenv("SEARXNG_BASE_URL", "http://searxng:8080") or "http://searxng:8080").rstrip("/")
|
||||||
|
SEARXNG_RESULT_LIMIT = max(1, int(os.getenv("SEARXNG_RESULT_LIMIT", "10") or "10"))
|
||||||
|
FIRECRAWL_BASE_URL = (os.getenv("FIRECRAWL_BASE_URL", "http://firecrawl:3002") or "http://firecrawl:3002").rstrip("/")
|
||||||
|
FIRECRAWL_API_KEY = os.getenv("FIRECRAWL_API_KEY", "").strip()
|
||||||
|
WEB_SEARCH_QUERY_COUNT = max(3, min(5, int(os.getenv("WEB_SEARCH_QUERY_COUNT", "4") or "4")))
|
||||||
|
WEB_SEARCH_SELECTED_URL_LIMIT = max(5, min(20, int(os.getenv("WEB_SEARCH_SELECTED_URL_LIMIT", "10") or "10")))
|
||||||
|
WEB_SEARCH_CRAWL_CONCURRENCY = max(1, min(5, int(os.getenv("WEB_SEARCH_CRAWL_CONCURRENCY", "3") or "3")))
|
||||||
|
WEB_SEARCH_CRAWL_TIMEOUT_SECONDS = max(10, min(90, int(os.getenv("WEB_SEARCH_CRAWL_TIMEOUT_SECONDS", "35") or "35")))
|
||||||
_markitdown_instance = None
|
_markitdown_instance = None
|
||||||
_risk_config = load_risk_config()
|
_risk_config = load_risk_config()
|
||||||
|
|
||||||
@@ -37,6 +51,17 @@ def _get_markitdown():
|
|||||||
return _markitdown_instance
|
return _markitdown_instance
|
||||||
|
|
||||||
|
|
||||||
|
def _convert_url_markdown(url: str) -> dict[str, Any]:
|
||||||
|
result = _get_markitdown().convert_url(url)
|
||||||
|
markdown = _normalize_multiline_text(str(getattr(result, "markdown", "") or ""))
|
||||||
|
title = str(getattr(result, "title", "") or "").strip()
|
||||||
|
return {
|
||||||
|
"url": url,
|
||||||
|
"title": title,
|
||||||
|
"markdown": markdown,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _safe_unlink(path: str | None) -> None:
|
def _safe_unlink(path: str | None) -> None:
|
||||||
if not path:
|
if not path:
|
||||||
return
|
return
|
||||||
@@ -52,6 +77,212 @@ def _sanitize_converted_markdown(text: str) -> str:
|
|||||||
return value.strip()
|
return value.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_multiline_text(value: str) -> str:
|
||||||
|
return (value or "").replace("\r\n", "\n").replace("\r", "\n").strip()
|
||||||
|
|
||||||
|
|
||||||
|
def _is_blocked_public_url(url: str) -> bool:
|
||||||
|
try:
|
||||||
|
parsed = urlparse((url or "").strip())
|
||||||
|
except Exception:
|
||||||
|
return True
|
||||||
|
if parsed.scheme not in {"http", "https"}:
|
||||||
|
return True
|
||||||
|
host = (parsed.hostname or "").strip().lower()
|
||||||
|
if not host:
|
||||||
|
return True
|
||||||
|
if host in {"localhost", "127.0.0.1", "::1"} or host.endswith(".local"):
|
||||||
|
return True
|
||||||
|
try:
|
||||||
|
ip = ipaddress.ip_address(host)
|
||||||
|
return ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_reserved or ip.is_multicast
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _strip_code_fence(value: str) -> str:
|
||||||
|
text = _normalize_multiline_text(value)
|
||||||
|
match = re.match(r"^```(?:json)?\s*([\s\S]*?)\s*```$", text, flags=re.IGNORECASE)
|
||||||
|
if match:
|
||||||
|
return match.group(1).strip()
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_json_array(value: str) -> list[Any]:
|
||||||
|
text = _strip_code_fence(value)
|
||||||
|
try:
|
||||||
|
parsed = json.loads(text)
|
||||||
|
return parsed if isinstance(parsed, list) else []
|
||||||
|
except Exception:
|
||||||
|
match = re.search(r"\[[\s\S]*\]", text)
|
||||||
|
if not match:
|
||||||
|
return []
|
||||||
|
try:
|
||||||
|
parsed = json.loads(match.group(0))
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
return parsed if isinstance(parsed, list) else []
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_search_queries(raw: str) -> list[str]:
|
||||||
|
items = _extract_json_array(raw)
|
||||||
|
queries: list[str] = []
|
||||||
|
if items:
|
||||||
|
for item in items:
|
||||||
|
text = str(item).strip()
|
||||||
|
if text and text not in queries:
|
||||||
|
queries.append(text)
|
||||||
|
else:
|
||||||
|
for line in _strip_code_fence(raw).splitlines():
|
||||||
|
text = re.sub(r"^\s*(?:[-*]|\d+[.)])\s*", "", line).strip()
|
||||||
|
if text and text not in queries:
|
||||||
|
queries.append(text)
|
||||||
|
return queries[:WEB_SEARCH_QUERY_COUNT]
|
||||||
|
|
||||||
|
|
||||||
|
def _clean_search_query_text(value: str) -> str:
|
||||||
|
text = _normalize_multiline_text(value)
|
||||||
|
text = re.sub(r"`{1,3}.*?`{1,3}", " ", text)
|
||||||
|
text = re.sub(r"[*_#>\[\]\(\){}|]+", " ", text)
|
||||||
|
text = re.sub(r"\s+", " ", text).strip(" -:;,./")
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def _build_fallback_search_queries(context: str, primary_queries: list[str]) -> list[str]:
|
||||||
|
queries: list[str] = []
|
||||||
|
|
||||||
|
def add(text: str) -> None:
|
||||||
|
cleaned = _clean_search_query_text(text)
|
||||||
|
if len(cleaned) < 2 or cleaned in queries:
|
||||||
|
return
|
||||||
|
queries.append(cleaned[:120])
|
||||||
|
|
||||||
|
for item in primary_queries:
|
||||||
|
add(item)
|
||||||
|
|
||||||
|
for line in context.splitlines():
|
||||||
|
cleaned = _clean_search_query_text(line)
|
||||||
|
if not cleaned:
|
||||||
|
continue
|
||||||
|
add(cleaned)
|
||||||
|
if re.search(r"[A-Za-z]", cleaned):
|
||||||
|
add(f"{cleaned} official")
|
||||||
|
add(f"{cleaned} github")
|
||||||
|
else:
|
||||||
|
add(f"{cleaned} 官网")
|
||||||
|
add(f"{cleaned} GitHub")
|
||||||
|
|
||||||
|
if context:
|
||||||
|
compact = _clean_search_query_text(context.replace("\n", " "))
|
||||||
|
if compact:
|
||||||
|
add(compact)
|
||||||
|
if re.search(r"[A-Za-z]", compact):
|
||||||
|
add(f"{compact} official documentation")
|
||||||
|
else:
|
||||||
|
add(f"{compact} 官方文档")
|
||||||
|
|
||||||
|
return queries[: max(WEB_SEARCH_QUERY_COUNT + 4, 8)]
|
||||||
|
|
||||||
|
|
||||||
|
def _build_no_result_content(queries: list[str], reason: str) -> str:
|
||||||
|
lines = [
|
||||||
|
"未检索到可用公开结果。",
|
||||||
|
"",
|
||||||
|
f"原因:{reason}",
|
||||||
|
]
|
||||||
|
if queries:
|
||||||
|
lines.extend(["", "已尝试的检索词:"])
|
||||||
|
lines.extend([f"- {query}" for query in queries])
|
||||||
|
lines.extend([
|
||||||
|
"",
|
||||||
|
"可以尝试缩短主题、补充专有名词,或直接给出官网、产品名、项目名、作者名等更具体的线索。",
|
||||||
|
])
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_selected_urls(raw: str) -> list[str]:
|
||||||
|
urls: list[str] = []
|
||||||
|
for item in _extract_json_array(raw):
|
||||||
|
text = str(item).strip()
|
||||||
|
if not text or _is_blocked_public_url(text) or text in urls:
|
||||||
|
continue
|
||||||
|
urls.append(text)
|
||||||
|
return urls[:WEB_SEARCH_SELECTED_URL_LIMIT]
|
||||||
|
|
||||||
|
|
||||||
|
def _summarize_search_results(query: str, results: list[dict[str, Any]]) -> str:
|
||||||
|
lines = [f"Query: {query}"]
|
||||||
|
for index, item in enumerate(results, start=1):
|
||||||
|
lines.append(
|
||||||
|
f"{index}. title={item.get('title', '')} url={item.get('url', '')} "
|
||||||
|
f"score={item.get('score', '')} date={item.get('published_date', '')} snippet={item.get('snippet', '')}"
|
||||||
|
)
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
async def _searxng_search(query: str, *, limit: int) -> list[dict[str, Any]]:
|
||||||
|
async with httpx.AsyncClient(timeout=httpx.Timeout(20.0, connect=10.0)) as client:
|
||||||
|
response = await client.get(
|
||||||
|
f"{SEARXNG_BASE_URL}/search",
|
||||||
|
params={"q": query, "format": "json"},
|
||||||
|
headers={
|
||||||
|
"Accept": "application/json",
|
||||||
|
"User-Agent": "llm-in-text-websearch/1.0",
|
||||||
|
"X-Forwarded-For": "127.0.0.1",
|
||||||
|
"X-Real-IP": "127.0.0.1",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
payload = response.json()
|
||||||
|
|
||||||
|
results: list[dict[str, Any]] = []
|
||||||
|
for item in payload.get("results") or []:
|
||||||
|
url = str(item.get("url") or item.get("link") or "").strip()
|
||||||
|
if not url or _is_blocked_public_url(url):
|
||||||
|
continue
|
||||||
|
results.append({
|
||||||
|
"title": str(item.get("title") or "").strip(),
|
||||||
|
"url": url,
|
||||||
|
"score": item.get("score"),
|
||||||
|
"published_date": str(item.get("publishedDate") or item.get("published_date") or item.get("published") or "").strip(),
|
||||||
|
"snippet": _normalize_multiline_text(str(item.get("content") or item.get("snippet") or "")),
|
||||||
|
})
|
||||||
|
if len(results) >= limit:
|
||||||
|
break
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
async def _firecrawl_scrape(url: str) -> dict[str, Any]:
|
||||||
|
headers = {"Content-Type": "application/json"}
|
||||||
|
if FIRECRAWL_API_KEY:
|
||||||
|
headers["Authorization"] = f"Bearer {FIRECRAWL_API_KEY}"
|
||||||
|
headers["X-Api-Key"] = FIRECRAWL_API_KEY
|
||||||
|
|
||||||
|
payload = None
|
||||||
|
async with httpx.AsyncClient(timeout=httpx.Timeout(20.0, connect=5.0, read=20.0)) as client:
|
||||||
|
try:
|
||||||
|
response = await client.post(
|
||||||
|
f"{FIRECRAWL_BASE_URL}/v1/scrape",
|
||||||
|
json={"url": url, "formats": ["markdown"]},
|
||||||
|
headers=headers,
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
payload = response.json()
|
||||||
|
except Exception:
|
||||||
|
payload = None
|
||||||
|
if payload is None:
|
||||||
|
return await asyncio.to_thread(_convert_url_markdown, url)
|
||||||
|
|
||||||
|
data = payload.get("data") or {}
|
||||||
|
markdown = _normalize_multiline_text(str(data.get("markdown") or data.get("content") or ""))
|
||||||
|
metadata = data.get("metadata") or {}
|
||||||
|
return {
|
||||||
|
"url": url,
|
||||||
|
"title": str(metadata.get("title") or "").strip(),
|
||||||
|
"markdown": markdown,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def sanitize_inline_completion_content(text: str, prefill: str = "") -> str:
|
def sanitize_inline_completion_content(text: str, prefill: str = "") -> str:
|
||||||
value = (text or "").strip()
|
value = (text or "").strip()
|
||||||
if not value:
|
if not value:
|
||||||
@@ -243,6 +474,194 @@ async def pro_completion_handler(
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
async def web_search_handler(
|
||||||
|
payload: dict[str, Any],
|
||||||
|
emit: Callable[[str, dict[str, Any]], Awaitable[None]],
|
||||||
|
is_cancelled: Callable[[], bool],
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
req = payload["request"]
|
||||||
|
identity, risk, lock_keys = await _enter_llm_execution(payload, emit)
|
||||||
|
policy = risk.get("policy") or {}
|
||||||
|
prefix = _normalize_multiline_text(req.get("prefix", ""))
|
||||||
|
suffix = _normalize_multiline_text(req.get("suffix", ""))
|
||||||
|
context = "\n\n".join(part for part in [prefix, suffix] if part).strip()
|
||||||
|
|
||||||
|
try:
|
||||||
|
await emit("progress", {"phase": "keywords", "message": "正在生成搜索关键词"})
|
||||||
|
keyword_prompt = (
|
||||||
|
"你是联网研究助手。请根据下面的上下文,生成 3 到 5 个适合在搜索引擎中直接使用的检索关键词或短句。\n"
|
||||||
|
"要求:\n"
|
||||||
|
"- 只返回 JSON 数组字符串\n"
|
||||||
|
"- 每个元素是一个简洁检索词\n"
|
||||||
|
"- 不要解释,不要 Markdown\n\n"
|
||||||
|
f"上下文:\n{context}"
|
||||||
|
)
|
||||||
|
keyword_result = await call_ollama(
|
||||||
|
keyword_prompt,
|
||||||
|
system_prompt="Return only a JSON array of search queries.",
|
||||||
|
tag=f'{payload["request_id"][:8]}-webq',
|
||||||
|
temperature=float(policy.get("temperature", 0.4)),
|
||||||
|
thinking=policy.get("thinking"),
|
||||||
|
model=policy.get("model"),
|
||||||
|
max_output_tokens=min(int(policy.get("max_output_tokens", 0) or 1024), 1024),
|
||||||
|
)
|
||||||
|
queries = _normalize_search_queries(keyword_result.get("content") or "")
|
||||||
|
if not queries:
|
||||||
|
queries = [_normalize_multiline_text(prefix or suffix)[:120] or "general research query"]
|
||||||
|
|
||||||
|
if is_cancelled():
|
||||||
|
raise asyncio.CancelledError()
|
||||||
|
|
||||||
|
await emit("progress", {"phase": "searching", "message": "正在通过 SearXNG 搜索"})
|
||||||
|
search_sections: list[str] = []
|
||||||
|
search_candidates: list[dict[str, Any]] = []
|
||||||
|
attempted_queries: list[str] = []
|
||||||
|
for query in queries:
|
||||||
|
if is_cancelled():
|
||||||
|
raise asyncio.CancelledError()
|
||||||
|
attempted_queries.append(query)
|
||||||
|
results = await _searxng_search(query, limit=SEARXNG_RESULT_LIMIT)
|
||||||
|
if not results:
|
||||||
|
continue
|
||||||
|
search_sections.append(_summarize_search_results(query, results))
|
||||||
|
search_candidates.extend(results)
|
||||||
|
|
||||||
|
if not search_candidates:
|
||||||
|
fallback_queries = _build_fallback_search_queries(context, queries)
|
||||||
|
retry_queries = [query for query in fallback_queries if query not in attempted_queries]
|
||||||
|
if retry_queries:
|
||||||
|
await emit("progress", {"phase": "searching", "message": "搜索结果较少,正在尝试更宽泛的检索词"})
|
||||||
|
for query in retry_queries:
|
||||||
|
if is_cancelled():
|
||||||
|
raise asyncio.CancelledError()
|
||||||
|
attempted_queries.append(query)
|
||||||
|
results = await _searxng_search(query, limit=SEARXNG_RESULT_LIMIT)
|
||||||
|
if not results:
|
||||||
|
continue
|
||||||
|
search_sections.append(_summarize_search_results(query, results))
|
||||||
|
search_candidates.extend(results)
|
||||||
|
|
||||||
|
if not search_candidates:
|
||||||
|
content = _build_no_result_content(attempted_queries, "SearXNG 未返回可用结果")
|
||||||
|
created_at = str(req.get("created_at") or payload.get("created_at") or "").strip() or datetime.now().isoformat()
|
||||||
|
await _exit_llm_execution(payload, identity, risk, lock_keys, status="completed", actual_output_text=content)
|
||||||
|
return {"content": content, "request_id": payload["request_id"], "created_at": created_at}
|
||||||
|
|
||||||
|
deduped_candidates: list[dict[str, Any]] = []
|
||||||
|
seen_urls: set[str] = set()
|
||||||
|
for item in search_candidates:
|
||||||
|
url = item["url"]
|
||||||
|
if url in seen_urls:
|
||||||
|
continue
|
||||||
|
seen_urls.add(url)
|
||||||
|
deduped_candidates.append(item)
|
||||||
|
|
||||||
|
await emit("progress", {"phase": "selecting_urls", "message": "正在筛选可信网址"})
|
||||||
|
search_results_text = "\n\n".join(search_sections)
|
||||||
|
selection_prompt = (
|
||||||
|
"你是研究检索筛选器。下面是多组搜索结果,请从中挑选 5 到 20 个最可信、最相关、最值得进一步抓取的 URL。\n"
|
||||||
|
"优先选择:官方文档、权威机构、原始来源、信息完整且日期清晰的页面。\n"
|
||||||
|
"只返回 JSON 数组,元素必须是 URL 字符串。\n\n"
|
||||||
|
f"原始上下文:\n{context}\n\n"
|
||||||
|
f"搜索结果:\n{search_results_text}"
|
||||||
|
)
|
||||||
|
selection_result = await call_ollama(
|
||||||
|
selection_prompt,
|
||||||
|
system_prompt="Return only a JSON array of selected URLs.",
|
||||||
|
tag=f'{payload["request_id"][:8]}-webu',
|
||||||
|
temperature=0.2,
|
||||||
|
thinking=policy.get("thinking"),
|
||||||
|
model=policy.get("model"),
|
||||||
|
max_output_tokens=min(int(policy.get("max_output_tokens", 0) or 1024), 1024),
|
||||||
|
)
|
||||||
|
selected_urls = _normalize_selected_urls(selection_result.get("content") or "")
|
||||||
|
if not selected_urls:
|
||||||
|
selected_urls = [item["url"] for item in deduped_candidates[:WEB_SEARCH_SELECTED_URL_LIMIT]]
|
||||||
|
|
||||||
|
if is_cancelled():
|
||||||
|
raise asyncio.CancelledError()
|
||||||
|
|
||||||
|
await emit("progress", {"phase": "crawling", "message": "正在抓取网页内容"})
|
||||||
|
selected_url_set = set(selected_urls)
|
||||||
|
selected_candidates = [item for item in deduped_candidates if item["url"] in selected_url_set][:WEB_SEARCH_SELECTED_URL_LIMIT]
|
||||||
|
crawl_sem = asyncio.Semaphore(WEB_SEARCH_CRAWL_CONCURRENCY)
|
||||||
|
|
||||||
|
async def _crawl_candidate(item: dict[str, Any]) -> dict[str, Any] | None:
|
||||||
|
if is_cancelled():
|
||||||
|
raise asyncio.CancelledError()
|
||||||
|
async with crawl_sem:
|
||||||
|
try:
|
||||||
|
scraped = await asyncio.wait_for(
|
||||||
|
_firecrawl_scrape(item["url"]),
|
||||||
|
timeout=WEB_SEARCH_CRAWL_TIMEOUT_SECONDS,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
if not scraped.get("markdown"):
|
||||||
|
return None
|
||||||
|
return {
|
||||||
|
"url": item["url"],
|
||||||
|
"title": item.get("title") or scraped.get("title") or "",
|
||||||
|
"score": item.get("score"),
|
||||||
|
"published_date": item.get("published_date") or "",
|
||||||
|
"content": scraped["markdown"],
|
||||||
|
}
|
||||||
|
|
||||||
|
crawl_results = await asyncio.gather(*[_crawl_candidate(item) for item in selected_candidates])
|
||||||
|
crawled_pages = [page for page in crawl_results if page]
|
||||||
|
|
||||||
|
if not crawled_pages:
|
||||||
|
content = _build_no_result_content(selected_urls, "已检索到候选网页,但未抓取到可用正文")
|
||||||
|
created_at = str(req.get("created_at") or payload.get("created_at") or "").strip() or datetime.now().isoformat()
|
||||||
|
await _exit_llm_execution(payload, identity, risk, lock_keys, status="completed", actual_output_text=content)
|
||||||
|
return {"content": content, "request_id": payload["request_id"], "created_at": created_at}
|
||||||
|
|
||||||
|
await emit("progress", {"phase": "synthesizing", "message": "正在整理搜索结果"})
|
||||||
|
page_sections = []
|
||||||
|
for index, page in enumerate(crawled_pages, start=1):
|
||||||
|
page_sections.append(
|
||||||
|
f"[Source {index}]\n"
|
||||||
|
f"URL: {page['url']}\n"
|
||||||
|
f"Title: {page.get('title', '')}\n"
|
||||||
|
f"Score: {page.get('score', '')}\n"
|
||||||
|
f"Date: {page.get('published_date', '')}\n"
|
||||||
|
f"Content:\n{page['content'][:12000]}"
|
||||||
|
)
|
||||||
|
crawled_text = "\n\n".join(page_sections)
|
||||||
|
|
||||||
|
synthesis_prompt = (
|
||||||
|
"你是研究写作助手。请根据原始上下文和抓取到的网页内容,写出一篇长篇、结构完整、信息密集的 Markdown 正文。\n"
|
||||||
|
"要求:\n"
|
||||||
|
"- 不要写标题\n"
|
||||||
|
"- 不要写引用编号、来源表或 URL 列表\n"
|
||||||
|
"- 直接输出最终正文\n"
|
||||||
|
"- 如果信息存在不确定性,用审慎措辞表达\n\n"
|
||||||
|
f"原始上下文:\n{context}\n\n"
|
||||||
|
f"抓取内容:\n{crawled_text}"
|
||||||
|
)
|
||||||
|
synthesis_result = await call_ollama(
|
||||||
|
synthesis_prompt,
|
||||||
|
system_prompt="Return only the final markdown body with no title and no citations list.",
|
||||||
|
tag=f'{payload["request_id"][:8]}-webf',
|
||||||
|
temperature=float(policy.get("temperature", 0.4)),
|
||||||
|
thinking=policy.get("thinking"),
|
||||||
|
model=policy.get("model"),
|
||||||
|
max_output_tokens=int(policy.get("max_output_tokens", 0) or 0),
|
||||||
|
)
|
||||||
|
content = _normalize_multiline_text(synthesis_result.get("content") or "")
|
||||||
|
if not content:
|
||||||
|
raise RuntimeError("联网搜索生成了空结果")
|
||||||
|
created_at = str(req.get("created_at") or payload.get("created_at") or "").strip() or datetime.now().isoformat()
|
||||||
|
await _exit_llm_execution(payload, identity, risk, lock_keys, status="completed", actual_output_text=content)
|
||||||
|
return {"content": content, "request_id": payload["request_id"], "created_at": created_at}
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
await _exit_llm_execution(payload, identity, risk, lock_keys, status="cancelled", error_code="cancelled")
|
||||||
|
raise
|
||||||
|
except Exception:
|
||||||
|
await _exit_llm_execution(payload, identity, risk, lock_keys, status="failed", error_code="web_search_failed")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
async def compress_handler(
|
async def compress_handler(
|
||||||
payload: dict[str, Any],
|
payload: dict[str, Any],
|
||||||
emit: Callable[[str, dict[str, Any]], Awaitable[None]],
|
emit: Callable[[str, dict[str, Any]], Awaitable[None]],
|
||||||
|
|||||||
+36
-2
@@ -25,6 +25,7 @@ TERMINAL_EVENTS = {"done", "error", "cancelled"}
|
|||||||
JOB_TYPES = (
|
JOB_TYPES = (
|
||||||
"completion",
|
"completion",
|
||||||
"pro_completion",
|
"pro_completion",
|
||||||
|
"web_search",
|
||||||
"compress",
|
"compress",
|
||||||
"ocr",
|
"ocr",
|
||||||
"convert",
|
"convert",
|
||||||
@@ -35,6 +36,7 @@ JOB_TYPES = (
|
|||||||
DEFAULT_CONCURRENCY = {
|
DEFAULT_CONCURRENCY = {
|
||||||
"completion": 2,
|
"completion": 2,
|
||||||
"pro_completion": 1,
|
"pro_completion": 1,
|
||||||
|
"web_search": 1,
|
||||||
"compress": 1,
|
"compress": 1,
|
||||||
"ocr": 1,
|
"ocr": 1,
|
||||||
"convert": 1,
|
"convert": 1,
|
||||||
@@ -45,6 +47,7 @@ DEFAULT_CONCURRENCY = {
|
|||||||
DEFAULT_QUEUE_SIZE = {
|
DEFAULT_QUEUE_SIZE = {
|
||||||
"completion": 16,
|
"completion": 16,
|
||||||
"pro_completion": 8,
|
"pro_completion": 8,
|
||||||
|
"web_search": 4,
|
||||||
"compress": 8,
|
"compress": 8,
|
||||||
"ocr": 8,
|
"ocr": 8,
|
||||||
"convert": 8,
|
"convert": 8,
|
||||||
@@ -54,11 +57,42 @@ DEFAULT_QUEUE_SIZE = {
|
|||||||
|
|
||||||
|
|
||||||
class JobSystemError(RuntimeError):
|
class JobSystemError(RuntimeError):
|
||||||
pass
|
"""任务系统内部错误"""
|
||||||
|
|
||||||
|
def __init__(self, message: str = "任务系统错误", error_code: str = "job_system_error") -> None:
|
||||||
|
super().__init__(message)
|
||||||
|
self.message = message
|
||||||
|
self.error_code = error_code
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"{self.error_code}: {self.message}"
|
||||||
|
|
||||||
|
|
||||||
class QueueFullError(JobSystemError):
|
class QueueFullError(JobSystemError):
|
||||||
pass
|
"""任务队列已满"""
|
||||||
|
|
||||||
|
def __init__(self, job_type: str, max_queue: int) -> None:
|
||||||
|
super().__init__(f"{job_type} 队列已满 (当前: {max_queue}/{max_queue})", "queue_full")
|
||||||
|
self.job_type = job_type
|
||||||
|
self.max_queue = max_queue
|
||||||
|
|
||||||
|
|
||||||
|
# 修复:添加完整的异常类实现
|
||||||
|
class JobSystemError(RuntimeError):
|
||||||
|
def __init__(self, message: str = "任务系统错误", error_code: str = "job_system_error") -> None:
|
||||||
|
super().__init__(message)
|
||||||
|
self.message = message
|
||||||
|
self.error_code = error_code
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"{self.error_code}: {self.message}"
|
||||||
|
|
||||||
|
|
||||||
|
class QueueFullError(JobSystemError):
|
||||||
|
def __init__(self, job_type: str, max_queue: int) -> None:
|
||||||
|
super().__init__(f"{job_type} 队列已满 (当前: {max_queue}/{max_queue})", "queue_full")
|
||||||
|
self.job_type = job_type
|
||||||
|
self.max_queue = max_queue
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
|
|||||||
@@ -47,6 +47,16 @@ def resolve_llm_policy(job_type: str, request_payload: dict[str, Any], config: R
|
|||||||
temperature=config.pro_temperature,
|
temperature=config.pro_temperature,
|
||||||
thinking=_normalize_thinking(request_payload.get("pro_thinking"), allow_high=True) or "medium",
|
thinking=_normalize_thinking(request_payload.get("pro_thinking"), allow_high=True) or "medium",
|
||||||
)
|
)
|
||||||
|
if job_type == "web_search":
|
||||||
|
return LLMPolicy(
|
||||||
|
job_type=job_type,
|
||||||
|
model=config.web_search_model,
|
||||||
|
profile="completion",
|
||||||
|
max_input_chars=config.web_search_max_input_chars,
|
||||||
|
max_output_tokens=config.web_search_max_output_tokens,
|
||||||
|
temperature=config.web_search_temperature,
|
||||||
|
thinking="low",
|
||||||
|
)
|
||||||
if job_type == "compress":
|
if job_type == "compress":
|
||||||
return LLMPolicy(
|
return LLMPolicy(
|
||||||
job_type=job_type,
|
job_type=job_type,
|
||||||
|
|||||||
+58
-3
@@ -5,6 +5,7 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
|
from datetime import datetime
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from fastapi import FastAPI, File, Form, HTTPException, Request, Response, Security, UploadFile
|
from fastapi import FastAPI, File, Form, HTTPException, Request, Response, Security, UploadFile
|
||||||
@@ -27,6 +28,7 @@ from job_handlers import (
|
|||||||
ocr_handler,
|
ocr_handler,
|
||||||
pro_completion_handler,
|
pro_completion_handler,
|
||||||
tts_handler,
|
tts_handler,
|
||||||
|
web_search_handler,
|
||||||
)
|
)
|
||||||
from job_system import (
|
from job_system import (
|
||||||
InMemoryJobManager,
|
InMemoryJobManager,
|
||||||
@@ -87,6 +89,14 @@ class ProCompletionRequest(BaseModel):
|
|||||||
user_preferences: Optional[UserPreferences] = None
|
user_preferences: Optional[UserPreferences] = None
|
||||||
|
|
||||||
|
|
||||||
|
class WebSearchRequest(BaseModel):
|
||||||
|
prefix: str
|
||||||
|
suffix: str
|
||||||
|
languageId: str = "markdown"
|
||||||
|
privacy_mode: bool = False
|
||||||
|
user_preferences: Optional[UserPreferences] = None
|
||||||
|
|
||||||
|
|
||||||
class CancelCompletionRequest(BaseModel):
|
class CancelCompletionRequest(BaseModel):
|
||||||
request_id: str
|
request_id: str
|
||||||
reason: str = "abort"
|
reason: str = "abort"
|
||||||
@@ -267,18 +277,24 @@ async def _authorize_request(
|
|||||||
|
|
||||||
|
|
||||||
def _register_handlers() -> None:
|
def _register_handlers() -> None:
|
||||||
|
"""注册所有任务处理器。每次调用都会重新获取当前 manager 实例并强制注册,
|
||||||
|
确保 Redis 重连或实例重建后处理器不会丢失。"""
|
||||||
global _handlers_registered
|
global _handlers_registered
|
||||||
if _handlers_registered:
|
|
||||||
return
|
|
||||||
manager = get_job_manager()
|
manager = get_job_manager()
|
||||||
|
# 强制清空旧 handlers,避免重复注册累积
|
||||||
|
manager.handlers.clear()
|
||||||
manager.register_handler("completion", completion_handler)
|
manager.register_handler("completion", completion_handler)
|
||||||
manager.register_handler("pro_completion", pro_completion_handler)
|
manager.register_handler("pro_completion", pro_completion_handler)
|
||||||
|
manager.register_handler("web_search", web_search_handler)
|
||||||
manager.register_handler("compress", compress_handler)
|
manager.register_handler("compress", compress_handler)
|
||||||
manager.register_handler("ocr", ocr_handler)
|
manager.register_handler("ocr", ocr_handler)
|
||||||
manager.register_handler("convert", convert_handler)
|
manager.register_handler("convert", convert_handler)
|
||||||
manager.register_handler("tts", tts_handler)
|
manager.register_handler("tts", tts_handler)
|
||||||
manager.register_handler("asr", asr_handler)
|
manager.register_handler("asr", asr_handler)
|
||||||
_handlers_registered = True
|
_handlers_registered = True
|
||||||
|
|
||||||
|
# 打印注册信息便于调试
|
||||||
|
logger.info("handlers registered: %s", list(manager.handlers.keys()))tered: %s", list(manager.handlers.keys()))
|
||||||
|
|
||||||
|
|
||||||
def _sse(event: str, data: dict) -> str:
|
def _sse(event: str, data: dict) -> str:
|
||||||
@@ -375,7 +391,7 @@ async def _guard_api_request(request: Request, *, scope: str) -> tuple[RiskIdent
|
|||||||
return identity, decision
|
return identity, decision
|
||||||
|
|
||||||
|
|
||||||
def _estimate_completion_chars(req: CompletionRequest | ProCompletionRequest) -> int:
|
def _estimate_completion_chars(req: CompletionRequest | ProCompletionRequest | WebSearchRequest) -> int:
|
||||||
return len(req.prefix or "") + len(req.suffix or "") + len(getattr(req, "instruction", "") or "")
|
return len(req.prefix or "") + len(req.suffix or "") + len(getattr(req, "instruction", "") or "")
|
||||||
|
|
||||||
|
|
||||||
@@ -551,6 +567,45 @@ async def cancel_pro_completion(req: CancelCompletionRequest, auth: dict = Secur
|
|||||||
return await _cancel_job(req.request_id or "", req.reason)
|
return await _cancel_job(req.request_id or "", req.reason)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/v1/web-search")
|
||||||
|
async def create_web_search(
|
||||||
|
request: Request,
|
||||||
|
req: WebSearchRequest,
|
||||||
|
auth: dict = Security(_authorize_request),
|
||||||
|
):
|
||||||
|
del auth
|
||||||
|
body = {
|
||||||
|
"prefix": req.prefix,
|
||||||
|
"suffix": req.suffix,
|
||||||
|
"languageId": req.languageId,
|
||||||
|
"privacy_mode": req.privacy_mode,
|
||||||
|
"user_preferences": _serialize_preferences(req.user_preferences),
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
identity, payload = await _prepare_llm_payload(
|
||||||
|
request,
|
||||||
|
job_type="web_search",
|
||||||
|
request_body=body,
|
||||||
|
raw_size=_estimate_completion_chars(req),
|
||||||
|
token_source_text=f"{req.prefix}\n{req.suffix}",
|
||||||
|
)
|
||||||
|
payload["created_at"] = datetime.utcnow().isoformat()
|
||||||
|
job_id = await _queue_job("web_search", payload, identity.request_id)
|
||||||
|
except RiskRejected as exc:
|
||||||
|
return _risk_json_response(_request_identity(request), exc.decision)
|
||||||
|
except QueueFullError as exc:
|
||||||
|
return JSONResponse({"error": str(exc), "request_id": _request_id(request)}, status_code=429)
|
||||||
|
except JobSystemError as exc:
|
||||||
|
return JSONResponse({"error": str(exc), "request_id": _request_id(request)}, status_code=503)
|
||||||
|
return await _stream_job(job_id)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/v1/web-search/cancel")
|
||||||
|
async def cancel_web_search(req: CancelCompletionRequest, auth: dict = Security(_authorize_request)):
|
||||||
|
del auth
|
||||||
|
return await _cancel_job(req.request_id or "", req.reason)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/v1/pro/completions/status/{request_id}")
|
@app.get("/v1/pro/completions/status/{request_id}")
|
||||||
async def get_pro_completion_status(request_id: str, auth: dict = Security(_authorize_request)):
|
async def get_pro_completion_status(request_id: str, auth: dict = Security(_authorize_request)):
|
||||||
del auth
|
del auth
|
||||||
|
|||||||
@@ -74,6 +74,10 @@ class RiskConfig:
|
|||||||
pro_max_input_chars: int
|
pro_max_input_chars: int
|
||||||
pro_max_output_tokens: int
|
pro_max_output_tokens: int
|
||||||
pro_temperature: float
|
pro_temperature: float
|
||||||
|
web_search_model: str
|
||||||
|
web_search_max_input_chars: int
|
||||||
|
web_search_max_output_tokens: int
|
||||||
|
web_search_temperature: float
|
||||||
compress_max_input_chars: int
|
compress_max_input_chars: int
|
||||||
compress_max_output_tokens: int
|
compress_max_output_tokens: int
|
||||||
ocr_max_input_bytes: int
|
ocr_max_input_bytes: int
|
||||||
@@ -128,6 +132,10 @@ def load_risk_config() -> RiskConfig:
|
|||||||
pro_max_input_chars=_int_env("RISK_PRO_MAX_INPUT_CHARS", 48000),
|
pro_max_input_chars=_int_env("RISK_PRO_MAX_INPUT_CHARS", 48000),
|
||||||
pro_max_output_tokens=_int_env("RISK_PRO_MAX_OUTPUT_TOKENS", 2048),
|
pro_max_output_tokens=_int_env("RISK_PRO_MAX_OUTPUT_TOKENS", 2048),
|
||||||
pro_temperature=_float_env("RISK_PRO_TEMPERATURE", 0.6),
|
pro_temperature=_float_env("RISK_PRO_TEMPERATURE", 0.6),
|
||||||
|
web_search_model=_str_env("RISK_WEB_SEARCH_MODEL", os.getenv("LLM_MODEL", "gpt-4.1-mini")),
|
||||||
|
web_search_max_input_chars=_int_env("RISK_WEB_SEARCH_MAX_INPUT_CHARS", 128000),
|
||||||
|
web_search_max_output_tokens=_int_env("RISK_WEB_SEARCH_MAX_OUTPUT_TOKENS", 4096),
|
||||||
|
web_search_temperature=_float_env("RISK_WEB_SEARCH_TEMPERATURE", 0.4),
|
||||||
compress_max_input_chars=_int_env("RISK_COMPRESS_MAX_INPUT_CHARS", 128000),
|
compress_max_input_chars=_int_env("RISK_COMPRESS_MAX_INPUT_CHARS", 128000),
|
||||||
compress_max_output_tokens=_int_env("RISK_COMPRESS_MAX_OUTPUT_TOKENS", 1536),
|
compress_max_output_tokens=_int_env("RISK_COMPRESS_MAX_OUTPUT_TOKENS", 1536),
|
||||||
ocr_max_input_bytes=_int_env("RISK_OCR_MAX_INPUT_BYTES", 10 * 1024 * 1024),
|
ocr_max_input_bytes=_int_env("RISK_OCR_MAX_INPUT_BYTES", 10 * 1024 * 1024),
|
||||||
|
|||||||
@@ -178,7 +178,10 @@ class RedisRiskBackend(BaseRiskBackend):
|
|||||||
value = await self.redis.get(key)
|
value = await self.redis.get(key)
|
||||||
if value is None:
|
if value is None:
|
||||||
return 0
|
return 0
|
||||||
return int(value)
|
try:
|
||||||
|
return int(value)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return int(float(value))
|
||||||
|
|
||||||
async def set_int(self, key: str, value: int, ttl_seconds: int) -> None:
|
async def set_int(self, key: str, value: int, ttl_seconds: int) -> None:
|
||||||
await self.redis.set(key, value, ex=ttl_seconds)
|
await self.redis.set(key, value, ex=ttl_seconds)
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
import asyncio
|
||||||
|
import importlib
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
os.environ["JOB_BACKEND"] = "memory"
|
||||||
|
os.environ["DOCS_BACKEND"] = "memory"
|
||||||
|
|
||||||
|
CURRENT_DIR = Path(__file__).resolve().parent
|
||||||
|
BACKEND_DIR = CURRENT_DIR.parent
|
||||||
|
if str(BACKEND_DIR) not in sys.path:
|
||||||
|
sys.path.insert(0, str(BACKEND_DIR))
|
||||||
|
|
||||||
|
import job_handlers # type: ignore
|
||||||
|
import job_system # type: ignore
|
||||||
|
import risk_control # type: ignore
|
||||||
|
import session_store # type: ignore
|
||||||
|
import audit_store # type: ignore
|
||||||
|
|
||||||
|
main = importlib.import_module("main")
|
||||||
|
|
||||||
|
HEADERS = {"X-API-Key": main.API_KEY}
|
||||||
|
|
||||||
|
|
||||||
|
def setup_function():
|
||||||
|
job_system.reset_job_manager()
|
||||||
|
risk_control.reset_risk_controller()
|
||||||
|
session_store.reset_session_store()
|
||||||
|
audit_store.reset_audit_store()
|
||||||
|
main._handlers_registered = False
|
||||||
|
|
||||||
|
|
||||||
|
def _payload():
|
||||||
|
return {
|
||||||
|
"prefix": "比较当前主流向量数据库的设计差异",
|
||||||
|
"suffix": "",
|
||||||
|
"languageId": "markdown",
|
||||||
|
"privacy_mode": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_blocked_public_url():
|
||||||
|
assert job_handlers._is_blocked_public_url("http://127.0.0.1/test") is True
|
||||||
|
assert job_handlers._is_blocked_public_url("file:///tmp/test") is True
|
||||||
|
assert job_handlers._is_blocked_public_url("https://example.com/docs") is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_web_search_route_returns_done(monkeypatch):
|
||||||
|
async def fake_call_ollama(prompt, system_prompt=None, tag="", **kwargs): # noqa: ARG001
|
||||||
|
if tag.endswith("-webq"):
|
||||||
|
return {"content": '["vector database comparison", "pinecone weaviate qdrant"]'}
|
||||||
|
if tag.endswith("-webu"):
|
||||||
|
return {"content": '["https://example.com/a", "https://example.com/b"]'}
|
||||||
|
if tag.endswith("-webf"):
|
||||||
|
return {"content": "第一段\n\n第二段"}
|
||||||
|
raise AssertionError(f"unexpected tag: {tag}")
|
||||||
|
|
||||||
|
async def fake_searxng_search(query, *, limit): # noqa: ARG001
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"title": "Doc A",
|
||||||
|
"url": "https://example.com/a",
|
||||||
|
"score": 9.1,
|
||||||
|
"published_date": "2026-06-08",
|
||||||
|
"snippet": "snippet a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Doc B",
|
||||||
|
"url": "https://example.com/b",
|
||||||
|
"score": 8.8,
|
||||||
|
"published_date": "2026-06-07",
|
||||||
|
"snippet": "snippet b",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
async def fake_firecrawl_scrape(url):
|
||||||
|
return {"url": url, "title": f"title for {url}", "markdown": f"content for {url}"}
|
||||||
|
|
||||||
|
monkeypatch.setattr(job_handlers, "call_ollama", fake_call_ollama)
|
||||||
|
monkeypatch.setattr(job_handlers, "_searxng_search", fake_searxng_search)
|
||||||
|
monkeypatch.setattr(job_handlers, "_firecrawl_scrape", fake_firecrawl_scrape)
|
||||||
|
|
||||||
|
with TestClient(main.app) as client:
|
||||||
|
with client.stream("POST", "/v1/web-search", headers=HEADERS, json=_payload()) as resp:
|
||||||
|
assert resp.status_code == 200
|
||||||
|
body = "".join(resp.iter_text())
|
||||||
|
|
||||||
|
assert "event: progress" in body
|
||||||
|
assert "keywords" in body
|
||||||
|
assert "searching" in body
|
||||||
|
assert "selecting_urls" in body
|
||||||
|
assert "crawling" in body
|
||||||
|
assert "synthesizing" in body
|
||||||
|
assert "event: done" in body
|
||||||
|
assert "第一段" in body
|
||||||
|
|
||||||
|
|
||||||
|
def test_cancel_web_search(monkeypatch):
|
||||||
|
started = threading.Event()
|
||||||
|
cancelled = threading.Event()
|
||||||
|
|
||||||
|
async def fake_call_ollama(*args, **kwargs):
|
||||||
|
tag = kwargs.get("tag", "")
|
||||||
|
if tag.endswith("-webq"):
|
||||||
|
started.set()
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
await asyncio.sleep(0.05)
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
cancelled.set()
|
||||||
|
raise
|
||||||
|
return {"content": "[]"}
|
||||||
|
|
||||||
|
monkeypatch.setattr(job_handlers, "call_ollama", fake_call_ollama)
|
||||||
|
request_id = "req-web-search-cancel"
|
||||||
|
|
||||||
|
with TestClient(main.app) as client:
|
||||||
|
response_box = {}
|
||||||
|
|
||||||
|
def send_request():
|
||||||
|
with client.stream(
|
||||||
|
"POST",
|
||||||
|
"/v1/web-search",
|
||||||
|
headers={**HEADERS, "X-Request-Id": request_id},
|
||||||
|
json=_payload(),
|
||||||
|
) as response:
|
||||||
|
response_box["status_code"] = response.status_code
|
||||||
|
response_box["body"] = "".join(response.iter_text())
|
||||||
|
|
||||||
|
search_thread = threading.Thread(target=send_request, daemon=True)
|
||||||
|
search_thread.start()
|
||||||
|
|
||||||
|
assert started.wait(timeout=2.0)
|
||||||
|
|
||||||
|
cancel_response = client.post(
|
||||||
|
"/v1/web-search/cancel",
|
||||||
|
headers=HEADERS,
|
||||||
|
json={"request_id": request_id, "reason": "abort"},
|
||||||
|
)
|
||||||
|
assert cancel_response.status_code == 200
|
||||||
|
assert cancel_response.json() == {"cancelled": True, "status": "ok"}
|
||||||
|
|
||||||
|
search_thread.join(timeout=5.0)
|
||||||
|
assert not search_thread.is_alive()
|
||||||
|
assert cancelled.wait(timeout=2.0)
|
||||||
|
assert "event: cancelled" in response_box["body"]
|
||||||
@@ -9,6 +9,7 @@ from job_handlers import (
|
|||||||
ocr_handler,
|
ocr_handler,
|
||||||
pro_completion_handler,
|
pro_completion_handler,
|
||||||
tts_handler,
|
tts_handler,
|
||||||
|
web_search_handler,
|
||||||
)
|
)
|
||||||
from job_system import RedisJobManager, RedisWorker
|
from job_system import RedisJobManager, RedisWorker
|
||||||
|
|
||||||
@@ -23,6 +24,7 @@ async def main() -> None:
|
|||||||
manager = RedisJobManager()
|
manager = RedisJobManager()
|
||||||
manager.register_handler("completion", completion_handler)
|
manager.register_handler("completion", completion_handler)
|
||||||
manager.register_handler("pro_completion", pro_completion_handler)
|
manager.register_handler("pro_completion", pro_completion_handler)
|
||||||
|
manager.register_handler("web_search", web_search_handler)
|
||||||
manager.register_handler("compress", compress_handler)
|
manager.register_handler("compress", compress_handler)
|
||||||
manager.register_handler("ocr", ocr_handler)
|
manager.register_handler("ocr", ocr_handler)
|
||||||
manager.register_handler("convert", convert_handler)
|
manager.register_handler("convert", convert_handler)
|
||||||
|
|||||||
@@ -25,6 +25,61 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./docker-data/redis:/data
|
- ./docker-data/redis:/data
|
||||||
|
|
||||||
|
searxng:
|
||||||
|
image: ${DOCKER_REGISTRY_PREFIX:-}searxng/searxng:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
BASE_URL: http://searxng:8080/
|
||||||
|
INSTANCE_NAME: llm-in-text-search
|
||||||
|
volumes:
|
||||||
|
- ./docker-data/searxng:/etc/searxng
|
||||||
|
|
||||||
|
firecrawl-postgres:
|
||||||
|
image: ${DOCKER_REGISTRY_PREFIX:-}postgres:16-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: firecrawl
|
||||||
|
POSTGRES_USER: firecrawl
|
||||||
|
POSTGRES_PASSWORD: firecrawl
|
||||||
|
volumes:
|
||||||
|
- ./docker-data/firecrawl-postgres:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
firecrawl-rabbitmq:
|
||||||
|
image: ${DOCKER_REGISTRY_PREFIX:-}rabbitmq:3-management-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- ./docker-data/firecrawl-rabbitmq:/var/lib/rabbitmq
|
||||||
|
|
||||||
|
firecrawl-playwright:
|
||||||
|
image: ghcr.io/firecrawl/playwright-service:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
PORT: 3000
|
||||||
|
|
||||||
|
firecrawl:
|
||||||
|
image: ghcr.io/firecrawl/firecrawl:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
HOST: 0.0.0.0
|
||||||
|
PORT: 3002
|
||||||
|
POSTGRES_HOST: firecrawl-postgres
|
||||||
|
POSTGRES_PORT: 5432
|
||||||
|
POSTGRES_DB: firecrawl
|
||||||
|
POSTGRES_USER: firecrawl
|
||||||
|
POSTGRES_PASSWORD: firecrawl
|
||||||
|
REDIS_URL: redis://redis:6379/1
|
||||||
|
REDIS_RATE_LIMIT_URL: redis://redis:6379/1
|
||||||
|
NUQ_RABBITMQ_URL: amqp://guest:guest@firecrawl-rabbitmq:5672/
|
||||||
|
PLAYWRIGHT_MICROSERVICE_URL: http://firecrawl-playwright:3000/scrape
|
||||||
|
SEARXNG_ENDPOINT: http://searxng:8080
|
||||||
|
USE_DB_AUTHENTICATION: "false"
|
||||||
|
FIRECRAWL_API_KEY: ${FIRECRAWL_API_KEY:-change-me}
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
- firecrawl-postgres
|
||||||
|
- firecrawl-rabbitmq
|
||||||
|
- firecrawl-playwright
|
||||||
|
|
||||||
api:
|
api:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
@@ -39,9 +94,13 @@ services:
|
|||||||
DATABASE_URL: ${DATABASE_URL:-postgresql://llm_in_text:llm_in_text_change_me@postgres:5432/llm_in_text}
|
DATABASE_URL: ${DATABASE_URL:-postgresql://llm_in_text:llm_in_text_change_me@postgres:5432/llm_in_text}
|
||||||
DOCS_BACKEND: postgres
|
DOCS_BACKEND: postgres
|
||||||
JOB_SHARED_TEMP_DIR: /shared-jobs
|
JOB_SHARED_TEMP_DIR: /shared-jobs
|
||||||
|
SEARXNG_BASE_URL: http://searxng:8080
|
||||||
|
FIRECRAWL_BASE_URL: http://firecrawl:3002
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
- redis
|
- redis
|
||||||
|
- searxng
|
||||||
|
- firecrawl
|
||||||
ports:
|
ports:
|
||||||
- "8001:8001"
|
- "8001:8001"
|
||||||
volumes:
|
volumes:
|
||||||
@@ -62,8 +121,12 @@ services:
|
|||||||
DATABASE_URL: ${DATABASE_URL:-postgresql://llm_in_text:llm_in_text_change_me@postgres:5432/llm_in_text}
|
DATABASE_URL: ${DATABASE_URL:-postgresql://llm_in_text:llm_in_text_change_me@postgres:5432/llm_in_text}
|
||||||
DOCS_BACKEND: postgres
|
DOCS_BACKEND: postgres
|
||||||
JOB_SHARED_TEMP_DIR: /shared-jobs
|
JOB_SHARED_TEMP_DIR: /shared-jobs
|
||||||
|
SEARXNG_BASE_URL: http://searxng:8080
|
||||||
|
FIRECRAWL_BASE_URL: http://firecrawl:3002
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
- redis
|
- redis
|
||||||
|
- searxng
|
||||||
|
- firecrawl
|
||||||
volumes:
|
volumes:
|
||||||
- ./docker-data/jobs:/shared-jobs
|
- ./docker-data/jobs:/shared-jobs
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
rank,engine,score,avg_elapsed_ms,avg_results,queries_with_results,query_count,error_count,unresponsive_count,result_engines
|
||||||
|
1,bing,162.5,454.4,8.5,2,2,0,0,"[""bing""]"
|
||||||
|
2,yep,149.4,6559.8,20,2,2,0,0,"[""yep""]"
|
||||||
|
3,360search,140.0,612.9,4,2,2,0,0,"[""360search""]"
|
||||||
|
4,searchmysite,130.71,6429.3,10,2,2,0,0,"[""searchmysite""]"
|
||||||
|
5,startpage,129.21,6128.9,10,2,2,0,0,"[""startpage""]"
|
||||||
|
6,crowdview,122.0,1811.5,20,1,2,0,0,"[""crowdview""]"
|
||||||
|
7,duckduckgo news,120.37,2963.2,15,1,2,0,0,"[""duckduckgo news""]"
|
||||||
|
8,mwmbl,109.85,3615.0,17,1,2,0,0,"[""mwmbl""]"
|
||||||
|
9,openalex,92.51,7499.3,10,2,2,0,0,"[""openalex""]"
|
||||||
|
10,brave,92.0,1757.4,8.5,1,2,0,1,"[""brave""]"
|
||||||
|
11,stackoverflow,91.21,6779.4,5.5,2,2,0,0,"[""stackoverflow""]"
|
||||||
|
12,crossref,86.86,9674.9,19,2,2,0,0,"[""crossref""]"
|
||||||
|
13,reuters,83.71,5279.0,10,1,2,0,0,"[""reuters""]"
|
||||||
|
14,naver news,82.92,5257.9,5,1,2,0,0,"[""naver news""]"
|
||||||
|
15,github,77.74,5876.0,15,1,2,0,0,"[""github""]"
|
||||||
|
16,gitlab,67.0,6850.2,10,1,2,0,0,"[""gitlab""]"
|
||||||
|
17,microsoft learn,63.64,10236.2,9,2,2,0,0,"[""microsoft learn""]"
|
||||||
|
18,docker hub,63.07,5843.1,5,1,2,0,0,"[""docker hub""]"
|
||||||
|
19,hackernews,61.14,7536.4,15,1,2,0,0,"[""hackernews""]"
|
||||||
|
20,bing news,60.0,647.8,0,0,2,0,0,[]
|
||||||
|
21,brave.news,60.0,1949.2,0,0,2,0,0,[]
|
||||||
|
22,askubuntu,59.8,6669.6,5,1,2,0,0,"[""askubuntu""]"
|
||||||
|
23,npm,57.04,7946.2,12.5,1,2,0,0,"[""npm""]"
|
||||||
|
24,mdn,56.1,10389.9,6,2,2,0,0,"[""mdn""]"
|
||||||
|
25,arxiv,54.31,7218.6,5,1,2,0,0,"[""arxiv""]"
|
||||||
|
26,superuser,44.68,7981.8,4,1,2,0,0,"[""superuser""]"
|
||||||
|
27,pkg.go.dev,40.68,8782.4,25,1,2,0,0,"[""pkg.go.dev""]"
|
||||||
|
28,sourcehut,22.05,9645.2,1,1,2,0,0,"[""sourcehut""]"
|
||||||
|
29,startpage news,16.41,6859.0,0,0,2,0,0,[]
|
||||||
|
30,wikipedia,16.29,6871.1,0,0,2,0,0,[]
|
||||||
|
31,pubmed,15.5,9049.8,10,1,2,0,1,"[""pubmed""]"
|
||||||
|
32,pypi,1.03,8397.4,0,0,2,0,0,[]
|
||||||
|
33,semantic scholar,0,6657.8,0,0,2,0,2,[]
|
||||||
|
34,wikidata,0,8565.3,0,0,2,0,2,[]
|
||||||
|
35,lib.rs,0,9375.0,0,0,2,0,2,[]
|
||||||
|
36,qwant,0,10274.0,0,0,2,0,2,[]
|
||||||
|
37,qwant news,0,10529.5,0,0,2,0,2,[]
|
||||||
|
38,mojeek,0,11256.4,0,0,2,0,2,[]
|
||||||
|
39,mojeek news,0,11503.0,0,0,2,0,2,[]
|
||||||
|
40,seznam,0,12367.0,0,0,2,0,2,[]
|
||||||
|
41,wiby,0,15006.0,0,0,2,2,0,[]
|
||||||
|
File diff suppressed because one or more lines are too long
@@ -0,0 +1,50 @@
|
|||||||
|
# SearXNG candidate quality report
|
||||||
|
|
||||||
|
- Generated: 2026-06-09T05:37:25.909553+00:00
|
||||||
|
- Concurrency: 16
|
||||||
|
- Total elapsed: 44077.7 ms
|
||||||
|
- Queries: OpenAI, 人工智能 最新进展
|
||||||
|
|
||||||
|
| Rank | Engine | Score | Avg ms | Avg results | Result queries | Errors | Unresponsive | Result engines |
|
||||||
|
|---:|---|---:|---:|---:|---:|---:|---:|---|
|
||||||
|
| 1 | bing | 162.5 | 454.4 | 8.5 | 2/2 | 0 | 0 | bing |
|
||||||
|
| 2 | yep | 149.4 | 6559.8 | 20 | 2/2 | 0 | 0 | yep |
|
||||||
|
| 3 | 360search | 140.0 | 612.9 | 4 | 2/2 | 0 | 0 | 360search |
|
||||||
|
| 4 | searchmysite | 130.71 | 6429.3 | 10 | 2/2 | 0 | 0 | searchmysite |
|
||||||
|
| 5 | startpage | 129.21 | 6128.9 | 10 | 2/2 | 0 | 0 | startpage |
|
||||||
|
| 6 | crowdview | 122.0 | 1811.5 | 20 | 1/2 | 0 | 0 | crowdview |
|
||||||
|
| 7 | duckduckgo news | 120.37 | 2963.2 | 15 | 1/2 | 0 | 0 | duckduckgo news |
|
||||||
|
| 8 | mwmbl | 109.85 | 3615.0 | 17 | 1/2 | 0 | 0 | mwmbl |
|
||||||
|
| 9 | openalex | 92.51 | 7499.3 | 10 | 2/2 | 0 | 0 | openalex |
|
||||||
|
| 10 | brave | 92.0 | 1757.4 | 8.5 | 1/2 | 0 | 1 | brave |
|
||||||
|
| 11 | stackoverflow | 91.21 | 6779.4 | 5.5 | 2/2 | 0 | 0 | stackoverflow |
|
||||||
|
| 12 | crossref | 86.86 | 9674.9 | 19 | 2/2 | 0 | 0 | crossref |
|
||||||
|
| 13 | reuters | 83.71 | 5279.0 | 10 | 1/2 | 0 | 0 | reuters |
|
||||||
|
| 14 | naver news | 82.92 | 5257.9 | 5 | 1/2 | 0 | 0 | naver news |
|
||||||
|
| 15 | github | 77.74 | 5876.0 | 15 | 1/2 | 0 | 0 | github |
|
||||||
|
| 16 | gitlab | 67.0 | 6850.2 | 10 | 1/2 | 0 | 0 | gitlab |
|
||||||
|
| 17 | microsoft learn | 63.64 | 10236.2 | 9 | 2/2 | 0 | 0 | microsoft learn |
|
||||||
|
| 18 | docker hub | 63.07 | 5843.1 | 5 | 1/2 | 0 | 0 | docker hub |
|
||||||
|
| 19 | hackernews | 61.14 | 7536.4 | 15 | 1/2 | 0 | 0 | hackernews |
|
||||||
|
| 20 | bing news | 60.0 | 647.8 | 0 | 0/2 | 0 | 0 | |
|
||||||
|
| 21 | brave.news | 60.0 | 1949.2 | 0 | 0/2 | 0 | 0 | |
|
||||||
|
| 22 | askubuntu | 59.8 | 6669.6 | 5 | 1/2 | 0 | 0 | askubuntu |
|
||||||
|
| 23 | npm | 57.04 | 7946.2 | 12.5 | 1/2 | 0 | 0 | npm |
|
||||||
|
| 24 | mdn | 56.1 | 10389.9 | 6 | 2/2 | 0 | 0 | mdn |
|
||||||
|
| 25 | arxiv | 54.31 | 7218.6 | 5 | 1/2 | 0 | 0 | arxiv |
|
||||||
|
| 26 | superuser | 44.68 | 7981.8 | 4 | 1/2 | 0 | 0 | superuser |
|
||||||
|
| 27 | pkg.go.dev | 40.68 | 8782.4 | 25 | 1/2 | 0 | 0 | pkg.go.dev |
|
||||||
|
| 28 | sourcehut | 22.05 | 9645.2 | 1 | 1/2 | 0 | 0 | sourcehut |
|
||||||
|
| 29 | startpage news | 16.41 | 6859.0 | 0 | 0/2 | 0 | 0 | |
|
||||||
|
| 30 | wikipedia | 16.29 | 6871.1 | 0 | 0/2 | 0 | 0 | |
|
||||||
|
| 31 | pubmed | 15.5 | 9049.8 | 10 | 1/2 | 0 | 1 | pubmed |
|
||||||
|
| 32 | pypi | 1.03 | 8397.4 | 0 | 0/2 | 0 | 0 | |
|
||||||
|
| 33 | semantic scholar | 0 | 6657.8 | 0 | 0/2 | 0 | 2 | |
|
||||||
|
| 34 | wikidata | 0 | 8565.3 | 0 | 0/2 | 0 | 2 | |
|
||||||
|
| 35 | lib.rs | 0 | 9375.0 | 0 | 0/2 | 0 | 2 | |
|
||||||
|
| 36 | qwant | 0 | 10274.0 | 0 | 0/2 | 0 | 2 | |
|
||||||
|
| 37 | qwant news | 0 | 10529.5 | 0 | 0/2 | 0 | 2 | |
|
||||||
|
| 38 | mojeek | 0 | 11256.4 | 0 | 0/2 | 0 | 2 | |
|
||||||
|
| 39 | mojeek news | 0 | 11503.0 | 0 | 0/2 | 0 | 2 | |
|
||||||
|
| 40 | seznam | 0 | 12367.0 | 0 | 0/2 | 0 | 2 | |
|
||||||
|
| 41 | wiby | 0 | 15006.0 | 0 | 0/2 | 2 | 0 | |
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,255 @@
|
|||||||
|
# SearXNG engine timing report
|
||||||
|
|
||||||
|
- Generated: 2026-06-09T05:21:11.045325+00:00
|
||||||
|
- Query: `OpenAI`
|
||||||
|
- Enabled engines tested: 244
|
||||||
|
- request_timeout: 6.0s
|
||||||
|
- max_request_timeout: 8.0s
|
||||||
|
- videos tab enabled: False
|
||||||
|
|
||||||
|
| Engine | Elapsed ms | Results | Unresponsive | Errors |
|
||||||
|
|---|---:|---:|---|---|
|
||||||
|
| duckduckgo news | 1025.9 | 0 | | |
|
||||||
|
| bt4g | 1026.1 | 0 | | |
|
||||||
|
| openstreetmap | 1026.1 | 0 | | |
|
||||||
|
| wikisource | 1026.3 | 0 | | |
|
||||||
|
| pub.dev | 1027.5 | 0 | | |
|
||||||
|
| swisscows images | 1027.5 | 0 | | |
|
||||||
|
| nixos wiki | 1027.6 | 0 | | |
|
||||||
|
| flickr_api | 1027.9 | 0 | | |
|
||||||
|
| baidu images | 1028.0 | 0 | | |
|
||||||
|
| voidlinux | 1028.1 | 0 | | |
|
||||||
|
| library genesis | 1028.7 | 0 | | |
|
||||||
|
| mwmbl | 1028.7 | 0 | | |
|
||||||
|
| chinaso news | 1029.3 | 0 | | |
|
||||||
|
| semantic scholar | 1029.4 | 0 | | |
|
||||||
|
| uxwing | 1029.4 | 0 | | |
|
||||||
|
| ansa | 1029.5 | 0 | | |
|
||||||
|
| startpage | 1029.6 | 0 | | |
|
||||||
|
| heexy | 1029.7 | 0 | | |
|
||||||
|
| naver | 1029.7 | 0 | | |
|
||||||
|
| askubuntu | 1029.8 | 0 | | |
|
||||||
|
| openalex | 1029.8 | 0 | | |
|
||||||
|
| mymemory translated | 1029.9 | 0 | | |
|
||||||
|
| public domain image archive | 1029.9 | 0 | | |
|
||||||
|
| fynd | 1030.0 | 0 | | |
|
||||||
|
| gentoo | 1030.0 | 0 | | |
|
||||||
|
| reuters | 1030.1 | 0 | | |
|
||||||
|
| heexy images | 1030.2 | 0 | | |
|
||||||
|
| hackernews | 1030.3 | 0 | | |
|
||||||
|
| huggingface | 1030.3 | 0 | | |
|
||||||
|
| mojeek images | 1030.3 | 0 | | |
|
||||||
|
| openairedatasets | 1030.3 | 0 | | |
|
||||||
|
| codeberg | 1030.4 | 0 | | |
|
||||||
|
| goodreads | 1030.4 | 0 | | |
|
||||||
|
| deezer | 1030.5 | 0 | | |
|
||||||
|
| gitea.com | 1030.5 | 0 | | |
|
||||||
|
| lingva | 1030.5 | 0 | | |
|
||||||
|
| flaticon | 1030.6 | 0 | | |
|
||||||
|
| huggingface datasets | 1030.7 | 0 | | |
|
||||||
|
| ebay | 1030.8 | 0 | | |
|
||||||
|
| radio browser | 1030.8 | 0 | | |
|
||||||
|
| artic | 1030.9 | 0 | | |
|
||||||
|
| soundcloud | 1030.9 | 0 | | |
|
||||||
|
| wikivoyage | 1031.0 | 0 | | |
|
||||||
|
| 1337x | 1031.1 | 0 | | |
|
||||||
|
| bandcamp | 1031.1 | 0 | | |
|
||||||
|
| qwant images | 1031.1 | 0 | | |
|
||||||
|
| tagesschau | 1031.1 | 0 | | |
|
||||||
|
| z-library | 1031.2 | 0 | | |
|
||||||
|
| fyyd | 1031.3 | 0 | | |
|
||||||
|
| apple maps | 1031.4 | 0 | | |
|
||||||
|
| tootfinder | 1031.4 | 0 | | |
|
||||||
|
| wikinews | 1031.4 | 0 | | |
|
||||||
|
| superuser | 1031.5 | 0 | | |
|
||||||
|
| etymonline | 1031.6 | 0 | | |
|
||||||
|
| crowdview | 1031.8 | 0 | | |
|
||||||
|
| lobste.rs | 1031.9 | 0 | | |
|
||||||
|
| wikicommons.audio | 1031.9 | 0 | | |
|
||||||
|
| mozhi | 1032.0 | 0 | | |
|
||||||
|
| artstation | 1032.1 | 0 | | |
|
||||||
|
| duckduckgo | 1032.1 | 0 | | |
|
||||||
|
| quark | 1032.1 | 0 | | |
|
||||||
|
| apk mirror | 1032.2 | 0 | | |
|
||||||
|
| genius | 1032.2 | 0 | | |
|
||||||
|
| moviepilot | 1032.2 | 0 | | |
|
||||||
|
| dictzone | 1032.3 | 0 | | |
|
||||||
|
| library of congress | 1032.3 | 0 | | |
|
||||||
|
| naver images | 1032.3 | 0 | | |
|
||||||
|
| packagist | 1032.3 | 0 | | |
|
||||||
|
| gabanza | 1032.4 | 0 | | |
|
||||||
|
| lemmy comments | 1032.4 | 0 | | |
|
||||||
|
| microsoft learn | 1032.4 | 0 | | |
|
||||||
|
| fdroid | 1032.5 | 0 | | |
|
||||||
|
| mojeek news | 1032.5 | 0 | | |
|
||||||
|
| ipernity | 1032.6 | 0 | | |
|
||||||
|
| sogou wechat | 1032.6 | 0 | | |
|
||||||
|
| chefkoch | 1032.7 | 0 | | |
|
||||||
|
| duckduckgo images | 1032.7 | 0 | | |
|
||||||
|
| yep | 1032.7 | 0 | | |
|
||||||
|
| apple app store | 1032.8 | 0 | | |
|
||||||
|
| bitbucket | 1032.8 | 0 | | |
|
||||||
|
| reddit | 1032.8 | 0 | | |
|
||||||
|
| aol images | 1032.9 | 0 | | |
|
||||||
|
| arxiv | 1032.9 | 0 | | |
|
||||||
|
| chinaso images | 1032.9 | 0 | | |
|
||||||
|
| metacpan | 1032.9 | 0 | | |
|
||||||
|
| swisscows | 1032.9 | 0 | | |
|
||||||
|
| bing news | 1033.0 | 0 | | |
|
||||||
|
| libretranslate | 1033.0 | 0 | | |
|
||||||
|
| wikispecies | 1033.0 | 0 | | |
|
||||||
|
| baidu | 1033.1 | 0 | | |
|
||||||
|
| brave | 1033.1 | 0 | | |
|
||||||
|
| quark images | 1033.1 | 0 | | |
|
||||||
|
| sogou images | 1033.1 | 0 | | |
|
||||||
|
| azure | 1033.2 | 0 | | |
|
||||||
|
| braveapi | 1033.2 | 0 | | |
|
||||||
|
| discuss.python | 1033.2 | 0 | | |
|
||||||
|
| springer nature | 1033.2 | 0 | | |
|
||||||
|
| findthatmeme | 1033.3 | 0 | | |
|
||||||
|
| lemmy communities | 1033.3 | 0 | | |
|
||||||
|
| solidtorrents | 1033.3 | 0 | | |
|
||||||
|
| startpage news | 1033.3 | 0 | | |
|
||||||
|
| 1x | 1033.4 | 0 | | |
|
||||||
|
| mdn | 1033.4 | 0 | | |
|
||||||
|
| openrepos | 1033.4 | 0 | | |
|
||||||
|
| sourcehut | 1033.4 | 0 | | |
|
||||||
|
| yandex music | 1033.4 | 0 | | |
|
||||||
|
| cloudflareai | 1033.5 | 0 | | |
|
||||||
|
| gitlab | 1033.5 | 0 | | |
|
||||||
|
| openairepublications | 1033.5 | 0 | | |
|
||||||
|
| yandex images | 1033.5 | 0 | | |
|
||||||
|
| currency | 1033.6 | 0 | | |
|
||||||
|
| tineye | 1033.6 | 0 | | |
|
||||||
|
| docker hub | 1033.7 | 0 | | |
|
||||||
|
| grokipedia | 1033.7 | 0 | | |
|
||||||
|
| wolframalpha | 1033.7 | 0 | | |
|
||||||
|
| openverse | 1033.8 | 0 | | |
|
||||||
|
| woxikon.de synonyme | 1033.8 | 0 | | |
|
||||||
|
| qwant | 1033.9 | 0 | | |
|
||||||
|
| wordnik | 1033.9 | 0 | | |
|
||||||
|
| piratebay | 1034.0 | 0 | | |
|
||||||
|
| lemmy users | 1034.1 | 0 | | |
|
||||||
|
| seekninja | 1034.1 | 0 | | |
|
||||||
|
| startpage images | 1034.1 | 0 | | |
|
||||||
|
| openlibrary | 1034.2 | 0 | | |
|
||||||
|
| wikicommons.files | 1034.2 | 0 | | |
|
||||||
|
| rottentomatoes | 1034.3 | 0 | | |
|
||||||
|
| emojipedia | 1034.4 | 0 | | |
|
||||||
|
| marginalia | 1034.4 | 0 | | |
|
||||||
|
| naver news | 1034.4 | 0 | | |
|
||||||
|
| baidu kaifa | 1034.5 | 0 | | |
|
||||||
|
| elasticsearch | 1034.5 | 0 | | |
|
||||||
|
| gmx | 1034.5 | 0 | | |
|
||||||
|
| mastodon hashtags | 1034.5 | 0 | | |
|
||||||
|
| material icons | 1034.5 | 0 | | |
|
||||||
|
| npm | 1034.5 | 0 | | |
|
||||||
|
| free software directory | 1034.6 | 0 | | |
|
||||||
|
| frinkiac | 1034.7 | 0 | | |
|
||||||
|
| repology | 1034.7 | 0 | | |
|
||||||
|
| wolframalpha_api | 1034.7 | 0 | | |
|
||||||
|
| bing | 1034.8 | 0 | | |
|
||||||
|
| minecraft wiki | 1034.8 | 0 | | |
|
||||||
|
| steam | 1034.8 | 0 | | |
|
||||||
|
| wallhaven | 1034.8 | 0 | | |
|
||||||
|
| wikicommons.images | 1034.8 | 0 | | |
|
||||||
|
| swisscows news | 1034.9 | 0 | | |
|
||||||
|
| wikiversity | 1034.9 | 0 | | |
|
||||||
|
| yacy images | 1034.9 | 0 | | |
|
||||||
|
| flickr | 1035.0 | 0 | | |
|
||||||
|
| lemmy posts | 1035.0 | 0 | | |
|
||||||
|
| pixabay images | 1035.0 | 0 | | |
|
||||||
|
| bing images | 1035.1 | 0 | | |
|
||||||
|
| pypi | 1035.1 | 0 | | |
|
||||||
|
| habrahabr | 1035.2 | 0 | | |
|
||||||
|
| photon | 1035.2 | 0 | | |
|
||||||
|
| zapmeta | 1035.2 | 0 | | |
|
||||||
|
| hoogle | 1035.3 | 0 | | |
|
||||||
|
| lib.rs | 1035.3 | 0 | | |
|
||||||
|
| openclipart | 1035.3 | 0 | | |
|
||||||
|
| btdigg | 1035.4 | 0 | | |
|
||||||
|
| destatis | 1035.4 | 0 | | |
|
||||||
|
| openmeteo | 1035.4 | 0 | | |
|
||||||
|
| pinterest | 1035.4 | 0 | | |
|
||||||
|
| yandex | 1035.4 | 0 | | |
|
||||||
|
| devicons | 1035.5 | 0 | | |
|
||||||
|
| arch linux wiki | 1035.6 | 0 | | |
|
||||||
|
| yacy | 1035.6 | 0 | | |
|
||||||
|
| core.ac.uk | 1035.7 | 0 | | |
|
||||||
|
| deepl | 1035.7 | 0 | | |
|
||||||
|
| pi-hole.community | 1035.8 | 0 | | |
|
||||||
|
| presearch | 1035.8 | 0 | | |
|
||||||
|
| presearch images | 1035.8 | 0 | | |
|
||||||
|
| freesound | 1035.9 | 0 | | |
|
||||||
|
| mankier | 1035.9 | 0 | | |
|
||||||
|
| mastodon users | 1035.9 | 0 | | |
|
||||||
|
| pdbe | 1035.9 | 0 | | |
|
||||||
|
| torch | 1035.9 | 0 | | |
|
||||||
|
| ddg definitions | 1036.0 | 0 | | |
|
||||||
|
| hex | 1036.0 | 0 | | |
|
||||||
|
| il post | 1036.0 | 0 | | |
|
||||||
|
| sepiasearch | 1036.0 | 0 | | |
|
||||||
|
| wikiquote | 1036.0 | 0 | | |
|
||||||
|
| imgur | 1036.1 | 0 | | |
|
||||||
|
| wikipedia | 1036.1 | 0 | | |
|
||||||
|
| bpb | 1036.2 | 0 | | |
|
||||||
|
| adobe stock audio | 1036.3 | 0 | | |
|
||||||
|
| geizhals | 1036.3 | 0 | | |
|
||||||
|
| ollama | 1036.3 | 0 | | |
|
||||||
|
| adobe stock | 1036.4 | 0 | | |
|
||||||
|
| pkg.go.dev | 1036.4 | 0 | | |
|
||||||
|
| ina | 1036.5 | 0 | | |
|
||||||
|
| wikidata | 1036.5 | 0 | | |
|
||||||
|
| wikimini | 1036.5 | 0 | | |
|
||||||
|
| 360search | 1036.6 | 0 | | |
|
||||||
|
| astrophysics data system | 1036.6 | 0 | | |
|
||||||
|
| brave.news | 1036.6 | 0 | | |
|
||||||
|
| duden | 1036.7 | 0 | | |
|
||||||
|
| sogou | 1036.7 | 0 | | |
|
||||||
|
| wikibooks | 1036.7 | 0 | | |
|
||||||
|
| Torznab EZTV | 1036.8 | 0 | | |
|
||||||
|
| ahmia | 1036.8 | 0 | | |
|
||||||
|
| aol | 1036.8 | 0 | | |
|
||||||
|
| mojeek | 1036.8 | 0 | | |
|
||||||
|
| encyclosearch | 1036.9 | 0 | | |
|
||||||
|
| jisho | 1037.1 | 0 | | |
|
||||||
|
| searchmysite | 1037.1 | 0 | | |
|
||||||
|
| annas archive | 1037.2 | 0 | | |
|
||||||
|
| pexels | 1037.2 | 0 | | |
|
||||||
|
| wttr.in | 1037.2 | 0 | | |
|
||||||
|
| imdb | 1037.3 | 0 | | |
|
||||||
|
| crates.io | 1037.4 | 0 | | |
|
||||||
|
| deviantart | 1037.4 | 0 | | |
|
||||||
|
| qwant news | 1037.4 | 0 | | |
|
||||||
|
| crossref | 1037.5 | 0 | | |
|
||||||
|
| stackoverflow | 1037.6 | 0 | | |
|
||||||
|
| 500px | 1037.7 | 0 | | |
|
||||||
|
| mixcloud | 1037.7 | 0 | | |
|
||||||
|
| presearch news | 1037.7 | 0 | | |
|
||||||
|
| lucide | 1037.8 | 0 | | |
|
||||||
|
| boardreader | 1038.0 | 0 | | |
|
||||||
|
| seznam | 1038.0 | 0 | | |
|
||||||
|
| tokyotoshokan | 1038.0 | 0 | | |
|
||||||
|
| 9gag | 1038.1 | 0 | | |
|
||||||
|
| github code | 1038.3 | 0 | | |
|
||||||
|
| wiby | 1038.3 | 0 | | |
|
||||||
|
| pixiv | 1038.8 | 0 | | |
|
||||||
|
| selfhst icons | 1038.9 | 0 | | |
|
||||||
|
| huggingface spaces | 1039.1 | 0 | | |
|
||||||
|
| unsplash | 1039.3 | 0 | | |
|
||||||
|
| national vulnerability database | 1039.6 | 0 | | |
|
||||||
|
| brave.images | 1040.5 | 0 | | |
|
||||||
|
| erowid | 1040.6 | 0 | | |
|
||||||
|
| alpine linux packages | 1044.1 | 0 | | |
|
||||||
|
| senscritique | 1072.9 | 0 | | |
|
||||||
|
| pubmed | 1073.0 | 0 | | |
|
||||||
|
| wiktionary | 1073.0 | 0 | | |
|
||||||
|
| caddy.community | 1073.3 | 0 | | |
|
||||||
|
| cara | 1076.7 | 0 | | |
|
||||||
|
| anaconda | 1077.1 | 0 | | |
|
||||||
|
| kickass | 1077.1 | 0 | | |
|
||||||
|
| duckduckgo weather | 1077.2 | 0 | | |
|
||||||
|
| rubygems | 1077.4 | 0 | | |
|
||||||
|
| nyaa | 1077.5 | 0 | | |
|
||||||
|
| github | 1077.7 | 0 | | |
|
||||||
|
| cachy os packages | 1080.3 | 0 | | |
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,255 @@
|
|||||||
|
# SearXNG engine timing report
|
||||||
|
|
||||||
|
- Generated: 2026-06-09T05:24:36.248525+00:00
|
||||||
|
- Query: `OpenAI`
|
||||||
|
- Enabled engines tested: 244
|
||||||
|
- request_timeout: 6.0s
|
||||||
|
- max_request_timeout: 8.0s
|
||||||
|
- videos tab enabled: False
|
||||||
|
|
||||||
|
| Engine | Elapsed ms | Results | Unresponsive | Errors |
|
||||||
|
|---|---:|---:|---|---|
|
||||||
|
| gmx | 223.3 | 0 | ['gmx', 'Suspended: timeout'] | |
|
||||||
|
| brave | 432.9 | 0 | ['brave', 'Suspended: too many requests'] | |
|
||||||
|
| adobe stock audio | 472.4 | 0 | ['adobe stock audio', 'Suspended: access denied'] | |
|
||||||
|
| currency | 499.5 | 0 | | |
|
||||||
|
| sogou images | 646.9 | 48 | | |
|
||||||
|
| yacy | 666.8 | 0 | ['yacy', 'Suspended: timeout'] | |
|
||||||
|
| wikidata | 779.4 | 0 | ['wikidata', 'Suspended: timeout'] | |
|
||||||
|
| dictzone | 842.3 | 0 | | |
|
||||||
|
| yandex | 899.6 | 0 | ['yandex', 'Suspended: HTTP error'] | |
|
||||||
|
| bandcamp | 916.2 | 0 | | |
|
||||||
|
| unsplash | 1111.3 | 0 | ['unsplash', 'parsing error'] | |
|
||||||
|
| chefkoch | 1164.0 | 0 | | |
|
||||||
|
| rubygems | 1178.3 | 30 | | |
|
||||||
|
| baidu images | 1185.7 | 10 | | |
|
||||||
|
| bing | 1208.1 | 0 | ['bing', 'Suspended: HTTP connection error'] | |
|
||||||
|
| mozhi | 1284.4 | 0 | | |
|
||||||
|
| yacy images | 1324.7 | 0 | ['yacy images', 'Suspended: timeout'] | |
|
||||||
|
| qwant | 1339.0 | 0 | ['qwant', 'Suspended: timeout'] | |
|
||||||
|
| wikiquote | 1355.2 | 0 | ['wikiquote', 'Suspended: timeout'] | |
|
||||||
|
| moviepilot | 1387.3 | 0 | | |
|
||||||
|
| yandex images | 1390.4 | 0 | ['yandex images', 'Suspended: HTTP error'] | |
|
||||||
|
| brave.images | 1392.0 | 0 | ['brave.images', 'Suspended: too many requests'] | |
|
||||||
|
| mdn | 1451.7 | 10 | | |
|
||||||
|
| duckduckgo | 1555.5 | 0 | ['duckduckgo', 'CAPTCHA'] | |
|
||||||
|
| mastodon users | 1576.4 | 40 | | |
|
||||||
|
| photon | 1580.5 | 10 | | |
|
||||||
|
| lingva | 1599.4 | 0 | | |
|
||||||
|
| searchmysite | 1600.2 | 10 | | |
|
||||||
|
| mixcloud | 1744.6 | 0 | ['mixcloud', 'HTTP connection error'] | |
|
||||||
|
| quark | 1759.7 | 0 | ['quark', 'Suspended: CAPTCHA'] | |
|
||||||
|
| senscritique | 1809.2 | 16 | | |
|
||||||
|
| lemmy users | 1841.1 | 0 | ['lemmy users', 'Suspended: timeout'] | |
|
||||||
|
| mymemory translated | 1881.8 | 0 | | |
|
||||||
|
| arxiv | 1909.9 | 10 | | |
|
||||||
|
| pdbe | 1944.0 | 0 | | |
|
||||||
|
| pub.dev | 1953.6 | 10 | | |
|
||||||
|
| qwant images | 1956.7 | 0 | ['qwant images', 'Suspended: timeout'] | |
|
||||||
|
| wikipedia | 1975.9 | 0 | | |
|
||||||
|
| pypi | 1997.1 | 0 | | |
|
||||||
|
| yep | 2011.6 | 20 | | |
|
||||||
|
| destatis | 2051.3 | 0 | | |
|
||||||
|
| mojeek | 2067.4 | 0 | ['mojeek', 'Suspended: access denied'] | |
|
||||||
|
| fyyd | 2068.9 | 10 | | |
|
||||||
|
| bpb | 2086.7 | 15 | | |
|
||||||
|
| imdb | 2088.3 | 7 | | |
|
||||||
|
| pinterest | 2090.7 | 18 | | |
|
||||||
|
| wikicommons.images | 2100.3 | 10 | | |
|
||||||
|
| docker hub | 2104.9 | 10 | | |
|
||||||
|
| lucide | 2107.4 | 0 | | |
|
||||||
|
| tineye | 2109.2 | 0 | | |
|
||||||
|
| bing news | 2158.4 | 0 | | |
|
||||||
|
| steam | 2168.7 | 3 | | |
|
||||||
|
| gitlab | 2173.7 | 20 | | |
|
||||||
|
| uxwing | 2201.2 | 0 | ['uxwing', 'access denied'] | |
|
||||||
|
| crowdview | 2250.3 | 40 | | |
|
||||||
|
| ddg definitions | 2294.1 | 2 | | |
|
||||||
|
| superuser | 2312.2 | 8 | | |
|
||||||
|
| discuss.python | 2322.2 | 50 | | |
|
||||||
|
| aol | 2404.7 | 10 | | |
|
||||||
|
| selfhst icons | 2422.6 | 1 | | |
|
||||||
|
| naver | 2425.1 | 0 | | |
|
||||||
|
| openlibrary | 2427.1 | 0 | ['openlibrary', 'Suspended: timeout'] | |
|
||||||
|
| 500px | 2431.9 | 0 | ['500px', 'HTTP connection error'] | |
|
||||||
|
| naver news | 2451.5 | 10 | | |
|
||||||
|
| 9gag | 2474.7 | 0 | ['9gag', 'access denied'] | |
|
||||||
|
| wikispecies | 2476.0 | 5 | | |
|
||||||
|
| fynd | 2495.8 | 10 | | |
|
||||||
|
| sogou wechat | 2499.9 | 10 | | |
|
||||||
|
| mwmbl | 2519.2 | 34 | | |
|
||||||
|
| deezer | 2546.0 | 25 | | |
|
||||||
|
| bing images | 2550.4 | 0 | | |
|
||||||
|
| pixabay images | 2570.1 | 0 | ['pixabay images', 'parsing error'] | |
|
||||||
|
| huggingface spaces | 2584.2 | 1000 | | |
|
||||||
|
| imgur | 2586.0 | 39 | | |
|
||||||
|
| lemmy comments | 2593.3 | 0 | ['lemmy comments', 'Suspended: timeout'] | |
|
||||||
|
| baidu | 2597.9 | 10 | | |
|
||||||
|
| wikivoyage | 2634.2 | 0 | ['wikivoyage', 'Suspended: timeout'] | |
|
||||||
|
| hackernews | 2641.1 | 30 | | |
|
||||||
|
| radio browser | 2660.1 | 2 | | |
|
||||||
|
| wikicommons.audio | 2660.3 | 10 | | |
|
||||||
|
| jisho | 2675.7 | 1 | | |
|
||||||
|
| geizhals | 2692.5 | 0 | ['geizhals', 'access denied'] | |
|
||||||
|
| presearch images | 2705.2 | 100 | | |
|
||||||
|
| goodreads | 2718.2 | 0 | ['goodreads', 'parsing error'] | |
|
||||||
|
| huggingface datasets | 2741.9 | 736 | | |
|
||||||
|
| reddit | 2786.3 | 0 | ['reddit', 'access denied'] | |
|
||||||
|
| wikimini | 2837.7 | 0 | ['wikimini', 'Suspended: timeout'] | |
|
||||||
|
| semantic scholar | 2843.2 | 0 | ['semantic scholar', 'access denied'] | |
|
||||||
|
| askubuntu | 2859.7 | 10 | | |
|
||||||
|
| swisscows images | 2860.2 | 230 | ['bing', 'Suspended: HTTP connection error'], ['brave', 'Suspended: too many requests'], ['duckduckgo', 'CAPTCHA'], ['gmx', 'Suspended: timeout'], ['mojeek', 'Suspended: access den | |
|
||||||
|
| mojeek images | 2883.6 | 0 | ['mojeek images', 'access denied'] | |
|
||||||
|
| boardreader | 2914.1 | 10 | | |
|
||||||
|
| 360search | 2914.6 | 0 | ['360search', 'Suspended: timeout'] | |
|
||||||
|
| microsoft learn | 2989.8 | 10 | | |
|
||||||
|
| sogou | 2995.6 | 0 | ['sogou', 'Suspended: CAPTCHA'] | |
|
||||||
|
| presearch news | 3024.0 | 12 | | |
|
||||||
|
| rottentomatoes | 3034.3 | 20 | | |
|
||||||
|
| seznam | 3034.7 | 0 | ['seznam', 'Suspended: timeout'] | |
|
||||||
|
| flickr | 3062.2 | 25 | | |
|
||||||
|
| emojipedia | 3073.0 | 0 | ['emojipedia', 'access denied'] | |
|
||||||
|
| bt4g | 3076.5 | 0 | ['bt4g', 'HTTP connection error'] | |
|
||||||
|
| devicons | 3090.0 | 0 | | |
|
||||||
|
| braveapi | 3094.0 | 191 | ['360search', 'Suspended: timeout'], ['aol', 'Suspended: HTTP error'], ['baidu', 'Suspended: CAPTCHA'], ['brave', 'Suspended: too many requests'], ['duckduckgo', 'CAPTCHA'], ['gmx' | |
|
||||||
|
| lobste.rs | 3102.7 | 20 | | |
|
||||||
|
| mankier | 3121.6 | 0 | | |
|
||||||
|
| baidu kaifa | 3149.4 | 10 | | |
|
||||||
|
| github | 3195.6 | 30 | | |
|
||||||
|
| adobe stock | 3205.4 | 0 | ['adobe stock', 'access denied'] | |
|
||||||
|
| findthatmeme | 3208.5 | 50 | | |
|
||||||
|
| flaticon | 3210.0 | 1 | | |
|
||||||
|
| gabanza | 3226.9 | 30 | | |
|
||||||
|
| pi-hole.community | 3231.0 | 4 | | |
|
||||||
|
| cachy os packages | 3246.0 | 10 | | |
|
||||||
|
| artic | 3262.9 | 20 | | |
|
||||||
|
| azure | 3337.2 | 191 | ['360search', 'Suspended: timeout'], ['aol', 'Suspended: HTTP error'], ['baidu', 'Suspended: CAPTCHA'], ['brave', 'Suspended: too many requests'], ['duckduckgo', 'CAPTCHA'], ['gmx' | |
|
||||||
|
| wolframalpha | 3347.7 | 0 | ['wolframalpha', 'timeout'] | |
|
||||||
|
| 1337x | 3362.4 | 0 | ['1337x', 'access denied'] | |
|
||||||
|
| btdigg | 3386.7 | 0 | ['btdigg', 'too many requests'] | |
|
||||||
|
| minecraft wiki | 3400.6 | 5 | | |
|
||||||
|
| heexy | 3404.7 | 191 | ['360search', 'Suspended: timeout'], ['aol', 'Suspended: HTTP error'], ['baidu', 'Suspended: CAPTCHA'], ['brave', 'Suspended: too many requests'], ['duckduckgo', 'CAPTCHA'], ['gmx' | |
|
||||||
|
| caddy.community | 3440.0 | 4 | | |
|
||||||
|
| zapmeta | 3457.0 | 0 | ['zapmeta', 'Suspended: access denied'] | |
|
||||||
|
| seekninja | 3465.1 | 229 | ['bing', 'Suspended: HTTP connection error'], ['brave', 'Suspended: too many requests'], ['duckduckgo', 'CAPTCHA'], ['gmx', 'Suspended: timeout'], ['mojeek', 'Suspended: access den | |
|
||||||
|
| annas archive | 3518.6 | 0 | ['annas archive', 'HTTP connection error'] | |
|
||||||
|
| duckduckgo images | 3525.0 | 95 | | |
|
||||||
|
| pkg.go.dev | 3575.8 | 50 | | |
|
||||||
|
| sepiasearch | 3617.9 | 10 | | |
|
||||||
|
| quark images | 3622.2 | 10 | | |
|
||||||
|
| hex | 3652.9 | 10 | | |
|
||||||
|
| il post | 3697.3 | 10 | | |
|
||||||
|
| nixos wiki | 3720.5 | 1 | | |
|
||||||
|
| brave.news | 3731.7 | 0 | | |
|
||||||
|
| huggingface | 3741.2 | 1000 | | |
|
||||||
|
| wikicommons.files | 3744.6 | 10 | | |
|
||||||
|
| ansa | 3770.4 | 12 | | |
|
||||||
|
| hoogle | 3784.8 | 25 | | |
|
||||||
|
| crates.io | 3789.5 | 10 | | |
|
||||||
|
| piratebay | 3849.7 | 35 | | |
|
||||||
|
| anaconda | 3862.4 | 0 | | |
|
||||||
|
| pexels | 3879.9 | 20 | | |
|
||||||
|
| material icons | 3893.7 | 0 | | |
|
||||||
|
| tagesschau | 3894.2 | 0 | ['tagesschau', 'Suspended: HTTP connection error'] | |
|
||||||
|
| wttr.in | 3899.2 | 0 | ['wttr.in', 'parsing error'] | |
|
||||||
|
| national vulnerability database | 3900.7 | 10 | | |
|
||||||
|
| naver images | 3932.4 | 0 | | |
|
||||||
|
| deviantart | 3998.8 | 0 | | |
|
||||||
|
| ollama | 4042.6 | 20 | | |
|
||||||
|
| encyclosearch | 4056.8 | 15 | | |
|
||||||
|
| lib.rs | 4128.2 | 0 | ['lib.rs', 'access denied'] | |
|
||||||
|
| deepl | 4132.6 | 191 | ['360search', 'Suspended: timeout'], ['aol', 'Suspended: HTTP error'], ['baidu', 'Suspended: CAPTCHA'], ['brave', 'Suspended: too many requests'], ['duckduckgo', 'CAPTCHA'], ['gmx' | |
|
||||||
|
| openmeteo | 4135.8 | 0 | | |
|
||||||
|
| sourcehut | 4147.8 | 2 | | |
|
||||||
|
| openrepos | 4203.1 | 2 | | |
|
||||||
|
| lemmy posts | 4218.7 | 0 | ['lemmy posts', 'Suspended: timeout'] | |
|
||||||
|
| repology | 4226.1 | 236 | ['bing', 'Suspended: HTTP connection error'], ['brave', 'Suspended: too many requests'], ['duckduckgo', 'CAPTCHA'], ['gmx', 'Suspended: timeout'], ['mojeek', 'Suspended: access den | |
|
||||||
|
| nyaa | 4237.6 | 0 | | |
|
||||||
|
| packagist | 4242.1 | 15 | | |
|
||||||
|
| metacpan | 4261.6 | 0 | ['metacpan', 'HTTP error'] | |
|
||||||
|
| duckduckgo news | 4274.4 | 30 | | |
|
||||||
|
| presearch | 4327.9 | 14 | | |
|
||||||
|
| free software directory | 4395.2 | 0 | | |
|
||||||
|
| gentoo | 4425.4 | 0 | | |
|
||||||
|
| heexy images | 4442.7 | 191 | ['360search', 'Suspended: timeout'], ['aol', 'Suspended: HTTP error'], ['baidu', 'Suspended: CAPTCHA'], ['brave', 'Suspended: too many requests'], ['duckduckgo', 'CAPTCHA'], ['gmx' | |
|
||||||
|
| swisscows | 4449.4 | 230 | ['bing', 'Suspended: HTTP connection error'], ['brave', 'Suspended: too many requests'], ['duckduckgo', 'CAPTCHA'], ['gmx', 'Suspended: timeout'], ['mojeek', 'Suspended: access den | |
|
||||||
|
| mastodon hashtags | 4489.6 | 40 | | |
|
||||||
|
| torch | 4490.0 | 191 | ['360search', 'Suspended: timeout'], ['aol', 'Suspended: HTTP error'], ['baidu', 'Suspended: CAPTCHA'], ['brave', 'Suspended: too many requests'], ['duckduckgo', 'CAPTCHA'], ['gmx' | |
|
||||||
|
| crossref | 4506.0 | 18 | | |
|
||||||
|
| artstation | 4655.0 | 20 | | |
|
||||||
|
| frinkiac | 4668.4 | 0 | | |
|
||||||
|
| erowid | 4713.1 | 0 | | |
|
||||||
|
| woxikon.de synonyme | 4721.1 | 0 | ['woxikon.de synonyme', 'access denied'] | |
|
||||||
|
| apple app store | 4746.2 | 39 | | |
|
||||||
|
| springer nature | 4768.7 | 229 | ['bing', 'Suspended: HTTP connection error'], ['brave', 'Suspended: too many requests'], ['duckduckgo', 'CAPTCHA'], ['gmx', 'Suspended: timeout'], ['mojeek', 'Suspended: access den | |
|
||||||
|
| startpage | 4790.7 | 10 | | |
|
||||||
|
| soundcloud | 4814.0 | 9 | | |
|
||||||
|
| startpage news | 4817.2 | 0 | | |
|
||||||
|
| pubmed | 4827.6 | 20 | | |
|
||||||
|
| reuters | 4947.2 | 20 | | |
|
||||||
|
| wordnik | 4970.4 | 0 | | |
|
||||||
|
| apk mirror | 5026.0 | 10 | | |
|
||||||
|
| habrahabr | 5148.4 | 0 | | |
|
||||||
|
| github code | 5502.6 | 225 | ['bing', 'Suspended: HTTP connection error'], ['brave', 'Suspended: too many requests'], ['duckduckgo', 'CAPTCHA'], ['gmx', 'Suspended: timeout'], ['mojeek', 'Suspended: access den | |
|
||||||
|
| stackoverflow | 5504.1 | 10 | | |
|
||||||
|
| apple maps | 5522.6 | 0 | ['apple maps', 'HTTP error'] | |
|
||||||
|
| libretranslate | 5534.1 | 191 | ['360search', 'Suspended: timeout'], ['aol', 'Suspended: HTTP error'], ['baidu', 'Suspended: CAPTCHA'], ['brave', 'Suspended: too many requests'], ['duckduckgo', 'CAPTCHA'], ['gmx' | |
|
||||||
|
| Torznab EZTV | 5610.6 | 191 | ['360search', 'Suspended: timeout'], ['aol', 'Suspended: HTTP error'], ['baidu', 'Suspended: CAPTCHA'], ['brave', 'Suspended: too many requests'], ['duckduckgo', 'CAPTCHA'], ['gmx' | |
|
||||||
|
| core.ac.uk | 5651.0 | 191 | ['360search', 'Suspended: timeout'], ['aol', 'Suspended: HTTP error'], ['baidu', 'Suspended: CAPTCHA'], ['brave', 'Suspended: too many requests'], ['duckduckgo', 'CAPTCHA'], ['gmx' | |
|
||||||
|
| mojeek news | 5679.8 | 0 | ['mojeek news', 'access denied'] | |
|
||||||
|
| npm | 5705.4 | 25 | | |
|
||||||
|
| duden | 5822.9 | 1 | | |
|
||||||
|
| library of congress | 5912.4 | 0 | ['library of congress', 'parsing error'] | |
|
||||||
|
| aol images | 6040.5 | 0 | ['aol images', 'HTTP error'] | |
|
||||||
|
| voidlinux | 6242.5 | 1 | | |
|
||||||
|
| alpine linux packages | 6474.4 | 0 | | |
|
||||||
|
| marginalia | 6480.6 | 192 | ['360search', 'Suspended: timeout'], ['aol', 'Suspended: HTTP error'], ['baidu', 'Suspended: CAPTCHA'], ['brave', 'Suspended: too many requests'], ['duckduckgo', 'CAPTCHA'], ['gmx' | |
|
||||||
|
| ipernity | 6577.7 | 0 | ['ipernity', 'timeout'] | |
|
||||||
|
| gitea.com | 6647.2 | 10 | | |
|
||||||
|
| openstreetmap | 6784.4 | 2 | | |
|
||||||
|
| wikisource | 6810.9 | 0 | ['wikisource', 'timeout'] | |
|
||||||
|
| yandex music | 6882.8 | 0 | ['yandex music', 'HTTP error'] | |
|
||||||
|
| cara | 6951.4 | 24 | | |
|
||||||
|
| 1x | 6962.7 | 0 | | |
|
||||||
|
| startpage images | 7363.1 | 49 | | |
|
||||||
|
| duckduckgo weather | 7374.6 | 0 | ['duckduckgo weather', 'timeout'] | |
|
||||||
|
| wiby | 7413.0 | 0 | ['wiby', 'timeout'] | |
|
||||||
|
| swisscows news | 7434.2 | 230 | ['bing', 'Suspended: HTTP connection error'], ['brave', 'Suspended: too many requests'], ['duckduckgo', 'CAPTCHA'], ['gmx', 'Suspended: timeout'], ['mojeek', 'Suspended: access den | |
|
||||||
|
| wiktionary | 7442.0 | 0 | ['wiktionary', 'timeout'] | |
|
||||||
|
| genius | 7490.2 | 0 | ['genius', 'access denied'] | |
|
||||||
|
| wikibooks | 7744.2 | 0 | ['wikibooks', 'timeout'] | |
|
||||||
|
| openalex | 7759.0 | 10 | | |
|
||||||
|
| fdroid | 7872.9 | 0 | ['fdroid', 'timeout'] | |
|
||||||
|
| etymonline | 7893.8 | 0 | | |
|
||||||
|
| wikiversity | 8016.9 | 0 | ['wikiversity', 'Suspended: timeout'] | |
|
||||||
|
| flickr_api | 8020.6 | 281 | ['duckduckgo', 'CAPTCHA'], ['gmx', 'timeout'], ['openlibrary', 'timeout'], ['qwant', 'timeout'], ['seznam', 'timeout'], ['tagesschau', 'HTTP connection error'], ['wiby', 'timeout'] | |
|
||||||
|
| codeberg | 8024.5 | 0 | ['codeberg', 'timeout'] | |
|
||||||
|
| bitbucket | 8186.1 | 0 | | |
|
||||||
|
| openclipart | 8229.3 | 230 | ['brave', 'Suspended: too many requests'], ['duckduckgo', 'CAPTCHA'], ['gmx', 'Suspended: timeout'], ['mojeek', 'Suspended: access denied'], ['openlibrary', 'Suspended: timeout'], | |
|
||||||
|
| public domain image archive | 8248.5 | 4 | | |
|
||||||
|
| wikinews | 8255.1 | 0 | ['wikinews', 'timeout'] | |
|
||||||
|
| wallhaven | 8320.1 | 211 | ['360search', 'Suspended: timeout'], ['baidu', 'Suspended: CAPTCHA'], ['brave', 'Suspended: too many requests'], ['duckduckgo', 'CAPTCHA'], ['gmx', 'Suspended: timeout'], ['mojeek' | |
|
||||||
|
| lemmy communities | 8444.4 | 0 | ['lemmy communities', 'timeout'] | |
|
||||||
|
| freesound | 8760.3 | 263 | ['bing', 'HTTP connection error'], ['duckduckgo', 'CAPTCHA'], ['gmx', 'Suspended: timeout'], ['mojeek', 'access denied'], ['openlibrary', 'Suspended: timeout'], ['quark', 'CAPTCHA' | |
|
||||||
|
| openverse | 8833.7 | 0 | ['openverse', 'timeout'] | |
|
||||||
|
| tokyotoshokan | 9010.8 | 0 | ['tokyotoshokan', 'timeout'] | |
|
||||||
|
| astrophysics data system | 9129.3 | 246 | ['bing', 'Suspended: HTTP connection error'], ['brave', 'too many requests'], ['duckduckgo', 'CAPTCHA'], ['gmx', 'Suspended: timeout'], ['mojeek', 'Suspended: access denied'], ['op | |
|
||||||
|
| library genesis | 9175.3 | 0 | ['library genesis', 'timeout'] | |
|
||||||
|
| qwant news | 9213.3 | 0 | ['qwant news', 'timeout'] | |
|
||||||
|
| tootfinder | 9497.9 | 0 | ['tootfinder', 'timeout'] | |
|
||||||
|
| kickass | 9569.1 | 0 | ['kickass', 'timeout'] | |
|
||||||
|
| cloudflareai | 9714.5 | 201 | ['360search', 'Suspended: timeout'], ['aol', 'HTTP error'], ['baidu', 'Suspended: CAPTCHA'], ['brave', 'Suspended: too many requests'], ['duckduckgo', 'CAPTCHA'], ['gmx', 'Suspende | |
|
||||||
|
| chinaso news | 9739.6 | 230 | ['360search', 'timeout'], ['brave', 'Suspended: too many requests'], ['duckduckgo', 'CAPTCHA'], ['gmx', 'Suspended: timeout'], ['mojeek', 'Suspended: access denied'], ['openlibrary | |
|
||||||
|
| ina | 9839.0 | 0 | ['ina', 'timeout'] | |
|
||||||
|
| arch linux wiki | 9925.3 | 0 | | |
|
||||||
|
| grokipedia | 9985.0 | 211 | ['360search', 'Suspended: timeout'], ['baidu', 'CAPTCHA'], ['brave', 'Suspended: too many requests'], ['duckduckgo', 'CAPTCHA'], ['gmx', 'Suspended: timeout'], ['mojeek', 'Suspende | |
|
||||||
|
| chinaso images | 9985.5 | 211 | ['360search', 'Suspended: timeout'], ['baidu', 'CAPTCHA'], ['brave', 'Suspended: too many requests'], ['duckduckgo', 'CAPTCHA'], ['gmx', 'Suspended: timeout'], ['mojeek', 'Suspende | |
|
||||||
|
| elasticsearch | 10129.5 | 230 | ['bing', 'Suspended: HTTP connection error'], ['brave', 'Suspended: too many requests'], ['duckduckgo', 'CAPTCHA'], ['gmx', 'Suspended: timeout'], ['mojeek', 'Suspended: access den | |
|
||||||
|
| pixiv | 10197.7 | 263 | ['bing', 'Suspended: HTTP connection error'], ['duckduckgo', 'CAPTCHA'], ['gmx', 'Suspended: timeout'], ['mojeek', 'Suspended: access denied'], ['openlibrary', 'Suspended: timeout' | |
|
||||||
|
| openairedatasets | 10220.6 | 0 | ['openairedatasets', 'timeout'] | |
|
||||||
|
| ahmia | 10599.8 | 225 | ['brave', 'Suspended: too many requests'], ['duckduckgo', 'CAPTCHA'], ['gmx', 'timeout'], ['mojeek', 'Suspended: access denied'], ['openlibrary', 'timeout'], ['presearch', 'Suspend | |
|
||||||
|
| z-library | 10885.9 | 225 | ['brave', 'Suspended: too many requests'], ['duckduckgo', 'CAPTCHA'], ['gmx', 'Suspended: timeout'], ['mojeek', 'Suspended: access denied'], ['openlibrary', 'Suspended: timeout'], | |
|
||||||
|
| wolframalpha_api | 10996.9 | 230 | ['brave', 'Suspended: too many requests'], ['duckduckgo', 'CAPTCHA'], ['gmx', 'timeout'], ['mojeek', 'Suspended: access denied'], ['openlibrary', 'timeout'], ['presearch', 'Suspend | |
|
||||||
|
| openairepublications | 11178.7 | 0 | ['openairepublications', 'timeout'] | |
|
||||||
|
| solidtorrents | 11261.0 | 0 | ['solidtorrents', 'timeout'] | |
|
||||||
|
| ebay | 12007.6 | 0 | | ReadTimeout: |
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,27 @@
|
|||||||
|
# SearXNG final engine timing report
|
||||||
|
|
||||||
|
- Generated: 2026-06-09T05:29:38.995723+00:00
|
||||||
|
- Diagnostic query: `OpenAI`
|
||||||
|
- Enabled engines tested: 9
|
||||||
|
- request_timeout: 6.0s
|
||||||
|
- max_request_timeout: 8.0s
|
||||||
|
- tabs: general, news
|
||||||
|
|
||||||
|
| Engine | Shortcut | Elapsed ms | Results | Result engines | Unresponsive | Errors |
|
||||||
|
|---|---|---:|---:|---|---|---|
|
||||||
|
| 360search | 360so | 572.9 | 5 | 360search | [] | |
|
||||||
|
| crowdview | cv | 970.4 | 40 | crowdview | [] | |
|
||||||
|
| yep | yep | 1236.1 | 20 | yep | [] | |
|
||||||
|
| duckduckgo news | ddn | 1251.7 | 30 | duckduckgo news | [] | |
|
||||||
|
| naver news | nvrn | 1781.1 | 10 | naver news | [] | |
|
||||||
|
| searchmysite | sms | 2062.9 | 10 | searchmysite | [] | |
|
||||||
|
| mwmbl | mwm | 2474.9 | 34 | mwmbl | [] | |
|
||||||
|
| reuters | reu | 3044.4 | 20 | reuters | [] | |
|
||||||
|
| startpage | sp | 3639.6 | 10 | startpage | [] | |
|
||||||
|
|
||||||
|
## Default Search Export Summary
|
||||||
|
- Query: `OpenAI`
|
||||||
|
- Elapsed: 1351.8 ms
|
||||||
|
- Results: 118
|
||||||
|
- Result engines: 360search, crowdview, mwmbl, searchmysite, startpage, yep
|
||||||
|
- Unresponsive: []
|
||||||
@@ -0,0 +1,245 @@
|
|||||||
|
engine,shortcut,backend,categories,configured_timeout,elapsed_ms,status_code,ok,result_count,unresponsive_engines,errors
|
||||||
|
duckduckgo news,ddn,duckduckgo_extra,"[""news""]",,1025.9,503,False,0,[],
|
||||||
|
bt4g,bt4g,bt4g,null,,1026.1,503,False,0,[],
|
||||||
|
openstreetmap,osm,openstreetmap,null,,1026.1,503,False,0,[],
|
||||||
|
wikisource,ws,mediawiki,"[""general"", ""wikimedia""]",,1026.3,503,False,0,[],
|
||||||
|
pub.dev,pd,xpath,"[""packages"", ""it""]",8.0,1027.5,503,False,0,[],
|
||||||
|
swisscows images,swi,swisscows,"""images""",,1027.5,503,False,0,[],
|
||||||
|
nixos wiki,nixw,mediawiki,"[""it"", ""software wikis""]",,1027.6,503,False,0,[],
|
||||||
|
flickr_api,fla,flickr,"""images""",,1027.9,503,False,0,[],
|
||||||
|
baidu images,bdi,baidu,"[""images""]",,1028.0,503,False,0,[],
|
||||||
|
voidlinux,void,voidlinux,null,,1028.1,503,False,0,[],
|
||||||
|
library genesis,lg,xpath,"""files""",8.0,1028.7,503,False,0,[],
|
||||||
|
mwmbl,mwm,mwmbl,null,,1028.7,503,False,0,[],
|
||||||
|
chinaso news,chinaso,chinaso,"[""news""]",,1029.3,503,False,0,[],
|
||||||
|
semantic scholar,se,semantic_scholar,null,,1029.4,503,False,0,[],
|
||||||
|
uxwing,ux,uxwing,null,,1029.4,503,False,0,[],
|
||||||
|
ansa,ans,ansa,null,,1029.5,503,False,0,[],
|
||||||
|
startpage,sp,startpage,"[""general"", ""web""]",,1029.6,503,False,0,[],
|
||||||
|
heexy,he,heexy,"""general""",,1029.7,503,False,0,[],
|
||||||
|
naver,nvr,naver,"[""general"", ""web""]",,1029.7,503,False,0,[],
|
||||||
|
askubuntu,ubuntu,stackexchange,"[""it"", ""q&a""]",,1029.8,503,False,0,[],
|
||||||
|
openalex,oa,openalex,null,8.0,1029.8,503,False,0,[],
|
||||||
|
mymemory translated,tl,translated,null,8.0,1029.9,503,False,0,[],
|
||||||
|
public domain image archive,pdia,public_domain_image_archive,null,,1029.9,503,False,0,[],
|
||||||
|
fynd,fynd,xpath,"""general""",,1030.0,503,False,0,[],
|
||||||
|
gentoo,ge,mediawiki,"[""it"", ""software wikis""]",8.0,1030.0,503,False,0,[],
|
||||||
|
reuters,reu,reuters,null,,1030.1,503,False,0,[],
|
||||||
|
heexy images,hei,heexy,"""images""",,1030.2,503,False,0,[],
|
||||||
|
hackernews,hn,hackernews,null,,1030.3,503,False,0,[],
|
||||||
|
huggingface,hf,huggingface,null,,1030.3,503,False,0,[],
|
||||||
|
mojeek images,mjkimg,mojeek,"[""images"", ""web""]",,1030.3,503,False,0,[],
|
||||||
|
openairedatasets,oad,json_engine,"""science""",8.0,1030.3,503,False,0,[],
|
||||||
|
codeberg,cb,gitea,null,,1030.4,503,False,0,[],
|
||||||
|
goodreads,good,goodreads,null,8.0,1030.4,503,False,0,[],
|
||||||
|
deezer,dz,deezer,null,,1030.5,503,False,0,[],
|
||||||
|
gitea.com,gitea,gitea,null,,1030.5,503,False,0,[],
|
||||||
|
lingva,lv,lingva,null,8.0,1030.5,503,False,0,[],
|
||||||
|
flaticon,fli,flaticon,null,,1030.6,503,False,0,[],
|
||||||
|
huggingface datasets,hfd,huggingface,null,,1030.7,503,False,0,[],
|
||||||
|
ebay,eb,ebay,null,5,1030.8,503,False,0,[],
|
||||||
|
radio browser,rb,radio_browser,null,,1030.8,503,False,0,[],
|
||||||
|
artic,arc,artic,null,8.0,1030.9,503,False,0,[],
|
||||||
|
soundcloud,sc,soundcloud,null,,1030.9,503,False,0,[],
|
||||||
|
wikivoyage,wy,mediawiki,"[""general"", ""wikimedia""]",,1031.0,503,False,0,[],
|
||||||
|
1337x,1337x,1337x,null,,1031.1,503,False,0,[],
|
||||||
|
bandcamp,bc,bandcamp,"""music""",,1031.1,503,False,0,[],
|
||||||
|
qwant images,qwi,qwant,"[""images"", ""web""]",,1031.1,503,False,0,[],
|
||||||
|
tagesschau,ts,tagesschau,null,,1031.1,503,False,0,[],
|
||||||
|
z-library,zlib,zlibrary,null,8.0,1031.2,503,False,0,[],
|
||||||
|
fyyd,fy,fyyd,null,8.0,1031.3,503,False,0,[],
|
||||||
|
apple maps,apm,apple_maps,null,8.0,1031.4,503,False,0,[],
|
||||||
|
tootfinder,toot,tootfinder,null,,1031.4,503,False,0,[],
|
||||||
|
wikinews,wn,mediawiki,"[""news"", ""wikimedia""]",,1031.4,503,False,0,[],
|
||||||
|
superuser,su,stackexchange,"[""it"", ""q&a""]",,1031.5,503,False,0,[],
|
||||||
|
etymonline,et,xpath,"[""dictionaries""]",,1031.6,503,False,0,[],
|
||||||
|
crowdview,cv,json_engine,"""general""",,1031.8,503,False,0,[],
|
||||||
|
lobste.rs,lo,xpath,"""it""",8.0,1031.9,503,False,0,[],
|
||||||
|
wikicommons.audio,wca,wikicommons,"""music""",,1031.9,503,False,0,[],
|
||||||
|
mozhi,mz,mozhi,null,8.0,1032.0,503,False,0,[],
|
||||||
|
artstation,as,artstation,"""images""",,1032.1,503,False,0,[],
|
||||||
|
duckduckgo,ddg,duckduckgo,null,,1032.1,503,False,0,[],
|
||||||
|
quark,qk,quark,"[""general""]",,1032.1,503,False,0,[],
|
||||||
|
apk mirror,apkm,apkmirror,null,8.0,1032.2,503,False,0,[],
|
||||||
|
genius,gen,genius,null,,1032.2,503,False,0,[],
|
||||||
|
moviepilot,mp,moviepilot,null,,1032.2,503,False,0,[],
|
||||||
|
dictzone,dc,dictzone,null,,1032.3,503,False,0,[],
|
||||||
|
library of congress,loc,loc,"""images""",,1032.3,503,False,0,[],
|
||||||
|
naver images,nvri,naver,"[""images""]",,1032.3,503,False,0,[],
|
||||||
|
packagist,pack,json_engine,"[""it"", ""packages""]",8.0,1032.3,503,False,0,[],
|
||||||
|
gabanza,gab,xpath,null,4,1032.4,503,False,0,[],
|
||||||
|
lemmy comments,lecom,lemmy,null,,1032.4,503,False,0,[],
|
||||||
|
microsoft learn,msl,microsoft_learn,null,,1032.4,503,False,0,[],
|
||||||
|
fdroid,fd,fdroid,null,,1032.5,503,False,0,[],
|
||||||
|
mojeek news,mjknews,mojeek,"[""news"", ""web""]",,1032.5,503,False,0,[],
|
||||||
|
ipernity,ip,ipernity,null,,1032.6,503,False,0,[],
|
||||||
|
sogou wechat,sogouw,sogou_wechat,null,,1032.6,503,False,0,[],
|
||||||
|
chefkoch,chef,chefkoch,null,,1032.7,503,False,0,[],
|
||||||
|
duckduckgo images,ddi,duckduckgo_extra,"[""images""]",,1032.7,503,False,0,[],
|
||||||
|
yep,yep,yep,"""general""",,1032.7,503,False,0,[],
|
||||||
|
apple app store,aps,apple_app_store,null,,1032.8,503,False,0,[],
|
||||||
|
bitbucket,bb,xpath,"[""it"", ""repos""]",8.0,1032.8,503,False,0,[],
|
||||||
|
reddit,re,reddit,null,,1032.8,503,False,0,[],
|
||||||
|
aol images,aoli,aol,"[""images""]",,1032.9,503,False,0,[],
|
||||||
|
arxiv,arx,arxiv,null,,1032.9,503,False,0,[],
|
||||||
|
chinaso images,chinasoi,chinaso,"[""images""]",,1032.9,503,False,0,[],
|
||||||
|
metacpan,cpan,metacpan,null,,1032.9,503,False,0,[],
|
||||||
|
swisscows,sw,swisscows,"""general""",,1032.9,503,False,0,[],
|
||||||
|
bing news,bin,bing_news,null,,1033.0,503,False,0,[],
|
||||||
|
libretranslate,lt,libretranslate,null,,1033.0,503,False,0,[],
|
||||||
|
wikispecies,wsp,mediawiki,"[""general"", ""science"", ""wikimedia""]",,1033.0,503,False,0,[],
|
||||||
|
baidu,bd,baidu,"[""general""]",,1033.1,503,False,0,[],
|
||||||
|
brave,br,brave,"[""general"", ""web""]",,1033.1,503,False,0,[],
|
||||||
|
quark images,qki,quark,"[""images""]",,1033.1,503,False,0,[],
|
||||||
|
sogou images,sogoui,sogou_images,null,,1033.1,503,False,0,[],
|
||||||
|
azure,az,azure,"[""it"", ""cloud""]",,1033.2,503,False,0,[],
|
||||||
|
braveapi,,braveapi,null,,1033.2,503,False,0,[],
|
||||||
|
discuss.python,dpy,discourse,"[""it"", ""q&a""]",,1033.2,503,False,0,[],
|
||||||
|
springer nature,springer,springer,null,5,1033.2,503,False,0,[],
|
||||||
|
findthatmeme,ftm,findthatmeme,null,,1033.3,503,False,0,[],
|
||||||
|
lemmy communities,leco,lemmy,null,,1033.3,503,False,0,[],
|
||||||
|
solidtorrents,solid,solidtorrents,null,8.0,1033.3,503,False,0,[],
|
||||||
|
startpage news,spn,startpage,"[""news"", ""web""]",,1033.3,503,False,0,[],
|
||||||
|
1x,1x,www1x,null,8.0,1033.4,503,False,0,[],
|
||||||
|
mdn,mdn,json_engine,"[""it""]",,1033.4,503,False,0,[],
|
||||||
|
openrepos,or,xpath,"""files""",8.0,1033.4,503,False,0,[],
|
||||||
|
sourcehut,srht,sourcehut,null,,1033.4,503,False,0,[],
|
||||||
|
yandex music,ydm,yandex_music,null,,1033.4,503,False,0,[],
|
||||||
|
cloudflareai,cfai,cloudflareai,null,8.0,1033.5,503,False,0,[],
|
||||||
|
gitlab,gl,gitlab,null,,1033.5,503,False,0,[],
|
||||||
|
openairepublications,oap,json_engine,"""science""",8.0,1033.5,503,False,0,[],
|
||||||
|
yandex images,ydi,yandex,"""images""",,1033.5,503,False,0,[],
|
||||||
|
currency,cc,currency_convert,null,,1033.6,503,False,0,[],
|
||||||
|
tineye,tin,tineye,null,8.0,1033.6,503,False,0,[],
|
||||||
|
docker hub,dh,docker_hub,"[""it"", ""packages""]",,1033.7,503,False,0,[],
|
||||||
|
grokipedia,gp,grokipedia,null,,1033.7,503,False,0,[],
|
||||||
|
wolframalpha,wa,wolframalpha_noapi,"""general""",8.0,1033.7,503,False,0,[],
|
||||||
|
openverse,opv,openverse,"""images""",,1033.8,503,False,0,[],
|
||||||
|
woxikon.de synonyme,woxi,xpath,"[""dictionaries""]",8.0,1033.8,503,False,0,[],
|
||||||
|
qwant,qw,qwant,"[""general"", ""web""]",,1033.9,503,False,0,[],
|
||||||
|
wordnik,wnik,wordnik,null,8.0,1033.9,503,False,0,[],
|
||||||
|
piratebay,tpb,piratebay,null,8.0,1034.0,503,False,0,[],
|
||||||
|
lemmy users,leus,lemmy,null,,1034.1,503,False,0,[],
|
||||||
|
seekninja,sen,seekninja,null,8.0,1034.1,503,False,0,[],
|
||||||
|
startpage images,spi,startpage,"[""images"", ""web""]",,1034.1,503,False,0,[],
|
||||||
|
openlibrary,ol,openlibrary,null,8.0,1034.2,503,False,0,[],
|
||||||
|
wikicommons.files,wcf,wikicommons,"""files""",,1034.2,503,False,0,[],
|
||||||
|
rottentomatoes,rt,rottentomatoes,null,,1034.3,503,False,0,[],
|
||||||
|
emojipedia,em,emojipedia,null,8.0,1034.4,503,False,0,[],
|
||||||
|
marginalia,mar,marginalia,null,,1034.4,503,False,0,[],
|
||||||
|
naver news,nvrn,naver,"[""news""]",,1034.4,503,False,0,[],
|
||||||
|
baidu kaifa,bdk,baidu,"[""it""]",,1034.5,503,False,0,[],
|
||||||
|
elasticsearch,els,elasticsearch,null,,1034.5,503,False,0,[],
|
||||||
|
gmx,gmx,gmx,null,,1034.5,503,False,0,[],
|
||||||
|
mastodon hashtags,mah,mastodon,null,,1034.5,503,False,0,[],
|
||||||
|
material icons,mi,material_icons,null,,1034.5,503,False,0,[],
|
||||||
|
npm,npm,npm,null,8.0,1034.5,503,False,0,[],
|
||||||
|
free software directory,fsd,mediawiki,"[""it"", ""software wikis""]",8.0,1034.6,503,False,0,[],
|
||||||
|
frinkiac,frk,frinkiac,null,,1034.7,503,False,0,[],
|
||||||
|
repology,rep,repology,null,,1034.7,503,False,0,[],
|
||||||
|
wolframalpha_api,waa,wolframalpha_api,"""general""",8.0,1034.7,503,False,0,[],
|
||||||
|
bing,bi,bing,null,,1034.8,503,False,0,[],
|
||||||
|
minecraft wiki,mcw,mediawiki,"[""software wikis""]",,1034.8,503,False,0,[],
|
||||||
|
steam,stm,steam,null,,1034.8,503,False,0,[],
|
||||||
|
wallhaven,wh,wallhaven,null,,1034.8,503,False,0,[],
|
||||||
|
wikicommons.images,wci,wikicommons,"""images""",,1034.8,503,False,0,[],
|
||||||
|
swisscows news,swn,swisscows_news,null,,1034.9,503,False,0,[],
|
||||||
|
wikiversity,wv,mediawiki,"[""general"", ""wikimedia""]",,1034.9,503,False,0,[],
|
||||||
|
yacy images,yai,yacy,"""images""",8.0,1034.9,503,False,0,[],
|
||||||
|
flickr,fl,flickr_noapi,"""images""",,1035.0,503,False,0,[],
|
||||||
|
lemmy posts,lepo,lemmy,null,,1035.0,503,False,0,[],
|
||||||
|
pixabay images,pixi,pixabay,"""images""",,1035.0,503,False,0,[],
|
||||||
|
bing images,bii,bing_images,null,,1035.1,503,False,0,[],
|
||||||
|
pypi,pypi,pypi,null,,1035.1,503,False,0,[],
|
||||||
|
habrahabr,habr,xpath,"""it""",8.0,1035.2,503,False,0,[],
|
||||||
|
photon,ph,photon,null,,1035.2,503,False,0,[],
|
||||||
|
zapmeta,zpm,xpath,null,,1035.2,503,False,0,[],
|
||||||
|
hoogle,ho,xpath,"[""it"", ""packages""]",,1035.3,503,False,0,[],
|
||||||
|
lib.rs,lrs,lib_rs,null,,1035.3,503,False,0,[],
|
||||||
|
openclipart,ocl,openclipart,null,8.0,1035.3,503,False,0,[],
|
||||||
|
btdigg,bt,btdigg,null,,1035.4,503,False,0,[],
|
||||||
|
destatis,destat,destatis,null,,1035.4,503,False,0,[],
|
||||||
|
openmeteo,om,open_meteo,null,,1035.4,503,False,0,[],
|
||||||
|
pinterest,pin,pinterest,null,,1035.4,503,False,0,[],
|
||||||
|
yandex,yd,yandex,"""general""",,1035.4,503,False,0,[],
|
||||||
|
devicons,di,devicons,null,8.0,1035.5,503,False,0,[],
|
||||||
|
arch linux wiki,al,archlinux,null,,1035.6,503,False,0,[],
|
||||||
|
yacy,ya,yacy,"""general""",8.0,1035.6,503,False,0,[],
|
||||||
|
core.ac.uk,cor,core,null,,1035.7,503,False,0,[],
|
||||||
|
deepl,dpl,deepl,null,8.0,1035.7,503,False,0,[],
|
||||||
|
pi-hole.community,pi,discourse,"[""it"", ""q&a""]",,1035.8,503,False,0,[],
|
||||||
|
presearch,ps,presearch,"[""general"", ""web""]",8.0,1035.8,503,False,0,[],
|
||||||
|
presearch images,psimg,presearch,"[""images"", ""web""]",8.0,1035.8,503,False,0,[],
|
||||||
|
freesound,fnd,freesound,null,8.0,1035.9,503,False,0,[],
|
||||||
|
mankier,man,json_engine,"""it""",,1035.9,503,False,0,[],
|
||||||
|
mastodon users,mau,mastodon,null,,1035.9,503,False,0,[],
|
||||||
|
pdbe,pdb,pdbe,null,,1035.9,503,False,0,[],
|
||||||
|
torch,tch,xpath,"""onions""",,1035.9,503,False,0,[],
|
||||||
|
ddg definitions,ddd,duckduckgo_definitions,null,,1036.0,503,False,0,[],
|
||||||
|
hex,hex,hex,null,,1036.0,503,False,0,[],
|
||||||
|
il post,pst,il_post,null,,1036.0,503,False,0,[],
|
||||||
|
sepiasearch,sep,sepiasearch,null,,1036.0,503,False,0,[],
|
||||||
|
wikiquote,wq,mediawiki,"[""general"", ""wikimedia""]",,1036.0,503,False,0,[],
|
||||||
|
imgur,img,imgur,null,,1036.1,503,False,0,[],
|
||||||
|
wikipedia,wp,wikipedia,"[""general""]",,1036.1,503,False,0,[],
|
||||||
|
bpb,bpb,bpb,null,,1036.2,503,False,0,[],
|
||||||
|
adobe stock audio,asa,adobe_stock,"[""music""]",6,1036.3,503,False,0,[],
|
||||||
|
geizhals,geiz,geizhals,null,,1036.3,503,False,0,[],
|
||||||
|
ollama,ollama,ollama,null,,1036.3,503,False,0,[],
|
||||||
|
adobe stock,asi,adobe_stock,"[""images""]",6,1036.4,503,False,0,[],
|
||||||
|
pkg.go.dev,pgo,pkg_go_dev,null,,1036.4,503,False,0,[],
|
||||||
|
ina,in,ina,null,8.0,1036.5,503,False,0,[],
|
||||||
|
wikidata,wd,wikidata,"[""general""]",8.0,1036.5,503,False,0,[],
|
||||||
|
wikimini,wkmn,xpath,"""general""",,1036.5,503,False,0,[],
|
||||||
|
360search,360so,360search,null,8.0,1036.6,503,False,0,[],
|
||||||
|
astrophysics data system,ads,astrophysics_data_system,null,,1036.6,503,False,0,[],
|
||||||
|
brave.news,brnews,brave,"""news""",,1036.6,503,False,0,[],
|
||||||
|
duden,du,duden,null,,1036.7,503,False,0,[],
|
||||||
|
sogou,sogou,sogou,null,,1036.7,503,False,0,[],
|
||||||
|
wikibooks,wb,mediawiki,"[""general"", ""wikimedia""]",,1036.7,503,False,0,[],
|
||||||
|
Torznab EZTV,eztv,torznab,null,,1036.8,503,False,0,[],
|
||||||
|
ahmia,ah,ahmia,"""onions""",8.0,1036.8,503,False,0,[],
|
||||||
|
aol,aol,aol,"[""general""]",,1036.8,503,False,0,[],
|
||||||
|
mojeek,mjk,mojeek,"[""general"", ""web""]",,1036.8,503,False,0,[],
|
||||||
|
encyclosearch,es,json_engine,"""general""",,1036.9,503,False,0,[],
|
||||||
|
jisho,js,jisho,null,8.0,1037.1,503,False,0,[],
|
||||||
|
searchmysite,sms,xpath,"""general""",,1037.1,503,False,0,[],
|
||||||
|
annas archive,aa,annas_archive,null,5,1037.2,503,False,0,[],
|
||||||
|
pexels,pe,pexels,null,,1037.2,503,False,0,[],
|
||||||
|
wttr.in,wttr,wttr,null,8.0,1037.2,503,False,0,[],
|
||||||
|
imdb,imdb,imdb,null,8.0,1037.3,503,False,0,[],
|
||||||
|
crates.io,crates,crates,null,8.0,1037.4,503,False,0,[],
|
||||||
|
deviantart,da,deviantart,null,8.0,1037.4,503,False,0,[],
|
||||||
|
qwant news,qwn,qwant,"""news""",,1037.4,503,False,0,[],
|
||||||
|
crossref,cr,crossref,null,8.0,1037.5,503,False,0,[],
|
||||||
|
stackoverflow,st,stackexchange,"[""it"", ""q&a""]",,1037.6,503,False,0,[],
|
||||||
|
500px,500,500px,null,5,1037.7,503,False,0,[],
|
||||||
|
mixcloud,mc,mixcloud,null,,1037.7,503,False,0,[],
|
||||||
|
presearch news,psnews,presearch,"[""news"", ""web""]",8.0,1037.7,503,False,0,[],
|
||||||
|
lucide,luc,lucide,null,8.0,1037.8,503,False,0,[],
|
||||||
|
boardreader,boa,boardreader,null,,1038.0,503,False,0,[],
|
||||||
|
seznam,szn,seznam,null,,1038.0,503,False,0,[],
|
||||||
|
tokyotoshokan,tt,tokyotoshokan,null,8.0,1038.0,503,False,0,[],
|
||||||
|
9gag,9g,9gag,null,,1038.1,503,False,0,[],
|
||||||
|
github code,ghc,github_code,null,8.0,1038.3,503,False,0,[],
|
||||||
|
wiby,wib,json_engine,"[""general"", ""web""]",,1038.3,503,False,0,[],
|
||||||
|
pixiv,pv,pixiv,null,,1038.8,503,False,0,[],
|
||||||
|
selfhst icons,si,selfhst,null,,1038.9,503,False,0,[],
|
||||||
|
huggingface spaces,hfs,huggingface,null,,1039.1,503,False,0,[],
|
||||||
|
unsplash,us,unsplash,null,,1039.3,503,False,0,[],
|
||||||
|
national vulnerability database,nvd,nvd,null,,1039.6,503,False,0,[],
|
||||||
|
brave.images,brimg,brave,"[""images"", ""web""]",,1040.5,503,False,0,[],
|
||||||
|
erowid,ew,xpath,[],,1040.6,503,False,0,[],
|
||||||
|
alpine linux packages,alp,alpinelinux,null,,1044.1,503,False,0,[],
|
||||||
|
senscritique,scr,senscritique,null,8.0,1072.9,503,False,0,[],
|
||||||
|
pubmed,pub,pubmed,null,,1073.0,503,False,0,[],
|
||||||
|
wiktionary,wt,mediawiki,"[""dictionaries"", ""wikimedia""]",,1073.0,503,False,0,[],
|
||||||
|
caddy.community,caddy,discourse,"[""it"", ""q&a""]",,1073.3,503,False,0,[],
|
||||||
|
cara,ca,cara,null,,1076.7,503,False,0,[],
|
||||||
|
anaconda,conda,xpath,"""it""",8.0,1077.1,503,False,0,[],
|
||||||
|
kickass,kc,kickass,null,8.0,1077.1,503,False,0,[],
|
||||||
|
duckduckgo weather,ddw,duckduckgo_weather,null,,1077.2,503,False,0,[],
|
||||||
|
rubygems,rbg,xpath,"[""it"", ""packages""]",,1077.4,503,False,0,[],
|
||||||
|
nyaa,nt,nyaa,null,,1077.5,503,False,0,[],
|
||||||
|
github,gh,github,null,,1077.7,503,False,0,[],
|
||||||
|
cachy os packages,cos,cachy_os,null,,1080.3,503,False,0,[],
|
||||||
|
@@ -0,0 +1,245 @@
|
|||||||
|
engine,shortcut,backend,categories,configured_timeout,elapsed_ms,status_code,ok,result_count,unresponsive_engines,errors
|
||||||
|
gmx,gmx,gmx,null,,223.3,200,False,0,"[[""gmx"", ""Suspended: timeout""]]",
|
||||||
|
brave,br,brave,"[""general"", ""web""]",,432.9,200,False,0,"[[""brave"", ""Suspended: too many requests""]]",
|
||||||
|
adobe stock audio,asa,adobe_stock,"[""music""]",6,472.4,200,False,0,"[[""adobe stock audio"", ""Suspended: access denied""]]",
|
||||||
|
currency,cc,currency_convert,null,,499.5,200,False,0,[],
|
||||||
|
sogou images,sogoui,sogou_images,null,,646.9,200,True,48,[],
|
||||||
|
yacy,ya,yacy,"""general""",8.0,666.8,200,False,0,"[[""yacy"", ""Suspended: timeout""]]",
|
||||||
|
wikidata,wd,wikidata,"[""general""]",8.0,779.4,200,False,0,"[[""wikidata"", ""Suspended: timeout""]]",
|
||||||
|
dictzone,dc,dictzone,null,,842.3,200,False,0,[],
|
||||||
|
yandex,yd,yandex,"""general""",,899.6,200,False,0,"[[""yandex"", ""Suspended: HTTP error""]]",
|
||||||
|
bandcamp,bc,bandcamp,"""music""",,916.2,200,False,0,[],
|
||||||
|
unsplash,us,unsplash,null,,1111.3,200,False,0,"[[""unsplash"", ""parsing error""]]",
|
||||||
|
chefkoch,chef,chefkoch,null,,1164.0,200,False,0,[],
|
||||||
|
rubygems,rbg,xpath,"[""it"", ""packages""]",,1178.3,200,True,30,[],
|
||||||
|
baidu images,bdi,baidu,"[""images""]",,1185.7,200,True,10,[],
|
||||||
|
bing,bi,bing,null,,1208.1,200,False,0,"[[""bing"", ""Suspended: HTTP connection error""]]",
|
||||||
|
mozhi,mz,mozhi,null,8.0,1284.4,200,False,0,[],
|
||||||
|
yacy images,yai,yacy,"""images""",8.0,1324.7,200,False,0,"[[""yacy images"", ""Suspended: timeout""]]",
|
||||||
|
qwant,qw,qwant,"[""general"", ""web""]",,1339.0,200,False,0,"[[""qwant"", ""Suspended: timeout""]]",
|
||||||
|
wikiquote,wq,mediawiki,"[""general"", ""wikimedia""]",,1355.2,200,False,0,"[[""wikiquote"", ""Suspended: timeout""]]",
|
||||||
|
moviepilot,mp,moviepilot,null,,1387.3,200,False,0,[],
|
||||||
|
yandex images,ydi,yandex,"""images""",,1390.4,200,False,0,"[[""yandex images"", ""Suspended: HTTP error""]]",
|
||||||
|
brave.images,brimg,brave,"[""images"", ""web""]",,1392.0,200,False,0,"[[""brave.images"", ""Suspended: too many requests""]]",
|
||||||
|
mdn,mdn,json_engine,"[""it""]",,1451.7,200,True,10,[],
|
||||||
|
duckduckgo,ddg,duckduckgo,null,,1555.5,200,False,0,"[[""duckduckgo"", ""CAPTCHA""]]",
|
||||||
|
mastodon users,mau,mastodon,null,,1576.4,200,True,40,[],
|
||||||
|
photon,ph,photon,null,,1580.5,200,True,10,[],
|
||||||
|
lingva,lv,lingva,null,8.0,1599.4,200,False,0,[],
|
||||||
|
searchmysite,sms,xpath,"""general""",,1600.2,200,True,10,[],
|
||||||
|
mixcloud,mc,mixcloud,null,,1744.6,200,False,0,"[[""mixcloud"", ""HTTP connection error""]]",
|
||||||
|
quark,qk,quark,"[""general""]",,1759.7,200,False,0,"[[""quark"", ""Suspended: CAPTCHA""]]",
|
||||||
|
senscritique,scr,senscritique,null,8.0,1809.2,200,True,16,[],
|
||||||
|
lemmy users,leus,lemmy,null,,1841.1,200,False,0,"[[""lemmy users"", ""Suspended: timeout""]]",
|
||||||
|
mymemory translated,tl,translated,null,8.0,1881.8,200,False,0,[],
|
||||||
|
arxiv,arx,arxiv,null,,1909.9,200,True,10,[],
|
||||||
|
pdbe,pdb,pdbe,null,,1944.0,200,False,0,[],
|
||||||
|
pub.dev,pd,xpath,"[""packages"", ""it""]",8.0,1953.6,200,True,10,[],
|
||||||
|
qwant images,qwi,qwant,"[""images"", ""web""]",,1956.7,200,False,0,"[[""qwant images"", ""Suspended: timeout""]]",
|
||||||
|
wikipedia,wp,wikipedia,"[""general""]",,1975.9,200,False,0,[],
|
||||||
|
pypi,pypi,pypi,null,,1997.1,200,False,0,[],
|
||||||
|
yep,yep,yep,"""general""",,2011.6,200,True,20,[],
|
||||||
|
destatis,destat,destatis,null,,2051.3,200,False,0,[],
|
||||||
|
mojeek,mjk,mojeek,"[""general"", ""web""]",,2067.4,200,False,0,"[[""mojeek"", ""Suspended: access denied""]]",
|
||||||
|
fyyd,fy,fyyd,null,8.0,2068.9,200,True,10,[],
|
||||||
|
bpb,bpb,bpb,null,,2086.7,200,True,15,[],
|
||||||
|
imdb,imdb,imdb,null,8.0,2088.3,200,True,7,[],
|
||||||
|
pinterest,pin,pinterest,null,,2090.7,200,True,18,[],
|
||||||
|
wikicommons.images,wci,wikicommons,"""images""",,2100.3,200,True,10,[],
|
||||||
|
docker hub,dh,docker_hub,"[""it"", ""packages""]",,2104.9,200,True,10,[],
|
||||||
|
lucide,luc,lucide,null,8.0,2107.4,200,False,0,[],
|
||||||
|
tineye,tin,tineye,null,8.0,2109.2,200,False,0,[],
|
||||||
|
bing news,bin,bing_news,null,,2158.4,200,False,0,[],
|
||||||
|
steam,stm,steam,null,,2168.7,200,True,3,[],
|
||||||
|
gitlab,gl,gitlab,null,,2173.7,200,True,20,[],
|
||||||
|
uxwing,ux,uxwing,null,,2201.2,200,False,0,"[[""uxwing"", ""access denied""]]",
|
||||||
|
crowdview,cv,json_engine,"""general""",,2250.3,200,True,40,[],
|
||||||
|
ddg definitions,ddd,duckduckgo_definitions,null,,2294.1,200,True,2,[],
|
||||||
|
superuser,su,stackexchange,"[""it"", ""q&a""]",,2312.2,200,True,8,[],
|
||||||
|
discuss.python,dpy,discourse,"[""it"", ""q&a""]",,2322.2,200,True,50,[],
|
||||||
|
aol,aol,aol,"[""general""]",,2404.7,200,True,10,[],
|
||||||
|
selfhst icons,si,selfhst,null,,2422.6,200,True,1,[],
|
||||||
|
naver,nvr,naver,"[""general"", ""web""]",,2425.1,200,False,0,[],
|
||||||
|
openlibrary,ol,openlibrary,null,8.0,2427.1,200,False,0,"[[""openlibrary"", ""Suspended: timeout""]]",
|
||||||
|
500px,500,500px,null,5,2431.9,200,False,0,"[[""500px"", ""HTTP connection error""]]",
|
||||||
|
naver news,nvrn,naver,"[""news""]",,2451.5,200,True,10,[],
|
||||||
|
9gag,9g,9gag,null,,2474.7,200,False,0,"[[""9gag"", ""access denied""]]",
|
||||||
|
wikispecies,wsp,mediawiki,"[""general"", ""science"", ""wikimedia""]",,2476.0,200,True,5,[],
|
||||||
|
fynd,fynd,xpath,"""general""",,2495.8,200,True,10,[],
|
||||||
|
sogou wechat,sogouw,sogou_wechat,null,,2499.9,200,True,10,[],
|
||||||
|
mwmbl,mwm,mwmbl,null,,2519.2,200,True,34,[],
|
||||||
|
deezer,dz,deezer,null,,2546.0,200,True,25,[],
|
||||||
|
bing images,bii,bing_images,null,,2550.4,200,False,0,[],
|
||||||
|
pixabay images,pixi,pixabay,"""images""",,2570.1,200,False,0,"[[""pixabay images"", ""parsing error""]]",
|
||||||
|
huggingface spaces,hfs,huggingface,null,,2584.2,200,True,1000,[],
|
||||||
|
imgur,img,imgur,null,,2586.0,200,True,39,[],
|
||||||
|
lemmy comments,lecom,lemmy,null,,2593.3,200,False,0,"[[""lemmy comments"", ""Suspended: timeout""]]",
|
||||||
|
baidu,bd,baidu,"[""general""]",,2597.9,200,True,10,[],
|
||||||
|
wikivoyage,wy,mediawiki,"[""general"", ""wikimedia""]",,2634.2,200,False,0,"[[""wikivoyage"", ""Suspended: timeout""]]",
|
||||||
|
hackernews,hn,hackernews,null,,2641.1,200,True,30,[],
|
||||||
|
radio browser,rb,radio_browser,null,,2660.1,200,True,2,[],
|
||||||
|
wikicommons.audio,wca,wikicommons,"""music""",,2660.3,200,True,10,[],
|
||||||
|
jisho,js,jisho,null,8.0,2675.7,200,True,1,[],
|
||||||
|
geizhals,geiz,geizhals,null,,2692.5,200,False,0,"[[""geizhals"", ""access denied""]]",
|
||||||
|
presearch images,psimg,presearch,"[""images"", ""web""]",8.0,2705.2,200,True,100,[],
|
||||||
|
goodreads,good,goodreads,null,8.0,2718.2,200,False,0,"[[""goodreads"", ""parsing error""]]",
|
||||||
|
huggingface datasets,hfd,huggingface,null,,2741.9,200,True,736,[],
|
||||||
|
reddit,re,reddit,null,,2786.3,200,False,0,"[[""reddit"", ""access denied""]]",
|
||||||
|
wikimini,wkmn,xpath,"""general""",,2837.7,200,False,0,"[[""wikimini"", ""Suspended: timeout""]]",
|
||||||
|
semantic scholar,se,semantic_scholar,null,,2843.2,200,False,0,"[[""semantic scholar"", ""access denied""]]",
|
||||||
|
askubuntu,ubuntu,stackexchange,"[""it"", ""q&a""]",,2859.7,200,True,10,[],
|
||||||
|
swisscows images,swi,swisscows,"""images""",,2860.2,200,False,230,"[[""bing"", ""Suspended: HTTP connection error""], [""brave"", ""Suspended: too many requests""], [""duckduckgo"", ""CAPTCHA""], [""gmx"", ""Suspended: timeout""], [""mojeek"", ""Suspended: access denied""], [""openlibrary"", ""Suspended: timeout""], [""presearch"", ""Suspended: too many requests""], [""quark"", ""Suspended: CAPTCHA""], [""qwant"", ""Suspended: timeout""], [""seznam"", ""Suspended: timeout""], [""sogou"", ""Suspended: CAPTCHA""], [""tagesschau"", ""Suspended: HTTP connection error""], [""wiby"", ""Suspended: timeout""], [""wikibooks"", ""Suspended: timeout""], [""wikidata"", ""Suspended: timeout""], [""wikimini"", ""Suspended: timeout""], [""wikiquote"", ""Suspended: timeout""], [""wikisource"", ""Suspended: timeout""], [""wikiversity"", ""Suspended: timeout""], [""wikivoyage"", ""Suspended: timeout""], [""wolframalpha"", ""Suspended: timeout""], [""yacy"", ""Suspended: timeout""], [""zapmeta"", ""Suspended: access denied""]]",
|
||||||
|
mojeek images,mjkimg,mojeek,"[""images"", ""web""]",,2883.6,200,False,0,"[[""mojeek images"", ""access denied""]]",
|
||||||
|
boardreader,boa,boardreader,null,,2914.1,200,True,10,[],
|
||||||
|
360search,360so,360search,null,8.0,2914.6,200,False,0,"[[""360search"", ""Suspended: timeout""]]",
|
||||||
|
microsoft learn,msl,microsoft_learn,null,,2989.8,200,True,10,[],
|
||||||
|
sogou,sogou,sogou,null,,2995.6,200,False,0,"[[""sogou"", ""Suspended: CAPTCHA""]]",
|
||||||
|
presearch news,psnews,presearch,"[""news"", ""web""]",8.0,3024.0,200,True,12,[],
|
||||||
|
rottentomatoes,rt,rottentomatoes,null,,3034.3,200,True,20,[],
|
||||||
|
seznam,szn,seznam,null,,3034.7,200,False,0,"[[""seznam"", ""Suspended: timeout""]]",
|
||||||
|
flickr,fl,flickr_noapi,"""images""",,3062.2,200,True,25,[],
|
||||||
|
emojipedia,em,emojipedia,null,8.0,3073.0,200,False,0,"[[""emojipedia"", ""access denied""]]",
|
||||||
|
bt4g,bt4g,bt4g,null,,3076.5,200,False,0,"[[""bt4g"", ""HTTP connection error""]]",
|
||||||
|
devicons,di,devicons,null,8.0,3090.0,200,False,0,[],
|
||||||
|
braveapi,,braveapi,null,,3094.0,200,False,191,"[[""360search"", ""Suspended: timeout""], [""aol"", ""Suspended: HTTP error""], [""baidu"", ""Suspended: CAPTCHA""], [""brave"", ""Suspended: too many requests""], [""duckduckgo"", ""CAPTCHA""], [""gmx"", ""Suspended: timeout""], [""mojeek"", ""Suspended: access denied""], [""openlibrary"", ""Suspended: timeout""], [""presearch"", ""Suspended: too many requests""], [""quark"", ""Suspended: CAPTCHA""], [""qwant"", ""Suspended: timeout""], [""seznam"", ""Suspended: timeout""], [""sogou"", ""Suspended: CAPTCHA""], [""tagesschau"", ""Suspended: HTTP connection error""], [""wiby"", ""Suspended: timeout""], [""wikibooks"", ""Suspended: timeout""], [""wikimini"", ""Suspended: timeout""], [""wikiquote"", ""Suspended: timeout""], [""wikisource"", ""Suspended: too many requests""], [""wikispecies"", ""Suspended: too many requests""], [""wikiversity"", ""Suspended: too many requests""], [""wikivoyage"", ""Suspended: too many requests""], [""wolframalpha"", ""Suspended: timeout""], [""yacy"", ""Suspended: timeout""], [""yandex"", ""Suspended: HTTP error""], [""zapmeta"", ""Suspended: access denied""]]",
|
||||||
|
lobste.rs,lo,xpath,"""it""",8.0,3102.7,200,True,20,[],
|
||||||
|
mankier,man,json_engine,"""it""",,3121.6,200,False,0,[],
|
||||||
|
baidu kaifa,bdk,baidu,"[""it""]",,3149.4,200,True,10,[],
|
||||||
|
github,gh,github,null,,3195.6,200,True,30,[],
|
||||||
|
adobe stock,asi,adobe_stock,"[""images""]",6,3205.4,200,False,0,"[[""adobe stock"", ""access denied""]]",
|
||||||
|
findthatmeme,ftm,findthatmeme,null,,3208.5,200,True,50,[],
|
||||||
|
flaticon,fli,flaticon,null,,3210.0,200,True,1,[],
|
||||||
|
gabanza,gab,xpath,null,4,3226.9,200,True,30,[],
|
||||||
|
pi-hole.community,pi,discourse,"[""it"", ""q&a""]",,3231.0,200,True,4,[],
|
||||||
|
cachy os packages,cos,cachy_os,null,,3246.0,200,True,10,[],
|
||||||
|
artic,arc,artic,null,8.0,3262.9,200,True,20,[],
|
||||||
|
azure,az,azure,"[""it"", ""cloud""]",,3337.2,200,False,191,"[[""360search"", ""Suspended: timeout""], [""aol"", ""Suspended: HTTP error""], [""baidu"", ""Suspended: CAPTCHA""], [""brave"", ""Suspended: too many requests""], [""duckduckgo"", ""CAPTCHA""], [""gmx"", ""Suspended: timeout""], [""mojeek"", ""Suspended: access denied""], [""openlibrary"", ""Suspended: timeout""], [""presearch"", ""Suspended: too many requests""], [""quark"", ""Suspended: CAPTCHA""], [""qwant"", ""Suspended: timeout""], [""seznam"", ""Suspended: timeout""], [""sogou"", ""Suspended: CAPTCHA""], [""tagesschau"", ""Suspended: HTTP connection error""], [""wiby"", ""Suspended: timeout""], [""wikibooks"", ""Suspended: timeout""], [""wikimini"", ""Suspended: timeout""], [""wikiquote"", ""Suspended: timeout""], [""wikisource"", ""Suspended: too many requests""], [""wikispecies"", ""Suspended: too many requests""], [""wikiversity"", ""Suspended: too many requests""], [""wikivoyage"", ""Suspended: too many requests""], [""wolframalpha"", ""Suspended: timeout""], [""yacy"", ""Suspended: timeout""], [""yandex"", ""Suspended: HTTP error""], [""zapmeta"", ""Suspended: access denied""]]",
|
||||||
|
wolframalpha,wa,wolframalpha_noapi,"""general""",8.0,3347.7,200,False,0,"[[""wolframalpha"", ""timeout""]]",
|
||||||
|
1337x,1337x,1337x,null,,3362.4,200,False,0,"[[""1337x"", ""access denied""]]",
|
||||||
|
btdigg,bt,btdigg,null,,3386.7,200,False,0,"[[""btdigg"", ""too many requests""]]",
|
||||||
|
minecraft wiki,mcw,mediawiki,"[""software wikis""]",,3400.6,200,True,5,[],
|
||||||
|
heexy,he,heexy,"""general""",,3404.7,200,False,191,"[[""360search"", ""Suspended: timeout""], [""aol"", ""Suspended: HTTP error""], [""baidu"", ""Suspended: CAPTCHA""], [""brave"", ""Suspended: too many requests""], [""duckduckgo"", ""CAPTCHA""], [""gmx"", ""Suspended: timeout""], [""mojeek"", ""Suspended: access denied""], [""openlibrary"", ""Suspended: timeout""], [""presearch"", ""Suspended: too many requests""], [""quark"", ""Suspended: CAPTCHA""], [""qwant"", ""Suspended: timeout""], [""seznam"", ""Suspended: timeout""], [""sogou"", ""Suspended: CAPTCHA""], [""tagesschau"", ""Suspended: HTTP connection error""], [""wiby"", ""Suspended: timeout""], [""wikibooks"", ""Suspended: timeout""], [""wikimini"", ""Suspended: timeout""], [""wikiquote"", ""Suspended: timeout""], [""wikisource"", ""Suspended: too many requests""], [""wikispecies"", ""Suspended: too many requests""], [""wikiversity"", ""Suspended: too many requests""], [""wikivoyage"", ""Suspended: too many requests""], [""wolframalpha"", ""Suspended: timeout""], [""yacy"", ""Suspended: timeout""], [""yandex"", ""Suspended: HTTP error""], [""zapmeta"", ""Suspended: access denied""]]",
|
||||||
|
caddy.community,caddy,discourse,"[""it"", ""q&a""]",,3440.0,200,True,4,[],
|
||||||
|
zapmeta,zpm,xpath,null,,3457.0,200,False,0,"[[""zapmeta"", ""Suspended: access denied""]]",
|
||||||
|
seekninja,sen,seekninja,null,8.0,3465.1,200,False,229,"[[""bing"", ""Suspended: HTTP connection error""], [""brave"", ""Suspended: too many requests""], [""duckduckgo"", ""CAPTCHA""], [""gmx"", ""Suspended: timeout""], [""mojeek"", ""Suspended: access denied""], [""openlibrary"", ""Suspended: timeout""], [""presearch"", ""too many requests""], [""quark"", ""Suspended: CAPTCHA""], [""qwant"", ""Suspended: timeout""], [""seznam"", ""Suspended: timeout""], [""sogou"", ""Suspended: CAPTCHA""], [""tagesschau"", ""Suspended: HTTP connection error""], [""wiby"", ""Suspended: timeout""], [""wikibooks"", ""Suspended: timeout""], [""wikidata"", ""Suspended: timeout""], [""wikimini"", ""Suspended: timeout""], [""wikiquote"", ""Suspended: timeout""], [""wikisource"", ""Suspended: timeout""], [""wikiversity"", ""Suspended: timeout""], [""wikivoyage"", ""Suspended: timeout""], [""wolframalpha"", ""Suspended: timeout""], [""yacy"", ""Suspended: timeout""], [""zapmeta"", ""Suspended: access denied""]]",
|
||||||
|
annas archive,aa,annas_archive,null,5,3518.6,200,False,0,"[[""annas archive"", ""HTTP connection error""]]",
|
||||||
|
duckduckgo images,ddi,duckduckgo_extra,"[""images""]",,3525.0,200,True,95,[],
|
||||||
|
pkg.go.dev,pgo,pkg_go_dev,null,,3575.8,200,True,50,[],
|
||||||
|
sepiasearch,sep,sepiasearch,null,,3617.9,200,True,10,[],
|
||||||
|
quark images,qki,quark,"[""images""]",,3622.2,200,True,10,[],
|
||||||
|
hex,hex,hex,null,,3652.9,200,True,10,[],
|
||||||
|
il post,pst,il_post,null,,3697.3,200,True,10,[],
|
||||||
|
nixos wiki,nixw,mediawiki,"[""it"", ""software wikis""]",,3720.5,200,True,1,[],
|
||||||
|
brave.news,brnews,brave,"""news""",,3731.7,200,False,0,[],
|
||||||
|
huggingface,hf,huggingface,null,,3741.2,200,True,1000,[],
|
||||||
|
wikicommons.files,wcf,wikicommons,"""files""",,3744.6,200,True,10,[],
|
||||||
|
ansa,ans,ansa,null,,3770.4,200,True,12,[],
|
||||||
|
hoogle,ho,xpath,"[""it"", ""packages""]",,3784.8,200,True,25,[],
|
||||||
|
crates.io,crates,crates,null,8.0,3789.5,200,True,10,[],
|
||||||
|
piratebay,tpb,piratebay,null,8.0,3849.7,200,True,35,[],
|
||||||
|
anaconda,conda,xpath,"""it""",8.0,3862.4,200,False,0,[],
|
||||||
|
pexels,pe,pexels,null,,3879.9,200,True,20,[],
|
||||||
|
material icons,mi,material_icons,null,,3893.7,200,False,0,[],
|
||||||
|
tagesschau,ts,tagesschau,null,,3894.2,200,False,0,"[[""tagesschau"", ""Suspended: HTTP connection error""]]",
|
||||||
|
wttr.in,wttr,wttr,null,8.0,3899.2,200,False,0,"[[""wttr.in"", ""parsing error""]]",
|
||||||
|
national vulnerability database,nvd,nvd,null,,3900.7,200,True,10,[],
|
||||||
|
naver images,nvri,naver,"[""images""]",,3932.4,200,False,0,[],
|
||||||
|
deviantart,da,deviantart,null,8.0,3998.8,200,False,0,[],
|
||||||
|
ollama,ollama,ollama,null,,4042.6,200,True,20,[],
|
||||||
|
encyclosearch,es,json_engine,"""general""",,4056.8,200,True,15,[],
|
||||||
|
lib.rs,lrs,lib_rs,null,,4128.2,200,False,0,"[[""lib.rs"", ""access denied""]]",
|
||||||
|
deepl,dpl,deepl,null,8.0,4132.6,200,False,191,"[[""360search"", ""Suspended: timeout""], [""aol"", ""Suspended: HTTP error""], [""baidu"", ""Suspended: CAPTCHA""], [""brave"", ""Suspended: too many requests""], [""duckduckgo"", ""CAPTCHA""], [""gmx"", ""Suspended: timeout""], [""mojeek"", ""Suspended: access denied""], [""openlibrary"", ""Suspended: timeout""], [""presearch"", ""Suspended: too many requests""], [""quark"", ""Suspended: CAPTCHA""], [""qwant"", ""Suspended: timeout""], [""seznam"", ""Suspended: timeout""], [""sogou"", ""Suspended: CAPTCHA""], [""tagesschau"", ""Suspended: HTTP connection error""], [""wiby"", ""Suspended: timeout""], [""wikibooks"", ""Suspended: timeout""], [""wikimini"", ""Suspended: timeout""], [""wikiquote"", ""Suspended: timeout""], [""wikisource"", ""Suspended: too many requests""], [""wikispecies"", ""Suspended: too many requests""], [""wikiversity"", ""Suspended: too many requests""], [""wikivoyage"", ""Suspended: too many requests""], [""wolframalpha"", ""Suspended: timeout""], [""yacy"", ""Suspended: timeout""], [""yandex"", ""Suspended: HTTP error""], [""zapmeta"", ""Suspended: access denied""]]",
|
||||||
|
openmeteo,om,open_meteo,null,,4135.8,200,False,0,[],
|
||||||
|
sourcehut,srht,sourcehut,null,,4147.8,200,True,2,[],
|
||||||
|
openrepos,or,xpath,"""files""",8.0,4203.1,200,True,2,[],
|
||||||
|
lemmy posts,lepo,lemmy,null,,4218.7,200,False,0,"[[""lemmy posts"", ""Suspended: timeout""]]",
|
||||||
|
repology,rep,repology,null,,4226.1,200,False,236,"[[""bing"", ""Suspended: HTTP connection error""], [""brave"", ""Suspended: too many requests""], [""duckduckgo"", ""CAPTCHA""], [""gmx"", ""Suspended: timeout""], [""mojeek"", ""Suspended: access denied""], [""openlibrary"", ""Suspended: timeout""], [""quark"", ""Suspended: CAPTCHA""], [""qwant"", ""Suspended: timeout""], [""seznam"", ""Suspended: timeout""], [""sogou"", ""Suspended: CAPTCHA""], [""tagesschau"", ""Suspended: HTTP connection error""], [""wiby"", ""Suspended: timeout""], [""wikibooks"", ""Suspended: timeout""], [""wikidata"", ""Suspended: timeout""], [""wikimini"", ""Suspended: timeout""], [""wikiquote"", ""Suspended: timeout""], [""wikisource"", ""Suspended: timeout""], [""wikiversity"", ""Suspended: timeout""], [""wikivoyage"", ""Suspended: timeout""], [""wolframalpha"", ""Suspended: timeout""], [""yacy"", ""Suspended: timeout""], [""zapmeta"", ""Suspended: access denied""]]",
|
||||||
|
nyaa,nt,nyaa,null,,4237.6,200,False,0,[],
|
||||||
|
packagist,pack,json_engine,"[""it"", ""packages""]",8.0,4242.1,200,True,15,[],
|
||||||
|
metacpan,cpan,metacpan,null,,4261.6,200,False,0,"[[""metacpan"", ""HTTP error""]]",
|
||||||
|
duckduckgo news,ddn,duckduckgo_extra,"[""news""]",,4274.4,200,True,30,[],
|
||||||
|
presearch,ps,presearch,"[""general"", ""web""]",8.0,4327.9,200,True,14,[],
|
||||||
|
free software directory,fsd,mediawiki,"[""it"", ""software wikis""]",8.0,4395.2,200,False,0,[],
|
||||||
|
gentoo,ge,mediawiki,"[""it"", ""software wikis""]",8.0,4425.4,200,False,0,[],
|
||||||
|
heexy images,hei,heexy,"""images""",,4442.7,200,False,191,"[[""360search"", ""Suspended: timeout""], [""aol"", ""Suspended: HTTP error""], [""baidu"", ""Suspended: CAPTCHA""], [""brave"", ""Suspended: too many requests""], [""duckduckgo"", ""CAPTCHA""], [""gmx"", ""Suspended: timeout""], [""mojeek"", ""Suspended: access denied""], [""openlibrary"", ""Suspended: timeout""], [""presearch"", ""Suspended: too many requests""], [""quark"", ""Suspended: CAPTCHA""], [""qwant"", ""Suspended: timeout""], [""seznam"", ""Suspended: timeout""], [""sogou"", ""Suspended: CAPTCHA""], [""tagesschau"", ""Suspended: HTTP connection error""], [""wiby"", ""Suspended: timeout""], [""wikibooks"", ""Suspended: timeout""], [""wikimini"", ""Suspended: timeout""], [""wikiquote"", ""Suspended: timeout""], [""wikisource"", ""Suspended: too many requests""], [""wikispecies"", ""Suspended: too many requests""], [""wikiversity"", ""Suspended: too many requests""], [""wikivoyage"", ""Suspended: too many requests""], [""wolframalpha"", ""Suspended: timeout""], [""yacy"", ""Suspended: timeout""], [""yandex"", ""Suspended: HTTP error""], [""zapmeta"", ""Suspended: access denied""]]",
|
||||||
|
swisscows,sw,swisscows,"""general""",,4449.4,200,False,230,"[[""bing"", ""Suspended: HTTP connection error""], [""brave"", ""Suspended: too many requests""], [""duckduckgo"", ""CAPTCHA""], [""gmx"", ""Suspended: timeout""], [""mojeek"", ""Suspended: access denied""], [""openlibrary"", ""Suspended: timeout""], [""presearch"", ""Suspended: too many requests""], [""quark"", ""Suspended: CAPTCHA""], [""qwant"", ""Suspended: timeout""], [""seznam"", ""Suspended: timeout""], [""sogou"", ""Suspended: CAPTCHA""], [""tagesschau"", ""Suspended: HTTP connection error""], [""wiby"", ""Suspended: timeout""], [""wikibooks"", ""Suspended: timeout""], [""wikidata"", ""Suspended: timeout""], [""wikimini"", ""Suspended: timeout""], [""wikiquote"", ""Suspended: timeout""], [""wikisource"", ""Suspended: timeout""], [""wikiversity"", ""Suspended: timeout""], [""wikivoyage"", ""Suspended: timeout""], [""wolframalpha"", ""Suspended: timeout""], [""yacy"", ""Suspended: timeout""], [""zapmeta"", ""Suspended: access denied""]]",
|
||||||
|
mastodon hashtags,mah,mastodon,null,,4489.6,200,True,40,[],
|
||||||
|
torch,tch,xpath,"""onions""",,4490.0,200,False,191,"[[""360search"", ""Suspended: timeout""], [""aol"", ""Suspended: HTTP error""], [""baidu"", ""Suspended: CAPTCHA""], [""brave"", ""Suspended: too many requests""], [""duckduckgo"", ""CAPTCHA""], [""gmx"", ""Suspended: timeout""], [""mojeek"", ""Suspended: access denied""], [""openlibrary"", ""Suspended: timeout""], [""presearch"", ""Suspended: too many requests""], [""quark"", ""Suspended: CAPTCHA""], [""qwant"", ""Suspended: timeout""], [""seznam"", ""Suspended: timeout""], [""sogou"", ""Suspended: CAPTCHA""], [""tagesschau"", ""Suspended: HTTP connection error""], [""wiby"", ""Suspended: timeout""], [""wikibooks"", ""Suspended: timeout""], [""wikimini"", ""Suspended: timeout""], [""wikiquote"", ""Suspended: timeout""], [""wikisource"", ""Suspended: too many requests""], [""wikispecies"", ""Suspended: too many requests""], [""wikiversity"", ""Suspended: too many requests""], [""wikivoyage"", ""Suspended: too many requests""], [""wolframalpha"", ""Suspended: timeout""], [""yacy"", ""Suspended: timeout""], [""yandex"", ""Suspended: HTTP error""], [""zapmeta"", ""Suspended: access denied""]]",
|
||||||
|
crossref,cr,crossref,null,8.0,4506.0,200,True,18,[],
|
||||||
|
artstation,as,artstation,"""images""",,4655.0,200,True,20,[],
|
||||||
|
frinkiac,frk,frinkiac,null,,4668.4,200,False,0,[],
|
||||||
|
erowid,ew,xpath,[],,4713.1,200,False,0,[],
|
||||||
|
woxikon.de synonyme,woxi,xpath,"[""dictionaries""]",8.0,4721.1,200,False,0,"[[""woxikon.de synonyme"", ""access denied""]]",
|
||||||
|
apple app store,aps,apple_app_store,null,,4746.2,200,True,39,[],
|
||||||
|
springer nature,springer,springer,null,5,4768.7,200,False,229,"[[""bing"", ""Suspended: HTTP connection error""], [""brave"", ""Suspended: too many requests""], [""duckduckgo"", ""CAPTCHA""], [""gmx"", ""Suspended: timeout""], [""mojeek"", ""Suspended: access denied""], [""openlibrary"", ""Suspended: timeout""], [""presearch"", ""Suspended: too many requests""], [""quark"", ""Suspended: CAPTCHA""], [""qwant"", ""Suspended: timeout""], [""seznam"", ""Suspended: timeout""], [""sogou"", ""Suspended: CAPTCHA""], [""tagesschau"", ""Suspended: HTTP connection error""], [""wiby"", ""Suspended: timeout""], [""wikibooks"", ""Suspended: timeout""], [""wikidata"", ""Suspended: timeout""], [""wikimini"", ""Suspended: timeout""], [""wikiquote"", ""Suspended: timeout""], [""wikisource"", ""Suspended: timeout""], [""wikiversity"", ""Suspended: timeout""], [""wikivoyage"", ""Suspended: timeout""], [""wolframalpha"", ""Suspended: timeout""], [""yacy"", ""Suspended: timeout""], [""zapmeta"", ""Suspended: access denied""]]",
|
||||||
|
startpage,sp,startpage,"[""general"", ""web""]",,4790.7,200,True,10,[],
|
||||||
|
soundcloud,sc,soundcloud,null,,4814.0,200,True,9,[],
|
||||||
|
startpage news,spn,startpage,"[""news"", ""web""]",,4817.2,200,False,0,[],
|
||||||
|
pubmed,pub,pubmed,null,,4827.6,200,True,20,[],
|
||||||
|
reuters,reu,reuters,null,,4947.2,200,True,20,[],
|
||||||
|
wordnik,wnik,wordnik,null,8.0,4970.4,200,False,0,[],
|
||||||
|
apk mirror,apkm,apkmirror,null,8.0,5026.0,200,True,10,[],
|
||||||
|
habrahabr,habr,xpath,"""it""",8.0,5148.4,200,False,0,[],
|
||||||
|
github code,ghc,github_code,null,8.0,5502.6,200,False,225,"[[""bing"", ""Suspended: HTTP connection error""], [""brave"", ""Suspended: too many requests""], [""duckduckgo"", ""CAPTCHA""], [""gmx"", ""Suspended: timeout""], [""mojeek"", ""Suspended: access denied""], [""openlibrary"", ""Suspended: timeout""], [""presearch"", ""Suspended: too many requests""], [""quark"", ""Suspended: CAPTCHA""], [""qwant"", ""Suspended: timeout""], [""seznam"", ""Suspended: timeout""], [""sogou"", ""Suspended: CAPTCHA""], [""tagesschau"", ""Suspended: HTTP connection error""], [""wiby"", ""Suspended: timeout""], [""wikibooks"", ""Suspended: timeout""], [""wikidata"", ""Suspended: timeout""], [""wikimini"", ""Suspended: timeout""], [""wikiquote"", ""Suspended: timeout""], [""wikisource"", ""Suspended: timeout""], [""wikispecies"", ""too many requests""], [""wikiversity"", ""Suspended: timeout""], [""wikivoyage"", ""Suspended: timeout""], [""wolframalpha"", ""Suspended: timeout""], [""yacy"", ""Suspended: timeout""], [""zapmeta"", ""Suspended: access denied""]]",
|
||||||
|
stackoverflow,st,stackexchange,"[""it"", ""q&a""]",,5504.1,200,True,10,[],
|
||||||
|
apple maps,apm,apple_maps,null,8.0,5522.6,200,False,0,"[[""apple maps"", ""HTTP error""]]",
|
||||||
|
libretranslate,lt,libretranslate,null,,5534.1,200,False,191,"[[""360search"", ""Suspended: timeout""], [""aol"", ""Suspended: HTTP error""], [""baidu"", ""Suspended: CAPTCHA""], [""brave"", ""Suspended: too many requests""], [""duckduckgo"", ""CAPTCHA""], [""gmx"", ""Suspended: timeout""], [""mojeek"", ""Suspended: access denied""], [""openlibrary"", ""Suspended: timeout""], [""presearch"", ""Suspended: too many requests""], [""quark"", ""Suspended: CAPTCHA""], [""qwant"", ""Suspended: timeout""], [""seznam"", ""Suspended: timeout""], [""sogou"", ""Suspended: CAPTCHA""], [""tagesschau"", ""Suspended: HTTP connection error""], [""wiby"", ""Suspended: timeout""], [""wikibooks"", ""Suspended: timeout""], [""wikimini"", ""Suspended: timeout""], [""wikiquote"", ""Suspended: timeout""], [""wikisource"", ""too many requests""], [""wikispecies"", ""Suspended: too many requests""], [""wikiversity"", ""too many requests""], [""wikivoyage"", ""too many requests""], [""wolframalpha"", ""Suspended: timeout""], [""yacy"", ""Suspended: timeout""], [""yandex"", ""Suspended: HTTP error""], [""zapmeta"", ""Suspended: access denied""]]",
|
||||||
|
Torznab EZTV,eztv,torznab,null,,5610.6,200,False,191,"[[""360search"", ""Suspended: timeout""], [""aol"", ""Suspended: HTTP error""], [""baidu"", ""Suspended: CAPTCHA""], [""brave"", ""Suspended: too many requests""], [""duckduckgo"", ""CAPTCHA""], [""gmx"", ""Suspended: timeout""], [""mojeek"", ""Suspended: access denied""], [""openlibrary"", ""Suspended: timeout""], [""presearch"", ""Suspended: too many requests""], [""quark"", ""Suspended: CAPTCHA""], [""qwant"", ""Suspended: timeout""], [""seznam"", ""Suspended: timeout""], [""sogou"", ""Suspended: CAPTCHA""], [""tagesschau"", ""Suspended: HTTP connection error""], [""wiby"", ""Suspended: timeout""], [""wikibooks"", ""Suspended: timeout""], [""wikimini"", ""Suspended: timeout""], [""wikiquote"", ""Suspended: timeout""], [""wikisource"", ""Suspended: too many requests""], [""wikispecies"", ""Suspended: too many requests""], [""wikiversity"", ""Suspended: too many requests""], [""wikivoyage"", ""Suspended: too many requests""], [""wolframalpha"", ""Suspended: timeout""], [""yacy"", ""Suspended: timeout""], [""yandex"", ""Suspended: HTTP error""], [""zapmeta"", ""Suspended: access denied""]]",
|
||||||
|
core.ac.uk,cor,core,null,,5651.0,200,False,191,"[[""360search"", ""Suspended: timeout""], [""aol"", ""Suspended: HTTP error""], [""baidu"", ""Suspended: CAPTCHA""], [""brave"", ""Suspended: too many requests""], [""duckduckgo"", ""CAPTCHA""], [""gmx"", ""Suspended: timeout""], [""mojeek"", ""Suspended: access denied""], [""openlibrary"", ""Suspended: timeout""], [""presearch"", ""Suspended: too many requests""], [""quark"", ""Suspended: CAPTCHA""], [""qwant"", ""Suspended: timeout""], [""seznam"", ""Suspended: timeout""], [""sogou"", ""Suspended: CAPTCHA""], [""tagesschau"", ""Suspended: HTTP connection error""], [""wiby"", ""Suspended: timeout""], [""wikibooks"", ""Suspended: timeout""], [""wikimini"", ""Suspended: timeout""], [""wikiquote"", ""Suspended: timeout""], [""wikisource"", ""Suspended: too many requests""], [""wikispecies"", ""Suspended: too many requests""], [""wikiversity"", ""too many requests""], [""wikivoyage"", ""too many requests""], [""wolframalpha"", ""Suspended: timeout""], [""yacy"", ""Suspended: timeout""], [""yandex"", ""Suspended: HTTP error""], [""zapmeta"", ""Suspended: access denied""]]",
|
||||||
|
mojeek news,mjknews,mojeek,"[""news"", ""web""]",,5679.8,200,False,0,"[[""mojeek news"", ""access denied""]]",
|
||||||
|
npm,npm,npm,null,8.0,5705.4,200,True,25,[],
|
||||||
|
duden,du,duden,null,,5822.9,200,True,1,[],
|
||||||
|
library of congress,loc,loc,"""images""",,5912.4,200,False,0,"[[""library of congress"", ""parsing error""]]",
|
||||||
|
aol images,aoli,aol,"[""images""]",,6040.5,200,False,0,"[[""aol images"", ""HTTP error""]]",
|
||||||
|
voidlinux,void,voidlinux,null,,6242.5,200,True,1,[],
|
||||||
|
alpine linux packages,alp,alpinelinux,null,,6474.4,200,False,0,[],
|
||||||
|
marginalia,mar,marginalia,null,,6480.6,200,False,192,"[[""360search"", ""Suspended: timeout""], [""aol"", ""Suspended: HTTP error""], [""baidu"", ""Suspended: CAPTCHA""], [""brave"", ""Suspended: too many requests""], [""duckduckgo"", ""CAPTCHA""], [""gmx"", ""Suspended: timeout""], [""mojeek"", ""Suspended: access denied""], [""openlibrary"", ""Suspended: timeout""], [""presearch"", ""Suspended: too many requests""], [""quark"", ""Suspended: CAPTCHA""], [""qwant"", ""Suspended: timeout""], [""seznam"", ""Suspended: timeout""], [""sogou"", ""Suspended: CAPTCHA""], [""tagesschau"", ""Suspended: HTTP connection error""], [""wiby"", ""Suspended: timeout""], [""wikibooks"", ""Suspended: timeout""], [""wikimini"", ""Suspended: timeout""], [""wikiquote"", ""Suspended: timeout""], [""wikisource"", ""Suspended: too many requests""], [""wikispecies"", ""Suspended: too many requests""], [""wikiversity"", ""Suspended: too many requests""], [""wikivoyage"", ""Suspended: too many requests""], [""yacy"", ""Suspended: timeout""], [""yandex"", ""Suspended: HTTP error""], [""zapmeta"", ""Suspended: access denied""]]",
|
||||||
|
ipernity,ip,ipernity,null,,6577.7,200,False,0,"[[""ipernity"", ""timeout""]]",
|
||||||
|
gitea.com,gitea,gitea,null,,6647.2,200,True,10,[],
|
||||||
|
openstreetmap,osm,openstreetmap,null,,6784.4,200,True,2,[],
|
||||||
|
wikisource,ws,mediawiki,"[""general"", ""wikimedia""]",,6810.9,200,False,0,"[[""wikisource"", ""timeout""]]",
|
||||||
|
yandex music,ydm,yandex_music,null,,6882.8,200,False,0,"[[""yandex music"", ""HTTP error""]]",
|
||||||
|
cara,ca,cara,null,,6951.4,200,True,24,[],
|
||||||
|
1x,1x,www1x,null,8.0,6962.7,200,False,0,[],
|
||||||
|
startpage images,spi,startpage,"[""images"", ""web""]",,7363.1,200,True,49,[],
|
||||||
|
duckduckgo weather,ddw,duckduckgo_weather,null,,7374.6,200,False,0,"[[""duckduckgo weather"", ""timeout""]]",
|
||||||
|
wiby,wib,json_engine,"[""general"", ""web""]",,7413.0,200,False,0,"[[""wiby"", ""timeout""]]",
|
||||||
|
swisscows news,swn,swisscows_news,null,,7434.2,200,False,230,"[[""bing"", ""Suspended: HTTP connection error""], [""brave"", ""Suspended: too many requests""], [""duckduckgo"", ""CAPTCHA""], [""gmx"", ""Suspended: timeout""], [""mojeek"", ""Suspended: access denied""], [""openlibrary"", ""Suspended: timeout""], [""presearch"", ""Suspended: too many requests""], [""quark"", ""Suspended: CAPTCHA""], [""qwant"", ""Suspended: timeout""], [""seznam"", ""Suspended: timeout""], [""sogou"", ""Suspended: CAPTCHA""], [""tagesschau"", ""Suspended: HTTP connection error""], [""wiby"", ""Suspended: timeout""], [""wikibooks"", ""Suspended: timeout""], [""wikidata"", ""Suspended: timeout""], [""wikimini"", ""Suspended: timeout""], [""wikiquote"", ""Suspended: timeout""], [""wikisource"", ""Suspended: timeout""], [""wikiversity"", ""Suspended: timeout""], [""wikivoyage"", ""Suspended: timeout""], [""wolframalpha"", ""Suspended: timeout""], [""yacy"", ""Suspended: timeout""], [""zapmeta"", ""Suspended: access denied""]]",
|
||||||
|
wiktionary,wt,mediawiki,"[""dictionaries"", ""wikimedia""]",,7442.0,200,False,0,"[[""wiktionary"", ""timeout""]]",
|
||||||
|
genius,gen,genius,null,,7490.2,200,False,0,"[[""genius"", ""access denied""]]",
|
||||||
|
wikibooks,wb,mediawiki,"[""general"", ""wikimedia""]",,7744.2,200,False,0,"[[""wikibooks"", ""timeout""]]",
|
||||||
|
openalex,oa,openalex,null,8.0,7759.0,200,True,10,[],
|
||||||
|
fdroid,fd,fdroid,null,,7872.9,200,False,0,"[[""fdroid"", ""timeout""]]",
|
||||||
|
etymonline,et,xpath,"[""dictionaries""]",,7893.8,200,False,0,[],
|
||||||
|
wikiversity,wv,mediawiki,"[""general"", ""wikimedia""]",,8016.9,200,False,0,"[[""wikiversity"", ""Suspended: timeout""]]",
|
||||||
|
flickr_api,fla,flickr,"""images""",,8020.6,200,False,281,"[[""duckduckgo"", ""CAPTCHA""], [""gmx"", ""timeout""], [""openlibrary"", ""timeout""], [""qwant"", ""timeout""], [""seznam"", ""timeout""], [""tagesschau"", ""HTTP connection error""], [""wiby"", ""timeout""], [""wikibooks"", ""timeout""], [""wikidata"", ""timeout""], [""wikimini"", ""timeout""], [""wikiversity"", ""timeout""], [""wolframalpha"", ""timeout""], [""yacy"", ""timeout""], [""zapmeta"", ""access denied""]]",
|
||||||
|
codeberg,cb,gitea,null,,8024.5,200,False,0,"[[""codeberg"", ""timeout""]]",
|
||||||
|
bitbucket,bb,xpath,"[""it"", ""repos""]",8.0,8186.1,200,False,0,[],
|
||||||
|
openclipart,ocl,openclipart,null,8.0,8229.3,200,False,230,"[[""brave"", ""Suspended: too many requests""], [""duckduckgo"", ""CAPTCHA""], [""gmx"", ""Suspended: timeout""], [""mojeek"", ""Suspended: access denied""], [""openlibrary"", ""Suspended: timeout""], [""presearch"", ""Suspended: too many requests""], [""quark"", ""Suspended: CAPTCHA""], [""qwant"", ""Suspended: timeout""], [""seznam"", ""Suspended: timeout""], [""sogou"", ""Suspended: CAPTCHA""], [""tagesschau"", ""Suspended: HTTP connection error""], [""wiby"", ""Suspended: timeout""], [""wikibooks"", ""timeout""], [""wikimini"", ""Suspended: timeout""], [""wikiquote"", ""Suspended: timeout""], [""wikisource"", ""Suspended: timeout""], [""wikispecies"", ""Suspended: too many requests""], [""wikivoyage"", ""Suspended: timeout""], [""wolframalpha"", ""Suspended: timeout""], [""yacy"", ""Suspended: timeout""], [""zapmeta"", ""Suspended: access denied""]]",
|
||||||
|
public domain image archive,pdia,public_domain_image_archive,null,,8248.5,200,True,4,[],
|
||||||
|
wikinews,wn,mediawiki,"[""news"", ""wikimedia""]",,8255.1,200,False,0,"[[""wikinews"", ""timeout""]]",
|
||||||
|
wallhaven,wh,wallhaven,null,,8320.1,200,False,211,"[[""360search"", ""Suspended: timeout""], [""baidu"", ""Suspended: CAPTCHA""], [""brave"", ""Suspended: too many requests""], [""duckduckgo"", ""CAPTCHA""], [""gmx"", ""Suspended: timeout""], [""mojeek"", ""Suspended: access denied""], [""openlibrary"", ""Suspended: timeout""], [""presearch"", ""Suspended: too many requests""], [""quark"", ""Suspended: CAPTCHA""], [""qwant"", ""Suspended: timeout""], [""seznam"", ""Suspended: timeout""], [""sogou"", ""Suspended: CAPTCHA""], [""tagesschau"", ""Suspended: HTTP connection error""], [""wiby"", ""Suspended: timeout""], [""wikibooks"", ""Suspended: timeout""], [""wikimini"", ""Suspended: timeout""], [""wikiquote"", ""timeout""], [""wikispecies"", ""Suspended: too many requests""], [""wikivoyage"", ""Suspended: timeout""], [""wolframalpha"", ""Suspended: timeout""], [""yacy"", ""Suspended: timeout""], [""yandex"", ""Suspended: HTTP error""], [""zapmeta"", ""Suspended: access denied""]]",
|
||||||
|
lemmy communities,leco,lemmy,null,,8444.4,200,False,0,"[[""lemmy communities"", ""timeout""]]",
|
||||||
|
freesound,fnd,freesound,null,8.0,8760.3,200,False,263,"[[""bing"", ""HTTP connection error""], [""duckduckgo"", ""CAPTCHA""], [""gmx"", ""Suspended: timeout""], [""mojeek"", ""access denied""], [""openlibrary"", ""Suspended: timeout""], [""quark"", ""CAPTCHA""], [""qwant"", ""Suspended: timeout""], [""seznam"", ""Suspended: timeout""], [""tagesschau"", ""Suspended: HTTP connection error""], [""wiby"", ""Suspended: timeout""], [""wikibooks"", ""Suspended: timeout""], [""wikidata"", ""Suspended: timeout""], [""wikimini"", ""Suspended: timeout""], [""wikiquote"", ""timeout""], [""wikiversity"", ""Suspended: timeout""], [""wolframalpha"", ""Suspended: timeout""], [""yacy"", ""Suspended: timeout""], [""zapmeta"", ""Suspended: access denied""]]",
|
||||||
|
openverse,opv,openverse,"""images""",,8833.7,200,False,0,"[[""openverse"", ""timeout""]]",
|
||||||
|
tokyotoshokan,tt,tokyotoshokan,null,8.0,9010.8,200,False,0,"[[""tokyotoshokan"", ""timeout""]]",
|
||||||
|
astrophysics data system,ads,astrophysics_data_system,null,,9129.3,200,False,246,"[[""bing"", ""Suspended: HTTP connection error""], [""brave"", ""too many requests""], [""duckduckgo"", ""CAPTCHA""], [""gmx"", ""Suspended: timeout""], [""mojeek"", ""Suspended: access denied""], [""openlibrary"", ""Suspended: timeout""], [""quark"", ""Suspended: CAPTCHA""], [""qwant"", ""Suspended: timeout""], [""seznam"", ""Suspended: timeout""], [""tagesschau"", ""Suspended: HTTP connection error""], [""wiby"", ""Suspended: timeout""], [""wikibooks"", ""Suspended: timeout""], [""wikidata"", ""Suspended: timeout""], [""wikimini"", ""Suspended: timeout""], [""wikiquote"", ""Suspended: timeout""], [""wikisource"", ""Suspended: timeout""], [""wikiversity"", ""Suspended: timeout""], [""wikivoyage"", ""timeout""], [""wolframalpha"", ""Suspended: timeout""], [""yacy"", ""Suspended: timeout""], [""zapmeta"", ""Suspended: access denied""]]",
|
||||||
|
library genesis,lg,xpath,"""files""",8.0,9175.3,200,False,0,"[[""library genesis"", ""timeout""]]",
|
||||||
|
qwant news,qwn,qwant,"""news""",,9213.3,200,False,0,"[[""qwant news"", ""timeout""]]",
|
||||||
|
tootfinder,toot,tootfinder,null,,9497.9,200,False,0,"[[""tootfinder"", ""timeout""]]",
|
||||||
|
kickass,kc,kickass,null,8.0,9569.1,200,False,0,"[[""kickass"", ""timeout""]]",
|
||||||
|
cloudflareai,cfai,cloudflareai,null,8.0,9714.5,200,False,201,"[[""360search"", ""Suspended: timeout""], [""aol"", ""HTTP error""], [""baidu"", ""Suspended: CAPTCHA""], [""brave"", ""Suspended: too many requests""], [""duckduckgo"", ""CAPTCHA""], [""gmx"", ""Suspended: timeout""], [""mojeek"", ""Suspended: access denied""], [""openlibrary"", ""Suspended: timeout""], [""presearch"", ""Suspended: too many requests""], [""quark"", ""Suspended: CAPTCHA""], [""qwant"", ""Suspended: timeout""], [""seznam"", ""Suspended: timeout""], [""sogou"", ""Suspended: CAPTCHA""], [""tagesschau"", ""Suspended: HTTP connection error""], [""wiby"", ""Suspended: timeout""], [""wikibooks"", ""Suspended: timeout""], [""wikimini"", ""Suspended: timeout""], [""wikiquote"", ""Suspended: timeout""], [""wikispecies"", ""Suspended: too many requests""], [""wikiversity"", ""too many requests""], [""wolframalpha"", ""Suspended: timeout""], [""yacy"", ""Suspended: timeout""], [""yandex"", ""Suspended: HTTP error""], [""zapmeta"", ""Suspended: access denied""]]",
|
||||||
|
chinaso news,chinaso,chinaso,"[""news""]",,9739.6,200,False,230,"[[""360search"", ""timeout""], [""brave"", ""Suspended: too many requests""], [""duckduckgo"", ""CAPTCHA""], [""gmx"", ""Suspended: timeout""], [""mojeek"", ""Suspended: access denied""], [""openlibrary"", ""Suspended: timeout""], [""presearch"", ""Suspended: too many requests""], [""quark"", ""Suspended: CAPTCHA""], [""qwant"", ""Suspended: timeout""], [""seznam"", ""Suspended: timeout""], [""sogou"", ""Suspended: CAPTCHA""], [""tagesschau"", ""Suspended: HTTP connection error""], [""wiby"", ""Suspended: timeout""], [""wikibooks"", ""timeout""], [""wikimini"", ""Suspended: timeout""], [""wikisource"", ""Suspended: timeout""], [""wikispecies"", ""Suspended: too many requests""], [""wikivoyage"", ""Suspended: timeout""], [""wolframalpha"", ""Suspended: timeout""], [""yacy"", ""Suspended: timeout""], [""zapmeta"", ""Suspended: access denied""]]",
|
||||||
|
ina,in,ina,null,8.0,9839.0,200,False,0,"[[""ina"", ""timeout""]]",
|
||||||
|
arch linux wiki,al,archlinux,null,,9925.3,200,False,0,[],
|
||||||
|
grokipedia,gp,grokipedia,null,,9985.0,200,False,211,"[[""360search"", ""Suspended: timeout""], [""baidu"", ""CAPTCHA""], [""brave"", ""Suspended: too many requests""], [""duckduckgo"", ""CAPTCHA""], [""gmx"", ""Suspended: timeout""], [""mojeek"", ""Suspended: access denied""], [""openlibrary"", ""Suspended: timeout""], [""presearch"", ""Suspended: too many requests""], [""quark"", ""Suspended: CAPTCHA""], [""qwant"", ""Suspended: timeout""], [""seznam"", ""Suspended: timeout""], [""sogou"", ""Suspended: CAPTCHA""], [""tagesschau"", ""Suspended: HTTP connection error""], [""wiby"", ""Suspended: timeout""], [""wikibooks"", ""Suspended: timeout""], [""wikimini"", ""Suspended: timeout""], [""wikiquote"", ""timeout""], [""wikispecies"", ""Suspended: too many requests""], [""wikivoyage"", ""Suspended: timeout""], [""wolframalpha"", ""Suspended: timeout""], [""yacy"", ""Suspended: timeout""], [""yandex"", ""Suspended: HTTP error""], [""zapmeta"", ""Suspended: access denied""]]",
|
||||||
|
chinaso images,chinasoi,chinaso,"[""images""]",,9985.5,200,False,211,"[[""360search"", ""Suspended: timeout""], [""baidu"", ""CAPTCHA""], [""brave"", ""Suspended: too many requests""], [""duckduckgo"", ""CAPTCHA""], [""gmx"", ""Suspended: timeout""], [""mojeek"", ""Suspended: access denied""], [""openlibrary"", ""Suspended: timeout""], [""presearch"", ""Suspended: too many requests""], [""quark"", ""Suspended: CAPTCHA""], [""qwant"", ""Suspended: timeout""], [""seznam"", ""Suspended: timeout""], [""sogou"", ""Suspended: CAPTCHA""], [""tagesschau"", ""Suspended: HTTP connection error""], [""wiby"", ""Suspended: timeout""], [""wikibooks"", ""Suspended: timeout""], [""wikimini"", ""Suspended: timeout""], [""wikiquote"", ""timeout""], [""wikispecies"", ""Suspended: too many requests""], [""wikivoyage"", ""Suspended: timeout""], [""wolframalpha"", ""Suspended: timeout""], [""yacy"", ""Suspended: timeout""], [""yandex"", ""Suspended: HTTP error""], [""zapmeta"", ""Suspended: access denied""]]",
|
||||||
|
elasticsearch,els,elasticsearch,null,,10129.5,200,False,230,"[[""bing"", ""Suspended: HTTP connection error""], [""brave"", ""Suspended: too many requests""], [""duckduckgo"", ""CAPTCHA""], [""gmx"", ""Suspended: timeout""], [""mojeek"", ""Suspended: access denied""], [""openlibrary"", ""Suspended: timeout""], [""presearch"", ""too many requests""], [""quark"", ""Suspended: CAPTCHA""], [""qwant"", ""Suspended: timeout""], [""seznam"", ""Suspended: timeout""], [""sogou"", ""CAPTCHA""], [""tagesschau"", ""Suspended: HTTP connection error""], [""wiby"", ""Suspended: timeout""], [""wikibooks"", ""Suspended: timeout""], [""wikidata"", ""Suspended: timeout""], [""wikimini"", ""Suspended: timeout""], [""wikiquote"", ""Suspended: timeout""], [""wikisource"", ""Suspended: timeout""], [""wikiversity"", ""Suspended: timeout""], [""wikivoyage"", ""timeout""], [""wolframalpha"", ""Suspended: timeout""], [""yacy"", ""Suspended: timeout""], [""zapmeta"", ""Suspended: access denied""]]",
|
||||||
|
pixiv,pv,pixiv,null,,10197.7,200,False,263,"[[""bing"", ""Suspended: HTTP connection error""], [""duckduckgo"", ""CAPTCHA""], [""gmx"", ""Suspended: timeout""], [""mojeek"", ""Suspended: access denied""], [""openlibrary"", ""Suspended: timeout""], [""quark"", ""Suspended: CAPTCHA""], [""qwant"", ""Suspended: timeout""], [""seznam"", ""Suspended: timeout""], [""tagesschau"", ""Suspended: HTTP connection error""], [""wiby"", ""Suspended: timeout""], [""wikibooks"", ""Suspended: timeout""], [""wikidata"", ""Suspended: timeout""], [""wikimini"", ""Suspended: timeout""], [""wikiquote"", ""timeout""], [""wikiversity"", ""Suspended: timeout""], [""wolframalpha"", ""Suspended: timeout""], [""yacy"", ""Suspended: timeout""], [""zapmeta"", ""Suspended: access denied""]]",
|
||||||
|
openairedatasets,oad,json_engine,"""science""",8.0,10220.6,200,False,0,"[[""openairedatasets"", ""timeout""]]",
|
||||||
|
ahmia,ah,ahmia,"""onions""",8.0,10599.8,200,False,225,"[[""brave"", ""Suspended: too many requests""], [""duckduckgo"", ""CAPTCHA""], [""gmx"", ""timeout""], [""mojeek"", ""Suspended: access denied""], [""openlibrary"", ""timeout""], [""presearch"", ""Suspended: too many requests""], [""quark"", ""Suspended: CAPTCHA""], [""qwant"", ""timeout""], [""seznam"", ""timeout""], [""sogou"", ""Suspended: CAPTCHA""], [""tagesschau"", ""HTTP connection error""], [""wiby"", ""timeout""], [""wikibooks"", ""Suspended: timeout""], [""wikimini"", ""timeout""], [""wikiquote"", ""Suspended: timeout""], [""wikisource"", ""Suspended: timeout""], [""wikispecies"", ""Suspended: too many requests""], [""wikiversity"", ""timeout""], [""wikivoyage"", ""Suspended: timeout""], [""wolframalpha"", ""Suspended: timeout""], [""yacy"", ""timeout""], [""zapmeta"", ""Suspended: access denied""]]",
|
||||||
|
z-library,zlib,zlibrary,null,8.0,10885.9,200,False,225,"[[""brave"", ""Suspended: too many requests""], [""duckduckgo"", ""CAPTCHA""], [""gmx"", ""Suspended: timeout""], [""mojeek"", ""Suspended: access denied""], [""openlibrary"", ""Suspended: timeout""], [""presearch"", ""Suspended: too many requests""], [""quark"", ""Suspended: CAPTCHA""], [""qwant"", ""Suspended: timeout""], [""seznam"", ""Suspended: timeout""], [""sogou"", ""Suspended: CAPTCHA""], [""tagesschau"", ""Suspended: HTTP connection error""], [""wiby"", ""Suspended: timeout""], [""wikibooks"", ""Suspended: timeout""], [""wikimini"", ""Suspended: timeout""], [""wikiquote"", ""timeout""], [""wikispecies"", ""Suspended: too many requests""], [""wikivoyage"", ""Suspended: timeout""], [""wolframalpha"", ""Suspended: timeout""], [""yacy"", ""Suspended: timeout""], [""yandex"", ""Suspended: HTTP error""], [""zapmeta"", ""Suspended: access denied""]]",
|
||||||
|
wolframalpha_api,waa,wolframalpha_api,"""general""",8.0,10996.9,200,False,230,"[[""brave"", ""Suspended: too many requests""], [""duckduckgo"", ""CAPTCHA""], [""gmx"", ""timeout""], [""mojeek"", ""Suspended: access denied""], [""openlibrary"", ""timeout""], [""presearch"", ""Suspended: too many requests""], [""quark"", ""Suspended: CAPTCHA""], [""qwant"", ""timeout""], [""seznam"", ""timeout""], [""sogou"", ""Suspended: CAPTCHA""], [""tagesschau"", ""Suspended: HTTP connection error""], [""wiby"", ""timeout""], [""wikibooks"", ""Suspended: timeout""], [""wikimini"", ""timeout""], [""wikiquote"", ""Suspended: timeout""], [""wikisource"", ""Suspended: timeout""], [""wikispecies"", ""Suspended: too many requests""], [""wikivoyage"", ""Suspended: timeout""], [""wolframalpha"", ""Suspended: timeout""], [""yacy"", ""timeout""], [""zapmeta"", ""Suspended: access denied""]]",
|
||||||
|
openairepublications,oap,json_engine,"""science""",8.0,11178.7,200,False,0,"[[""openairepublications"", ""timeout""]]",
|
||||||
|
solidtorrents,solid,solidtorrents,null,8.0,11261.0,200,False,0,"[[""solidtorrents"", ""timeout""]]",
|
||||||
|
ebay,eb,ebay,null,5,12007.6,,False,0,[],ReadTimeout:
|
||||||
|
@@ -0,0 +1,10 @@
|
|||||||
|
engine,shortcut,backend,categories,configured_timeout,bang_query,elapsed_ms,status_code,ok,result_count,result_engines,unresponsive_engines,errors
|
||||||
|
360search,360so,360search,null,8.0,!360so OpenAI,572.9,200,True,5,"[""360search""]",[],
|
||||||
|
crowdview,cv,json_engine,"""general""",,!cv OpenAI,970.4,200,True,40,"[""crowdview""]",[],
|
||||||
|
yep,yep,yep,"""general""",,!yep OpenAI,1236.1,200,True,20,"[""yep""]",[],
|
||||||
|
duckduckgo news,ddn,duckduckgo_extra,"[""news""]",,!ddn OpenAI,1251.7,200,True,30,"[""duckduckgo news""]",[],
|
||||||
|
naver news,nvrn,naver,"[""news""]",,!nvrn OpenAI,1781.1,200,True,10,"[""naver news""]",[],
|
||||||
|
searchmysite,sms,xpath,"""general""",,!sms OpenAI,2062.9,200,True,10,"[""searchmysite""]",[],
|
||||||
|
mwmbl,mwm,mwmbl,null,,!mwm OpenAI,2474.9,200,True,34,"[""mwmbl""]",[],
|
||||||
|
reuters,reu,reuters,null,,!reu OpenAI,3044.4,200,True,20,"[""reuters""]",[],
|
||||||
|
startpage,sp,startpage,"[""general"", ""web""]",,!sp OpenAI,3639.6,200,True,10,"[""startpage""]",[],
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,46 @@
|
|||||||
|
# SearXNG expanded final report
|
||||||
|
|
||||||
|
- Generated: 2026-06-09T05:39:10.118473+00:00
|
||||||
|
- Verification total elapsed: 11088.5 ms
|
||||||
|
- Engine count: 26
|
||||||
|
- Tabs: general, news, it, science
|
||||||
|
- Pool maxsize: 80
|
||||||
|
- Timeout: request 6.0s / max 8.0s
|
||||||
|
|
||||||
|
## Category Searches
|
||||||
|
| Search | Elapsed ms | Results | Engines | Unresponsive |
|
||||||
|
|---|---:|---:|---|---|
|
||||||
|
| default_general | 1837.4 | 126 | 360search, bing, crowdview, mwmbl, searchmysite, startpage, yep | [] |
|
||||||
|
| news | 1681.6 | 58 | duckduckgo news, naver news, reuters | [] |
|
||||||
|
| it | 4012.5 | 215 | askubuntu, docker hub, github, gitlab, hackernews, mdn, microsoft learn, npm, pkg.go.dev, sourcehut, stackoverflow, superuser | [] |
|
||||||
|
| science | 4961.8 | 58 | arxiv, crossref, openalex, pubmed | [] |
|
||||||
|
|
||||||
|
## Per Engine Bang Checks
|
||||||
|
| Engine | Elapsed ms | Results | Engines | Unresponsive | Quality |
|
||||||
|
|---|---:|---:|---|---|---:|
|
||||||
|
| bing | 165.2 | 9 | bing | [] | 65.0 |
|
||||||
|
| searchmysite | 298.6 | 10 | searchmysite | [] | 70.0 |
|
||||||
|
| duckduckgo news | 379.5 | 30 | duckduckgo news | [] | 90.0 |
|
||||||
|
| 360search | 406.8 | 5 | 360search | [] | 45.0 |
|
||||||
|
| reuters | 472.4 | 20 | reuters | [] | 63.0 |
|
||||||
|
| github | 544.7 | 30 | github | [] | 63.0 |
|
||||||
|
| startpage | 1045.3 | 10 | startpage | [] | 61.0 |
|
||||||
|
| superuser | 1057.6 | 8 | superuser | [] | 39.0 |
|
||||||
|
| mwmbl | 1382.2 | 34 | mwmbl | [] | 82.0 |
|
||||||
|
| gitlab | 1461.2 | 20 | gitlab | [] | 61.0 |
|
||||||
|
| yep | 1492.8 | 20 | yep | [] | 90.0 |
|
||||||
|
| mdn | 1585.1 | 10 | mdn | [] | 43.0 |
|
||||||
|
| stackoverflow | 1598.3 | 10 | stackoverflow | [] | 43.0 |
|
||||||
|
| microsoft learn | 1620.8 | 10 | microsoft learn | [] | 43.0 |
|
||||||
|
| askubuntu | 1685.8 | 10 | askubuntu | [] | 43.0 |
|
||||||
|
| naver news | 1694.3 | 10 | naver news | [] | 61.0 |
|
||||||
|
| docker hub | 1762.3 | 10 | docker hub | [] | 33.0 |
|
||||||
|
| arxiv | 1905.2 | 10 | arxiv | [] | 43.0 |
|
||||||
|
| npm | 2053.9 | 25 | npm | [] | 63.0 |
|
||||||
|
| pkg.go.dev | 2071.5 | 50 | pkg.go.dev | [] | 47.0 |
|
||||||
|
| sourcehut | 2286.6 | 2 | sourcehut | [] | 27.0 |
|
||||||
|
| openalex | 3019.1 | 10 | openalex | [] | 42.0 |
|
||||||
|
| crowdview | 3596.9 | 40 | crowdview | [] | 84.0 |
|
||||||
|
| hackernews | 3723.3 | 30 | hackernews | [] | 63.0 |
|
||||||
|
| pubmed | 4589.8 | 20 | pubmed | [] | 62.0 |
|
||||||
|
| crossref | 4708.4 | 18 | crossref | [] | 53.22 |
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
engine,elapsed_ms,result_count,result_engines,unresponsive,quality_score
|
||||||
|
bing,165.2,9,"[""bing""]",[],65.0
|
||||||
|
searchmysite,298.6,10,"[""searchmysite""]",[],70.0
|
||||||
|
duckduckgo news,379.5,30,"[""duckduckgo news""]",[],90.0
|
||||||
|
360search,406.8,5,"[""360search""]",[],45.0
|
||||||
|
reuters,472.4,20,"[""reuters""]",[],63.0
|
||||||
|
github,544.7,30,"[""github""]",[],63.0
|
||||||
|
startpage,1045.3,10,"[""startpage""]",[],61.0
|
||||||
|
superuser,1057.6,8,"[""superuser""]",[],39.0
|
||||||
|
mwmbl,1382.2,34,"[""mwmbl""]",[],82.0
|
||||||
|
gitlab,1461.2,20,"[""gitlab""]",[],61.0
|
||||||
|
yep,1492.8,20,"[""yep""]",[],90.0
|
||||||
|
mdn,1585.1,10,"[""mdn""]",[],43.0
|
||||||
|
stackoverflow,1598.3,10,"[""stackoverflow""]",[],43.0
|
||||||
|
microsoft learn,1620.8,10,"[""microsoft learn""]",[],43.0
|
||||||
|
askubuntu,1685.8,10,"[""askubuntu""]",[],43.0
|
||||||
|
naver news,1694.3,10,"[""naver news""]",[],61.0
|
||||||
|
docker hub,1762.3,10,"[""docker hub""]",[],33.0
|
||||||
|
arxiv,1905.2,10,"[""arxiv""]",[],43.0
|
||||||
|
npm,2053.9,25,"[""npm""]",[],63.0
|
||||||
|
pkg.go.dev,2071.5,50,"[""pkg.go.dev""]",[],47.0
|
||||||
|
sourcehut,2286.6,2,"[""sourcehut""]",[],27.0
|
||||||
|
openalex,3019.1,10,"[""openalex""]",[],42.0
|
||||||
|
crowdview,3596.9,40,"[""crowdview""]",[],84.0
|
||||||
|
hackernews,3723.3,30,"[""hackernews""]",[],63.0
|
||||||
|
pubmed,4589.8,20,"[""pubmed""]",[],62.0
|
||||||
|
crossref,4708.4,18,"[""crossref""]",[],53.22
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,843 @@
|
|||||||
|
{
|
||||||
|
"query": "OpenAI",
|
||||||
|
"status_code": 200,
|
||||||
|
"elapsed_ms": 1351.8,
|
||||||
|
"result_count": 118,
|
||||||
|
"result_engines": [
|
||||||
|
"360search",
|
||||||
|
"crowdview",
|
||||||
|
"mwmbl",
|
||||||
|
"searchmysite",
|
||||||
|
"startpage",
|
||||||
|
"yep"
|
||||||
|
],
|
||||||
|
"unresponsive_engines": [],
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"title": "OpenAI - Wikipedia",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/OpenAI",
|
||||||
|
"content": "Artificial intelligence research organization OpenAI, Inc. is an American artificial intelligence (AI) organization founded in December 2015 and headquartered in San Francisco …",
|
||||||
|
"engine": "yep",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAI: Sora: First Impressions | Hacker News",
|
||||||
|
"url": "https://news.ycombinator.com/item?id=39818823",
|
||||||
|
"content": "11 hours ago ... > Below are a few examples of the artists' work, with early thoughts from them on how they see Sora fitting into their workflows and businesses.",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAI 官网入口地址:如何轻松找到",
|
||||||
|
"url": "https://apifox.com/apiskills/openai-official-portal/",
|
||||||
|
"content": "2025年10月26日 - https://openai.com/OpenAI中文文档:.https://openai.xiniushu.com/OpenAI 官网是一个信息丰富、内涵深度的网站,旨在推动人工智能技术的前沿发展与应...",
|
||||||
|
"engine": "360search",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Simon Willison’s Weblog",
|
||||||
|
"url": "https://simonwillison.net/",
|
||||||
|
"content": "... built this using OpenAI Codex desktop, which turns out to have the Markdown session transcript export ...",
|
||||||
|
"engine": "searchmysite",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAI | Research & Deployment",
|
||||||
|
"url": "https://openai.com/",
|
||||||
|
"content": "We believe our research will eventually lead to artificial general intelligence, a system that can solve human-level problems. Building safe and beneficial ...",
|
||||||
|
"engine": "startpage",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAI",
|
||||||
|
"url": "https://Openai.com/",
|
||||||
|
"content": "",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "How can I plot my daily cost on Azure OpenAI for a given OpenAI ...",
|
||||||
|
"url": "https://webapps.stackexchange.com/questions/171646/how-can-i-plot-my-daily-cost-on-azure-openai-for-a-given-openai-model-and-not-t",
|
||||||
|
"content": "Aug 10, 2023 ... How can I plot my daily cost on Azure OpenAI for a given OpenAI model (and not the sum for all OpenAI models)? ... I created several Azure OpenAI ...",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAI Codex - Wikipedia",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/OpenAI_Codex",
|
||||||
|
"content": "Artificial intelligence model geared towards programming OpenAI Codex is an artificial intelligence model developed by OpenAI that translates natural language into code …",
|
||||||
|
"engine": "yep",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAI首页、文档和下载 - 人工智能工具包 - OSCHINA - 中文开源技术交流...",
|
||||||
|
"url": "https://www.oschina.net/p/openai",
|
||||||
|
"content": "OpenAI is dedicated to creating a full suite of highly interoperable Artificial Intelligence components that make the best use of to...",
|
||||||
|
"engine": "360search",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Why I joined OpenAI",
|
||||||
|
"url": "https://www.brendangregg.com/blog/2026-02-07/why-i-joined-openai.html",
|
||||||
|
"content": "... Performance Tools book Recent posts: 07 Feb 2026 » Why I joined OpenAI 05 Dec 2025 » Leaving Intel ...",
|
||||||
|
"engine": "searchmysite",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "ChatGPT",
|
||||||
|
"url": "https://chatgpt.com/",
|
||||||
|
"content": "ChatGPT is your AI chatbot for everyday use. Chat with the most advanced AI to explore ideas, solve problems, and learn faster.",
|
||||||
|
"engine": "startpage",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAIRE - Connect",
|
||||||
|
"url": "http://connect.openaire.eu/",
|
||||||
|
"content": "",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Why isnt the ChatGPT application open source? : r/OpenAI",
|
||||||
|
"url": "https://www.reddit.com/r/OpenAI/comments/13sivk7/why_isnt_the_chatgpt_application_open_source/",
|
||||||
|
"content": "May 26, 2023 ... Why isnt the ChatGPT application open source? I've seen several users wondering why ChatGPT isn't open-source. From what I've gathered, OpenAI ...",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAI Opens GPT-3 for Everyone | Towards Data Science",
|
||||||
|
"url": "https://towardsdatascience.com/openai-opens-gpt-3-for-everyone-fb7fed309f6/",
|
||||||
|
"content": "OpenAI Opens GPT-3 for Everyone | Towards Data Science How to get in and what to expect. … OpenAI charges per token – either prompted to or generated by GPT-3.",
|
||||||
|
"engine": "yep",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAI教程-CSDN博客",
|
||||||
|
"url": "https://blog.csdn.net/p312011150/article/details/80826704",
|
||||||
|
"content": "openai gym 是一个增强学习(reinforcement learning,RL)算法的测试床(testbed).(2) environment:环境,也就是游戏本身,...",
|
||||||
|
"engine": "360search",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Welcome to Ethan Marcotte’s website — Ethan Marcotte",
|
||||||
|
"url": "https://ethanmarcotte.com/",
|
||||||
|
"content": "... 2010 Selected articles The OpenAI workers’ open letter, and what it means for tech labor WBUR ...",
|
||||||
|
"engine": "searchmysite",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Built to benefit everyone: our plan - OpenAI",
|
||||||
|
"url": "https://openai.com/index/built-to-benefit-everyone-our-plan/",
|
||||||
|
"content": "8 hours ago ... Our mission at OpenAI is to ensure that AGI benefits all of humanity. That means building systems that help people do more of what they choose, ...",
|
||||||
|
"engine": "startpage",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAIRE's Repository Manager",
|
||||||
|
"url": "https://provide.openaire.eu",
|
||||||
|
"content": "",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Ask HN: Does OpenAI make a profit on GPT-3.5-turbo API? | Hacker ...",
|
||||||
|
"url": "https://news.ycombinator.com/item?id=36162923",
|
||||||
|
"content": "From a recent podcast [0] it seems unlikely that they make profit from gpt-3.5-turbo, at least not in the sense that it pays for employee salaries.",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAI Charter – CyberIR@MIT",
|
||||||
|
"url": "https://cyberir.mit.edu/site/openai-charter/",
|
||||||
|
"content": "This is the charter of the California-based startup OpenAI, founded by Elon Musk, Sam Altman, and many others.",
|
||||||
|
"engine": "yep",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenIAI首页_开放式工业增强智能平台_OpenIAI",
|
||||||
|
"url": "http://www.openiai.com/",
|
||||||
|
"content": "工业增强智能(Industrial Augmented Intelligence,IAI)是将计算机视觉、机器学习、大模型、增强现实、具身智能等智能化技术赋能工业领域各环节,实现强化、提升复杂作业过程...",
|
||||||
|
"engine": "360search",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Late Takes on OpenAI o1",
|
||||||
|
"url": "https://www.alexirpan.com/2024/12/04/late-o1-thoughts.html",
|
||||||
|
"content": "... writing one despite it being cold. (Also, OpenAI just announced they’re going to ship new stuff starting ...",
|
||||||
|
"engine": "searchmysite",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAIRE Service Catalogue",
|
||||||
|
"url": "http://catalogue.openaire.eu",
|
||||||
|
"content": "",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Practical report: the OpenAI API is a bad joke. If you think you can ...",
|
||||||
|
"url": "https://news.ycombinator.com/item?id=36622020",
|
||||||
|
"content": "You should apply and use OpenAI on azure. We've got close to 1m tokens per minute capacity across 3 instances and the latency is totally fine, like 800ms ...",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAI acquires Software Applications Incorporated, maker of Sky",
|
||||||
|
"url": "https://openai.com/index/openai-acquires-software-applications-incorporated/",
|
||||||
|
"content": "OpenAI acquires Software Applications Incorporated, maker of Sky | OpenAI That’s why we’re excited to share that OpenAI has acquired Software Applications Incorporated …",
|
||||||
|
"engine": "yep",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAI - 知乎",
|
||||||
|
"url": "https://www.zhihu.com/topic/20083046/hot",
|
||||||
|
"content": "OpenAI是一家人工智能公司,成立于2015年12月。OpenAI会和谷歌、苹果、IBM等知名公司创办的其它一系列项目一道探索先进计算机技术,解决面部识别或语言翻译等问题。2015年12月12日,非盈...",
|
||||||
|
"engine": "360search",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "How I run multiple $10K MRR companies on a $20/month tech stack | Steve Hanov's Blog",
|
||||||
|
"url": "https://stevehanov.ca/blog/how-i-run-multiple-10k-mrr-companies-on-a-20month-tech-stack",
|
||||||
|
"content": "... all of this at the OpenAI API. I could have paid hundreds of dollars in API credits, only to find a ...",
|
||||||
|
"engine": "searchmysite",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Careers - OpenAI",
|
||||||
|
"url": "https://openai.com/careers/search/",
|
||||||
|
"content": "Careers | OpenAI. Careers at OpenAI. 721 jobs. All teams. All locations. 3D Printing Lab Technician, Robotics. Robotics. San FranciscoApply now (opens in a new ...",
|
||||||
|
"engine": "startpage",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAIRE Graph - Home",
|
||||||
|
"url": "http://graph.openaire.eu/",
|
||||||
|
"content": "Join the Graph User Forum A 360o view of research Publications, research data, software, protocols and other research outcomes interlinked, and all linke…",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Debunking the Microsoft/OpenAI/Google hype and over-reaction ...",
|
||||||
|
"url": "https://news.ycombinator.com/item?id=34758862",
|
||||||
|
"content": "Feb 12, 2023 ... OpenAI is essentially a Microsoft AI division and having a AI model behind a SaaS is hardly revolutionary. I expect this AI hype to be short ...",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Microsoft invests $1 billion in OpenAI, which is going all-in on Azure",
|
||||||
|
"url": "https://www.zdnet.com/article/microsoft-invests-1-billion-in-openai-which-is-going-all-in-on-azure/",
|
||||||
|
"content": "Microsoft is investing $1 billion in the OpenAI company In exchange, it's getting commitments from OpenAI to make Microsoft its …",
|
||||||
|
"engine": "yep",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Benedict Evans",
|
||||||
|
"url": "https://www.ben-evans.com/",
|
||||||
|
"content": "... , what matters, and what it might mean. Essays 19 February 2026 How will OpenAI compete? 19 ...",
|
||||||
|
"engine": "searchmysite",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAI - LinkedIn",
|
||||||
|
"url": "https://www.linkedin.com/company/openai",
|
||||||
|
"content": "OpenAI is an AI research and deployment company dedicated to ensuring that general-purpose artificial intelligence benefits all of humanity.",
|
||||||
|
"engine": "startpage",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAIRE | Monitor",
|
||||||
|
"url": "https://monitor.openaire.eu",
|
||||||
|
"content": "Simplify research monitoring & evaluation. Monitor, discover and understand. Track your organization’s research output in a comprehensive manner. Identif…",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "$900k Median Package for Engineers at OpenAI | Hacker News",
|
||||||
|
"url": "https://news.ycombinator.com/item?id=36460082",
|
||||||
|
"content": "OpenAI might pay its engineers $900k, but an AI startup founder can easily get to a $5M, $10M valuation in under a year.",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "AI Agent Tools Directory",
|
||||||
|
"url": "https://claude.ai/public/artifacts/2b5f610d-a357-498a-b8f6-02a83d7ed1be",
|
||||||
|
"content": "| OpenAI GPT-4o / Claude 3 | Code Synthesis, Feature Engineering Suggestions | OpenAI / Anthropic | \\$20/month or API (\\$5–30/1M tokens) |",
|
||||||
|
"engine": "yep",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Manton Reece",
|
||||||
|
"url": "https://www.manton.org/",
|
||||||
|
"content": "... , as I’ve blogged before OpenAI has attempted to do more with open models and broad access to their API ...",
|
||||||
|
"engine": "searchmysite",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAI - Hugging Face",
|
||||||
|
"url": "https://huggingface.co/openai",
|
||||||
|
"content": "OpenAI · OpenAI on Hugging Face. Welcome to the official Hugging Face organization for OpenAI's open models!. New open-weight language models: gpt-oss-120b ...",
|
||||||
|
"engine": "startpage",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAIRE LOD Services",
|
||||||
|
"url": "http://lod.openaire.eu/",
|
||||||
|
"content": "",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAI's board has fired Sam Altman | Hacker News",
|
||||||
|
"url": "https://news.ycombinator.com/item?id=38309611",
|
||||||
|
"content": "Another source [1] claims: \"A knowledgeable source said the board struggle reflected a cultural clash at the organization, with Altman and Brockman focused on ...",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "ChatGPT API 文档,OpenAI API 文档",
|
||||||
|
"url": "https://apifox.com/apiskills/chatgpt-api/",
|
||||||
|
"content": "OpenAI 宣布 chatGPT开放! OpenAI 近期宣布,它现在允许第三方开发者通过 API 将 ChatGPT 集成到他们的应用程序和服务中,这样做将比使用现有的语言模型便宜得多。 OpenAI 中文文档: 在 OpenAI 还没开放 API 的时候,虽然我们能够与 ChatGPT 交流 …",
|
||||||
|
"engine": "yep",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Peter Steinberger",
|
||||||
|
"url": "https://steipete.me/",
|
||||||
|
"content": "... Steinberger on BlueSky Peter Steinberger on LinkedIn Send an email to Peter Steinberger OpenClaw, OpenAI and ...",
|
||||||
|
"engine": "searchmysite",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAI Platform",
|
||||||
|
"url": "https://platform.openai.com/",
|
||||||
|
"content": "Build on the OpenAI API Platform. Sign up or login with an OpenAI account to build with the OpenAI API. Email address. Continue. Or. Continue with Google",
|
||||||
|
"engine": "startpage",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAIRE Guidelines — OpenAIRE Guidelines documentation",
|
||||||
|
"url": "http://guidelines.openaire.eu",
|
||||||
|
"content": "Welcome to the OpenAIRE Guidelines. The intention of this is to provide a public space to share OpenAIREs work on interoperability and to engage with the…",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Ask HN: Is anyone else bearish on OpenAI? | Hacker News",
|
||||||
|
"url": "https://news.ycombinator.com/item?id=38226030",
|
||||||
|
"content": "There is a lot of hype, it not good at every problem, but it is quite good at some of them. If you have a classical NLP task, then it is good, particularly GPT4 ...",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Everything OpenAI announced at DevDay 2025: AgentKit, Apps SDK, ChatGPT, and more",
|
||||||
|
"url": "https://www.zdnet.com/article/everything-openai-announced-at-devday-2025-agent-kit-apps-sdk-chatgpt-and-more/",
|
||||||
|
"content": "OpenAI held its DevDay 2025 event this week on Oct. 6, and the company had plenty to share regarding how developers can build around its AI platform and various models.",
|
||||||
|
"engine": "yep",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Andrej Karpathy",
|
||||||
|
"url": "https://karpathy.ai/",
|
||||||
|
"content": "... back to OpenAI where I built a new team working on midtraining and synthetic data generation. 2017 ...",
|
||||||
|
"engine": "searchmysite",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAI - YouTube",
|
||||||
|
"url": "https://www.youtube.com/@OpenAI",
|
||||||
|
"content": "OpenAI's mission is to ensure that artificial general intelligence benefits all of humanity. ...more OpenAI's mission is to ensure that artificial general ...",
|
||||||
|
"engine": "startpage",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAIRE Blogs - OpenAIRE Blog",
|
||||||
|
"url": "http://www.openaire.eu/blogs",
|
||||||
|
"content": "Open Positions Recent News-Activities-Blogs The Creating Knowledge 2024 conference, held from 5-7 June 2024 at the University of Helsinki, focused on \"Th…",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "This is a surprisingly unintelligent move from OpenAI. It adds ...",
|
||||||
|
"url": "https://news.ycombinator.com/item?id=22193647",
|
||||||
|
"content": "Jan 30, 2020 ... Hopefully PyTorch will start offering better support for tensor processors like google's TPUs, but from the sound of it, OpenAI is primarily ...",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Cracking Open the OpenAI (Python) API | Towards Data Science",
|
||||||
|
"url": "https://towardsdatascience.com/cracking-open-the-openai-python-api-230e4cae7971/",
|
||||||
|
"content": "Cracking Open the OpenAI (Python) API | Towards Data Science A complete beginner-friendly introduction with example code [Skip to content](",
|
||||||
|
"engine": "yep",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Aaron Parecki",
|
||||||
|
"url": "https://aaronparecki.com/",
|
||||||
|
"content": "... provider — OpenAI, Anthropic, Cursor, or any trusted agent platform — attests to the user's identity at ...",
|
||||||
|
"engine": "searchmysite",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "About - OpenAI",
|
||||||
|
"url": "https://openai.com/about/",
|
||||||
|
"content": "OpenAI is an AI research and deployment company. Our mission is to ensure that artificial general intelligence benefits all of humanity.",
|
||||||
|
"engine": "startpage",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAIRE | Find and Share research",
|
||||||
|
"url": "http://explore.openaire.eu",
|
||||||
|
"content": "Connect all your research. If you can't find your research results in OpenAIRE, don't worry! Use our Link service, that reaches out to many external sourc…",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Ask HN: OpenAI and Profitability? | Hacker News",
|
||||||
|
"url": "https://news.ycombinator.com/item?id=35223549",
|
||||||
|
"content": "Mar 19, 2023 ... OpenAI went from non-for-profit in 2015 to capped-profit in 2019. So the max profit they're making is at most 100x ...",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "GPT-4o vs. GPT-4: The Ultimate Advances in AI",
|
||||||
|
"url": "https://neuroflash.com/blog/gpt-4o-vs-gpt-4-the-ultimate-advances-in-ai/",
|
||||||
|
"content": "OpenAI Technology … Explore the cutting-edge features and capabilities of GPT-4o model, by OpenAI. … OpenAI unveiled its latest and most advanced model, the GPT-4o",
|
||||||
|
"engine": "yep",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAIRE | Sustainable Development Goals",
|
||||||
|
"url": "http://aurora.openaire.eu/sdgs",
|
||||||
|
"content": "Science for UN Sustainable Development Goals Laying the foundation for new approaches and solutions. We have developed a classification scheme for UN Sus…",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAI Tokenizer | Hacker News",
|
||||||
|
"url": "https://news.ycombinator.com/item?id=35453400",
|
||||||
|
"content": "That is, byte pair encoding tokenization is itself based on how common it is to see particular characters in sequential order in the training data. Thus, if the ...",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "GitHub - oracle-samples/oci-openai: The OCI-OpenAI simplifies integration between OpenAI’s Python SDK and Oracle Cloud Infrastructure (OCI) GenAI service by providing robust authentication and authorization utilities.",
|
||||||
|
"url": "https://github.com/oracle-samples/oci-openai",
|
||||||
|
"content": "OCI OpenAI Python library provides secure and convenient access to the OpenAI-compatible REST API hosted by … oci-openai … Using the OCI OpenAI Synchronous Client",
|
||||||
|
"engine": "yep",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAIRE",
|
||||||
|
"url": "https://openaire.eu",
|
||||||
|
"content": "Join OpenAIRE leadership, policy voices, and community representatives for an open conversation about what comes next, and the role we all play in shapin…",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "How can I configure an Azure OpenAI resource so that one cannot ...",
|
||||||
|
"url": "https://webapps.stackexchange.com/questions/171557/how-can-i-configure-an-azure-openai-resource-so-that-one-cannot-spend-more-than",
|
||||||
|
"content": "Aug 5, 2023 ... 1 Answer 1 ... One cannot configure an Azure OpenAI resource so that one cannot spend more than some given amount of money on it, aside from ...",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Stargate advances with 4.5 GW partnership with Oracle",
|
||||||
|
"url": "https://openai.com/index/stargate-advances-with-partnership-with-oracle/",
|
||||||
|
"content": "Stargate advances with 4.5 GW partnership with Oracle | OpenAI … Oracle and OpenAI have entered an agreement to develop 4.5 gigawatts of additional Stargate data center capacity in the U.S.",
|
||||||
|
"engine": "yep",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAiRE | Investment Properties and Luxury Vacation Homes",
|
||||||
|
"url": "https://openaire.co/",
|
||||||
|
"content": "OpenAiRE OpenAiRE is an elevated boutique real estate brokerage based in West Hollywood, CA. Following the success of our sister company Open Air Homes, …",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "united states - OpenAI employees' move to Microsoft: Non-compete ...",
|
||||||
|
"url": "https://law.stackexchange.com/questions/97417/openai-employees-move-to-microsoft-non-compete-regulations",
|
||||||
|
"content": "Nov 21, 2023 ... 1 Answer 1 ... Non-compete clauses are not enforceable in California unless it in conjunction with the purchase of a business. This has been true ...",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAI compatibility | Gemini API | Google AI for Developers",
|
||||||
|
"url": "https://ai.google.dev/gemini-api/docs/openai",
|
||||||
|
"content": "({ … }); … () … // Poll until video is ready … // From the create call … () … ({ … }); … ()",
|
||||||
|
"engine": "yep",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAIs GPT-4 ist ein sichereres und nützlicheres ChatGPT",
|
||||||
|
"url": "https://the-decoder.de/?p=12491",
|
||||||
|
"content": "",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "open ai - OpenAI Gym: How is \"experience\" stored? - Artificial ...",
|
||||||
|
"url": "https://ai.stackexchange.com/questions/4126/openai-gym-how-is-experience-stored",
|
||||||
|
"content": "Sep 27, 2017 ... Edit: OpenAI does not offer anything to store experience out of the box, as I noted in my comment you could pretty easily whip up some code that ...",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Video: Jury Rejects Elon Musk’s Lawsuit Against OpenAI and Microsoft",
|
||||||
|
"url": "https://www.nytimes.com/video/business/media/100000010911130/openai-musk-trial-vertict.html",
|
||||||
|
"content": "new video loaded: Jury Rejects Elon Musk’s Lawsuit Against OpenAI and Microsoft … ## Jury Rejects Elon Musk’s Lawsuit Against OpenAI and Microsoft",
|
||||||
|
"engine": "yep",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAI – FlowingData",
|
||||||
|
"url": "https://flowingdata.com/tag/openai/",
|
||||||
|
"content": "",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Valid actions in OpenAI Gym - Data Science Stack Exchange",
|
||||||
|
"url": "https://datascience.stackexchange.com/questions/61618/valid-actions-in-openai-gym",
|
||||||
|
"content": "Oct 11, 2019 ... 1 Answer 1 ... In general, if the agent is simply not able to take non-valid actions in a given environment (e.g. due to strict rules of a game, ...",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Jony Ive Says He Wants His OpenAI Devices to ‘Make Us Happy’",
|
||||||
|
"url": "https://www.wired.com/story/sam-altman-and-jony-ives-ai-device-dev-day/",
|
||||||
|
"content": "Earlier reporting indicated that OpenAI is planning to manufacture a new category of hardware that doesn’t resemble a phone or laptop.",
|
||||||
|
"engine": "yep",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAI Five defeats Dota 2 world champions",
|
||||||
|
"url": "http://openai.com/five/",
|
||||||
|
"content": "Quick Links OpenAI Five defeats Dota 2 world champions OpenAI Five is the first AI to beat the world champions in an esports game, having won two back-to…",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "llm - Do I need a paid OpenAI account to use the LLMFunction ...",
|
||||||
|
"url": "https://mathematica.stackexchange.com/questions/296455/do-i-need-a-paid-openai-account-to-use-the-llmfunction",
|
||||||
|
"content": "Jan 15, 2024 ... 1 Answer 1 ... requires external service authentication, billing and internet connectivity. ... TextSummarize used to condense textual information.",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAI shuts down election influence operation that used ChatGPT | TechCrunch",
|
||||||
|
"url": "https://techcrunch.com/2024/08/16/openai-shuts-down-election-influence-operation-using-chatgpt/",
|
||||||
|
"content": "OpenAI has banned a cluster of ChatGPT accounts linked to an Iranian influence operation that was generating content about the U.S. presidential election,",
|
||||||
|
"engine": "yep",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAIRE OA week 2016 webinars",
|
||||||
|
"url": "https://goo.gl/HIcpJT",
|
||||||
|
"content": "OpenAIRE is hosting a series of webinars during Open Access Week 2016. Register here for these webinars (select the ones you want to attend) and we will …",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "robots.txt - Is it possible to exclude just OpenAI - ChatGPT from ...",
|
||||||
|
"url": "https://webmasters.stackexchange.com/questions/142359/is-it-possible-to-exclude-just-openai-chatgpt-from-scraping-my-website",
|
||||||
|
"content": "May 13, 2023 ... 2 Answers 2 ... Yes, it is possible both through a robots.txt declaration and to be restricted at the IP address level. ... Thanks for the pointer!",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Sam Altman Says ChatGPT Is on Track to Out-Talk Humanity",
|
||||||
|
"url": "https://www.wired.com/story/sam-altman-says-chatgpt-is-on-track-to-out-talk-humanity/",
|
||||||
|
"content": "OpenAI raised $40 billion … OpenAI will likely spend trillions of dollars on data centers alone in the “not very distant future,” Altman said.",
|
||||||
|
"engine": "yep",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAIRE Graph Community Call",
|
||||||
|
"url": "https://shorturl.at/ozLZ6",
|
||||||
|
"content": "OpenAIRE Graph Community Call Export Event Got questions or suggestions for the OpenAIRE Graph? Join us in the very first OpenAIRE Graph Community Call! …",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "machine learning - How to create custom action space in openai ...",
|
||||||
|
"url": "https://datascience.stackexchange.com/questions/114241/how-to-create-custom-action-space-in-openai-gym",
|
||||||
|
"content": "Sep 8, 2022 ... How to create custom action space in openai.gym ... I understand that in the new version the spaces have to be inherited from gym.spaces class.",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAI API key not working in my React App",
|
||||||
|
"url": "https://stackoverflow.com/questions/77131172/openai-api-key-not-working-in-my-react-app",
|
||||||
|
"content": "Note: OpenAI NodeJS SDK The solutions to these problems I provide below differ depending on whether you use OpenAI NodeJS SDK To check your OpenAI NodeJS SDK version …",
|
||||||
|
"engine": "yep",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Removal of Sam Altman from OpenAI",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Removal_of_Sam_Altman_from_OpenAI",
|
||||||
|
"content": "\"the board no longer has confidence in his ability to continue leading OpenAI\". The removal was predicated by employee concerns about his handling of",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "How can I view at once the cost incurred by all my Azure OpenAI ...",
|
||||||
|
"url": "https://webapps.stackexchange.com/questions/172159/how-can-i-view-at-once-the-cost-incurred-by-all-my-azure-openai-resources-for-th",
|
||||||
|
"content": "Sep 25, 2023 ... 1 Answer 1 · One can view the cost of all resources assigned to a subscription by going to https://portal.azure.com/#home, and clicking on the ...",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "2026 年如何免费使用 OpenAI API 密钥",
|
||||||
|
"url": "https://apifox.com/apiskills/openai-api-key-free/",
|
||||||
|
"content": ":这些免费的 OpenAI API 密钥可能有严格的使用限制,并且很快会过期。 … 这些平台将免费的 OpenAI API 密钥集成到生态系统中,提供了另一种体验 Open AI API 免费服务的途径。 … :确保该平台提供真正的免费 OpenAI API 密钥访问,并提供清晰的免费 OpenAI API 定价信息。",
|
||||||
|
"engine": "yep",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAIS",
|
||||||
|
"url": "https://zeldor.biz/tag/openais/",
|
||||||
|
"content": "",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Why did the openai's gym website close? - Artificial Intelligence ...",
|
||||||
|
"url": "https://ai.stackexchange.com/questions/4027/why-did-the-openais-gym-website-close",
|
||||||
|
"content": "Sep 13, 2017 ... 1 Answer 1 ... According to Open AI's Greg Brockman, the Gym website never had a big impact and so was never maintained. This is the reason he ...",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Openaire | lfx.advpic.eu.",
|
||||||
|
"url": "https://lfx.advpic.eu/openaire",
|
||||||
|
"content": "Openaire Book now at Openaire in Los Angeles, CA. Explore menu, see photos and read 2188 reviews: \"😍😍😍😍😍😍😍😍😍😍😍😍😍 I loved this place, the attention and th…",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "What has replaced OpenAI Retro Gym? : r/reinforcementlearning",
|
||||||
|
"url": "https://www.reddit.com/r/reinforcementlearning/comments/11whc6d/what_has_replaced_openai_retro_gym/",
|
||||||
|
"content": "Mar 20, 2023 ... What has replaced OpenAI Retro Gym? OpenAI Retro Gym hasn't been updated in years, despite being high profile enough to garner 3k stars. It ...",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "432877 – problems with checkpoint feature of openAIS",
|
||||||
|
"url": "http://bugzilla.redhat.com/show_bug.cgi?id=432877",
|
||||||
|
"content": "",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Azure openAI service - how long will they need to process my ...",
|
||||||
|
"url": "https://www.reddit.com/r/AZURE/comments/13so27w/azure_openai_service_how_long_will_they_need_to/",
|
||||||
|
"content": "May 27, 2023 ... There's a long waiting list for access. If you're a partner with MS, or having existing agreements with MS, then it's likely your applications ...",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Openais - Alteeve Wiki",
|
||||||
|
"url": "https://alteeve.ca/w/Openais",
|
||||||
|
"content": "Openais OpenAIS is the open-source implementation of the SA Forum's Application Interface Specification. It's a vendor-neutral specification that is used…",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Just released: Sora first use outside of OpenAI : r/OpenAI",
|
||||||
|
"url": "https://www.reddit.com/r/OpenAI/comments/1bnjnbd/just_released_sora_first_use_outside_of_openai/",
|
||||||
|
"content": "12 hours ago ... 490 votes, 116 comments. 1.2M subscribers in the OpenAI community. OpenAI is an AI research and deployment company. OpenAI's mission is to ...",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAIs Archives - AIPressRoom",
|
||||||
|
"url": "https://aipressroom.com/tag/openais/",
|
||||||
|
"content": "",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Dota 2 is like Chess, so OpenAi will always win against humans or ...",
|
||||||
|
"url": "https://www.reddit.com/r/TrueDoTA2/comments/oni0sj/dota_2_is_like_chess_so_openai_will_always_win/",
|
||||||
|
"content": "Jul 19, 2021 ... OpenAI being able to beat humans when its play a severely limited game basically attempts to remove our biggest advantage of being able to use ...",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAIRE at the EC Consultation Workshop \"Skills and Human Resou…",
|
||||||
|
"url": "https://www.slideshare.net/slideshow/openaire-at/26607734",
|
||||||
|
"content": "4. Current status: data skills Training for data managers to support ‘data scientists’ is in short supply at European level – especially within the libra…",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Python on Visual Studio Code \"ImportError: No module named ...",
|
||||||
|
"url": "https://www.reddit.com/r/learnpython/comments/u49tpx/python_on_visual_studio_code_importerror_no/",
|
||||||
|
"content": "Apr 15, 2022 ... Installed with \"pip3 install openai\" also \"pip install openai\". Looked at the version I'm using, and choose that version on VS \"Python ...",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAIRE - CONNECTing scientific results in support of Open Scie…",
|
||||||
|
"url": "https://doi.org/10.3030/731011",
|
||||||
|
"content": "Objective Open Science is around the corner. Scientists and organizations see it as a way to speed up, improve quality and reward, while policy makers se…",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Who are the actual members of OpenAI's board of directors and ...",
|
||||||
|
"url": "https://www.reddit.com/r/OpenAI/comments/17xuhho/who_are_the_actual_members_of_openais_board_of/",
|
||||||
|
"content": "Nov 18, 2023 ... OpenAI's board of directors consists of OpenAI chief scientist Ilya Sutskever, independent directors Quora CEO Adam D'Angelo, technology ...",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAIRE - Wikibooks, open books for an open world",
|
||||||
|
"url": "https://en.wikibooks.org/wiki/OpenAIRE",
|
||||||
|
"content": "",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "My Account Has Been Banned and OpenAI Won't Tell Me Why : r ...",
|
||||||
|
"url": "https://www.reddit.com/r/OpenAI/comments/18vrbyo/my_account_has_been_banned_and_openai_wont_tell/",
|
||||||
|
"content": "Jan 1, 2024 ... “Write an email that reads as if a lawyer had written it. The email is addressed towards the legal team of OpenAI. In this email, request your ...",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAI · GitHub",
|
||||||
|
"url": "http://github.com/openai",
|
||||||
|
"content": "Saved searches Use saved searches to filter your results more quickly You signed in with another tab or window. Reload to refresh your session.You signed…",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Why does OpenAI CTO make that face when asked about \"What ...",
|
||||||
|
"url": "https://www.reddit.com/r/OpenAI/comments/1bnhyl1/why_does_openai_cto_make_that_face_when_asked/",
|
||||||
|
"content": "13 hours ago ... OpenAI is currently being sued over training data; it seems like a no brainer to me that she was specifically told not to say a word about it ...",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAIRE Guidelines for CRIS Managers 1.2",
|
||||||
|
"url": "https://zenodo.org/records/8050936",
|
||||||
|
"content": "OpenAIRE Guidelines for CRIS Managers 1.2 Creators Description The Guidelines provide orientation for CRIS managers to expose their metadata in a way tha…",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Sam Altman - if i start going off, the openai board should go after me ...",
|
||||||
|
"url": "https://www.reddit.com/r/OpenAI/comments/17xzitw/sam_altman_if_i_start_going_off_the_openai_board/",
|
||||||
|
"content": "Nov 18, 2023 ... Sam Altman - if i start going off, the openai board should go after me for the full value of my shares · Reaffirms and pushes a narrative that ...",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "The Noonification: What is OpenAIs Whisper Model? (10/6/2022) | …",
|
||||||
|
"url": "https://hackernoon.com/10-6-2022-noonification",
|
||||||
|
"content": "The Noonification: What is OpenAIs Whisper Model? (10/6/2022) Too Long; Didn't Read People Mentioned Company Mentioned How are you, hacker? 🪐What's happe…",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "How to make a DSAR request to OpenAI to get my data they have ...",
|
||||||
|
"url": "https://www.reddit.com/r/gdpr/comments/13wy23c/how_to_make_a_dsar_request_to_openai_to_get_my/",
|
||||||
|
"content": "May 31, 2023 ... How to make a DSAR request to OpenAI to get my data they have collected · Send a written request to OpenAI via email or contact form. · OpenAI ...",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "2022 OpenAIs Archives - Latest News",
|
||||||
|
"url": "https://gettotext.com/tag/openais/",
|
||||||
|
"content": "",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Would you watch a movie entirely filmed in Sora? : r/OpenAI",
|
||||||
|
"url": "https://www.reddit.com/r/OpenAI/comments/1bls6h6/would_you_watch_a_movie_entirely_filmed_in_sora/",
|
||||||
|
"content": "3 days ago ... 821 votes, 427 comments. 1.2M subscribers in the OpenAI community. OpenAI is an AI research and deployment company. OpenAI's mission is to ...",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OSDL and the Service Availability Forum Jointly Support OpenAIS …",
|
||||||
|
"url": "http://lwn.net/Articles/109609/",
|
||||||
|
"content": "OSDL and the Service Availability Forum Jointly Support OpenAIS Open Source Development Labs, Inc. has announced that it will be working with the Service…",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Is ChatGPT4 Down for Anyone Else? : r/OpenAI",
|
||||||
|
"url": "https://www.reddit.com/r/OpenAI/comments/1bnqdmp/is_chatgpt4_down_for_anyone_else/",
|
||||||
|
"content": "7 hours ago ... Just came here to ask that. Mine is as well, so at least it's not just me.",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Accepted: openais 0.82-0ubuntu14 (source)",
|
||||||
|
"url": "https://lists.ubuntu.com/archives/hardy-changes/2008-January/003796.html",
|
||||||
|
"content": "",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "[D] Is there currently anything comparable to the OpenAI API? : r ...",
|
||||||
|
"url": "https://www.reddit.com/r/MachineLearning/comments/12arwkf/d_is_there_currently_anything_comparable_to_the/",
|
||||||
|
"content": "Apr 3, 2023 ... [D] Is there currently anything comparable to the OpenAI API? · AI21 Labs (simple interface, accurate models, and little filtering) · NLP Cloud ...",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "[Linux-cluster] Openais doesn't sync, coninuos errors",
|
||||||
|
"url": "https://www.redhat.com/archives/linux-cluster/2009-May/015794.html",
|
||||||
|
"content": "",
|
||||||
|
"engine": "mwmbl",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Anyone using Azure OpenAI? Thoughts, Opinions? : r/AZURE",
|
||||||
|
"url": "https://www.reddit.com/r/AZURE/comments/14axb5m/anyone_using_azure_openai_thoughts_opinions/",
|
||||||
|
"content": "Jun 16, 2023 ... The benefit is really for enterprises. Using Azure OpenAI means you can use the models without worrying about your data, pre-training content, ...",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "OpenAI",
|
||||||
|
"url": "https://www.reddit.com/r/OpenAI/",
|
||||||
|
"content": "r/OpenAI: OpenAI is an AI research and deployment company. OpenAI's mission is to ensure that artificial general intelligence benefits all of…",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "How do I use openai api or something else to chat to my database ...",
|
||||||
|
"url": "https://www.reddit.com/r/OpenAI/comments/18qlkf1/how_do_i_use_openai_api_or_something_else_to_chat/",
|
||||||
|
"content": "Dec 25, 2023 ... Expose your database via a REST API, then create a GPT on ChatGPT (or the Assistant API) and your custom GPT can directly call your REST API to ...",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "What Happened to OpenAI + RL? : r/reinforcementlearning",
|
||||||
|
"url": "https://www.reddit.com/r/reinforcementlearning/comments/rr7yk6/what_happened_to_openai_rl/",
|
||||||
|
"content": "Dec 29, 2021 ... OpenAI is no longer just a nonprofit research organization. Since this organization restructuring, OpenAI also disbanded its robotics team that ...",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Make HomeKit (a lot) smarter with OpenAI : r/HomeKit",
|
||||||
|
"url": "https://www.reddit.com/r/HomeKit/comments/121muti/make_homekit_a_lot_smarter_with_openai/",
|
||||||
|
"content": "Mar 25, 2023 ... I've seen some others making shortcuts that use OpenAI in the voice of Siri, but this is the first I've seen that actually integrates with ...",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "TL;DR of the recent drama? : r/OpenAI",
|
||||||
|
"url": "https://www.reddit.com/r/OpenAI/comments/17zfbe5/tldr_of_the_recent_drama/",
|
||||||
|
"content": "Nov 20, 2023 ... Investors and employees (including the interim CEO and essentially all the other executives) wage battle to bring him back. Pressure campaigns ...",
|
||||||
|
"engine": "crowdview",
|
||||||
|
"category": "general"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -314,8 +314,9 @@ import { docBlockNode, docBlockRemark, docBlockView } from '../plugins/docBlockP
|
|||||||
import { hiddenTextInputPlugin, hiddenTextNode, hiddenTextRemark, hiddenTextView } from '../plugins/hiddenTextPlugin'
|
import { hiddenTextInputPlugin, hiddenTextNode, hiddenTextRemark, hiddenTextView } from '../plugins/hiddenTextPlugin'
|
||||||
import { proBlockConfigCtx, proBlockHighlightPlugin, proBlockInputPlugin, proBlockNode, proBlockRemark, proBlockView } from '../plugins/proBlockPlugin'
|
import { proBlockConfigCtx, proBlockHighlightPlugin, proBlockInputPlugin, proBlockNode, proBlockRemark, proBlockView } from '../plugins/proBlockPlugin'
|
||||||
import { uploadBlockConfigCtx, uploadBlockInputPlugin, uploadBlockNode, uploadBlockRemark, uploadBlockView } from '../plugins/uploadBlockPlugin'
|
import { uploadBlockConfigCtx, uploadBlockInputPlugin, uploadBlockNode, uploadBlockRemark, uploadBlockView } from '../plugins/uploadBlockPlugin'
|
||||||
|
import { webSearchBlockConfigCtx, webSearchBlockInputPlugin, webSearchBlockNode, webSearchBlockRemark, webSearchBlockView } from '../plugins/webSearchBlockPlugin'
|
||||||
import { mermaidRenderPreview, refreshMermaidPreviews, codeBlockConfig } from '../plugins/mermaidPlugin'
|
import { mermaidRenderPreview, refreshMermaidPreviews, codeBlockConfig } from '../plugins/mermaidPlugin'
|
||||||
import { fetchProSuggestionStream, fetchSuggestion, fetchTTS } from '../utils/api.js'
|
import { fetchProSuggestionStream, fetchSuggestion, fetchTTS, fetchWebSearchStream } from '../utils/api.js'
|
||||||
import { useSettingsStore } from '../stores/settings'
|
import { useSettingsStore } from '../stores/settings'
|
||||||
import { useTemplatesStore } from '../stores/templates'
|
import { useTemplatesStore } from '../stores/templates'
|
||||||
import { useTheme } from '../composables/useTheme.js'
|
import { useTheme } from '../composables/useTheme.js'
|
||||||
@@ -327,6 +328,7 @@ import { setOcrCache, clearOcrCache, clearAllOcrCache, IMAGE_SIZE_LIMIT, calcula
|
|||||||
import { isDocumentVisible, getRecommendedDebounce, getRecommendedSyncInterval } from '../composables/useVisibility.js'
|
import { isDocumentVisible, getRecommendedDebounce, getRecommendedSyncInterval } from '../composables/useVisibility.js'
|
||||||
import { DOC_BLOCK_NODE_TYPE, getDocTypeFromFilename, isSupportedDocFile, transformDocBlockMarkdownForClipboard, transformLegacyDocBlocksForExport, transformSpecialDocBlocksToLegacy, isAudioFile } from '../utils/docBlock.js'
|
import { DOC_BLOCK_NODE_TYPE, getDocTypeFromFilename, isSupportedDocFile, transformDocBlockMarkdownForClipboard, transformLegacyDocBlocksForExport, transformSpecialDocBlocksToLegacy, isAudioFile } from '../utils/docBlock.js'
|
||||||
import { isUploadBlockTypeAllowed } from '../utils/uploadBlock.js'
|
import { isUploadBlockTypeAllowed } from '../utils/uploadBlock.js'
|
||||||
|
import { WEB_SEARCH_NODE_TYPE } from '../utils/webSearch.js'
|
||||||
|
|
||||||
const emit = defineEmits(['update:markdown'])
|
const emit = defineEmits(['update:markdown'])
|
||||||
const settings = useSettingsStore()
|
const settings = useSettingsStore()
|
||||||
@@ -779,7 +781,7 @@ const getCursorContext = (view) => {
|
|||||||
for (let depth = $from.depth; depth > 0; depth -= 1) {
|
for (let depth = $from.depth; depth > 0; depth -= 1) {
|
||||||
const node = $from.node(depth)
|
const node = $from.node(depth)
|
||||||
const typeName = node.type?.name || ''
|
const typeName = node.type?.name || ''
|
||||||
if (typeName === DOC_BLOCK_NODE_TYPE) {
|
if (typeName === DOC_BLOCK_NODE_TYPE || typeName === WEB_SEARCH_NODE_TYPE) {
|
||||||
inDocBlock = true
|
inDocBlock = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -1355,6 +1357,16 @@ crepe = new Crepe({
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
crepe.editor.config((ctx) => {
|
||||||
|
ctx.set(webSearchBlockConfigCtx.key, {
|
||||||
|
fetchWebSearchStream,
|
||||||
|
t,
|
||||||
|
showError: (message) => {
|
||||||
|
alert(message || '联网搜索失败')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
crepe.editor.config((ctx) => {
|
crepe.editor.config((ctx) => {
|
||||||
ctx.update(codeBlockConfig.key, (prev) => ({
|
ctx.update(codeBlockConfig.key, (prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
@@ -1376,6 +1388,7 @@ crepe = new Crepe({
|
|||||||
crepe.editor.use(copilotConfigCtx)
|
crepe.editor.use(copilotConfigCtx)
|
||||||
crepe.editor.use(proBlockConfigCtx)
|
crepe.editor.use(proBlockConfigCtx)
|
||||||
crepe.editor.use(uploadBlockConfigCtx)
|
crepe.editor.use(uploadBlockConfigCtx)
|
||||||
|
crepe.editor.use(webSearchBlockConfigCtx)
|
||||||
crepe.editor.use(copilotGhostMark)
|
crepe.editor.use(copilotGhostMark)
|
||||||
crepe.editor.use(copilotPlugin)
|
crepe.editor.use(copilotPlugin)
|
||||||
crepe.editor.use(proBlockRemark)
|
crepe.editor.use(proBlockRemark)
|
||||||
@@ -1391,6 +1404,10 @@ crepe = new Crepe({
|
|||||||
crepe.editor.use(uploadBlockNode)
|
crepe.editor.use(uploadBlockNode)
|
||||||
crepe.editor.use(uploadBlockView)
|
crepe.editor.use(uploadBlockView)
|
||||||
crepe.editor.use(uploadBlockInputPlugin)
|
crepe.editor.use(uploadBlockInputPlugin)
|
||||||
|
crepe.editor.use(webSearchBlockRemark)
|
||||||
|
crepe.editor.use(webSearchBlockNode)
|
||||||
|
crepe.editor.use(webSearchBlockView)
|
||||||
|
crepe.editor.use(webSearchBlockInputPlugin)
|
||||||
crepe.editor.use(docBlockRemark)
|
crepe.editor.use(docBlockRemark)
|
||||||
crepe.editor.use(docBlockNode)
|
crepe.editor.use(docBlockNode)
|
||||||
crepe.editor.use(docBlockView)
|
crepe.editor.use(docBlockView)
|
||||||
|
|||||||
@@ -0,0 +1,439 @@
|
|||||||
|
<template>
|
||||||
|
<section class="web-search-card" :class="{ 'is-collapsed': collapsedState, 'is-pending': isPendingOnly }">
|
||||||
|
<button
|
||||||
|
v-if="isPendingOnly"
|
||||||
|
type="button"
|
||||||
|
class="web-search-capsule"
|
||||||
|
@mousedown.stop.prevent
|
||||||
|
@click.stop="props.onActivate?.()"
|
||||||
|
>
|
||||||
|
<span class="web-search-badge">WEB</span>
|
||||||
|
<span class="web-search-label">{{ startLabel }}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<header class="web-search-card__header">
|
||||||
|
<div class="web-search-card__meta">
|
||||||
|
<div class="web-search-card__title">{{ titleLabel }}</div>
|
||||||
|
<div class="web-search-card__subtitle">{{ subtitleLabel }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="web-search-card__actions">
|
||||||
|
<button
|
||||||
|
v-if="isBusy"
|
||||||
|
type="button"
|
||||||
|
class="web-search-card__text-btn"
|
||||||
|
contenteditable="false"
|
||||||
|
@mousedown.stop.prevent
|
||||||
|
@click.stop="props.onCancel?.()"
|
||||||
|
>
|
||||||
|
{{ t('cancel') || '取消' }}
|
||||||
|
</button>
|
||||||
|
<template v-else-if="hasContent">
|
||||||
|
<button type="button" class="web-search-card__btn" title="压缩搜索结果" contenteditable="false" @mousedown.stop.prevent @click.stop="handleCompress">
|
||||||
|
<svg v-if="compressState === 'idle' || compressState === 'completed'" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M4 14h6v7H4z"/>
|
||||||
|
<path d="M14 9h6v12h-6z"/>
|
||||||
|
<path d="M4 9h6v5H4z"/>
|
||||||
|
</svg>
|
||||||
|
<span v-if="compressState === 'queued' || compressState === 'processing'" class="web-search-card__spinner"></span>
|
||||||
|
<svg v-else-if="compressState === 'error'" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<circle cx="12" cy="12" r="10"/>
|
||||||
|
<line x1="15" y1="9" x2="9" y2="15"/>
|
||||||
|
<line x1="9" y1="9" x2="15" y2="15"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="web-search-card__btn" :title="collapsedState ? '展开结果' : '折叠结果'" contenteditable="false" @mousedown.stop.prevent @click.stop="toggleCollapse">
|
||||||
|
<svg v-if="collapsedState" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<polyline points="9 18 15 12 9 6"/>
|
||||||
|
</svg>
|
||||||
|
<svg v-else width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<polyline points="6 9 12 15 18 9"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<button type="button" class="web-search-card__btn web-search-card__btn--danger" title="删除搜索块" contenteditable="false" @mousedown.stop.prevent @click.stop="props.onDelete?.()">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M3 6h18"/>
|
||||||
|
<path d="M8 6V4h8v2"/>
|
||||||
|
<path d="M19 6l-1 14H6L5 6"/>
|
||||||
|
<path d="M10 11v6"/>
|
||||||
|
<path d="M14 11v6"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div v-if="isBusy || hasError" class="web-search-card__progress">
|
||||||
|
<div v-if="isBusy" class="web-search-card__progress-row">
|
||||||
|
<span class="web-search-card__spinner"></span>
|
||||||
|
<span>{{ progressText || '正在联网搜索' }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="hasError" class="web-search-card__error">{{ errorMessage }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-show="hasContent && !collapsedState" class="web-search-card__body">
|
||||||
|
<div ref="editorRoot" class="web-search-card__editor"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||||
|
import { replaceAll } from '@milkdown/kit/utils'
|
||||||
|
import { Crepe } from '@milkdown/crepe'
|
||||||
|
import { editorViewCtx } from '@milkdown/kit/core'
|
||||||
|
import { copilotPlugin, copilotConfigCtx, copilotGhostMark, setCopilotEnabled, clearGhostSuggestion } from '../plugins/copilotPlugin'
|
||||||
|
import { hiddenTextInputPlugin, hiddenTextNode, hiddenTextRemark, hiddenTextView } from '../plugins/hiddenTextPlugin'
|
||||||
|
import { fetchSuggestion, submitCompress, pollCompressStatus } from '../utils/api.js'
|
||||||
|
import { isDocumentVisible, getRecommendedDebounce, getRecommendedSyncInterval } from '../composables/useVisibility.js'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
stage: { type: String, default: 'idle' },
|
||||||
|
progressText: { type: String, default: '' },
|
||||||
|
errorMessage: { type: String, default: '' },
|
||||||
|
content: { type: String, default: '' },
|
||||||
|
createdAt: { type: String, default: '' },
|
||||||
|
collapsed: { type: Boolean, default: false },
|
||||||
|
t: { type: Function, default: (key) => key },
|
||||||
|
onActivate: { type: Function, default: null },
|
||||||
|
onCancel: { type: Function, default: null },
|
||||||
|
onDelete: { type: Function, default: null },
|
||||||
|
onUpdateContent: { type: Function, default: null },
|
||||||
|
onUpdateCollapsed: { type: Function, default: null },
|
||||||
|
resolveSuggestionRequest: { type: Function, default: null },
|
||||||
|
})
|
||||||
|
|
||||||
|
const editorRoot = ref(null)
|
||||||
|
const collapsedState = ref(Boolean(props.collapsed))
|
||||||
|
const currentContent = ref(props.content || '')
|
||||||
|
const compressState = ref('idle')
|
||||||
|
let crepe = null
|
||||||
|
let syncTimer = null
|
||||||
|
let syncingExternal = false
|
||||||
|
let compressPoller = null
|
||||||
|
|
||||||
|
const ensureEditor = async () => {
|
||||||
|
if (crepe || !editorRoot.value || !hasContent.value) return
|
||||||
|
crepe = new Crepe({
|
||||||
|
root: editorRoot.value,
|
||||||
|
defaultValue: props.content || '',
|
||||||
|
features: {
|
||||||
|
[Crepe.Feature.Latex]: true,
|
||||||
|
[Crepe.Feature.ImageBlock]: true,
|
||||||
|
[Crepe.Feature.Table]: true,
|
||||||
|
[Crepe.Feature.ListCheck]: true,
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
showLineNumber: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
crepe.editor.config((ctx) => {
|
||||||
|
ctx.set(copilotConfigCtx.key, {
|
||||||
|
fetchSuggestion: async (prefix, suffix, languageId, signal) => {
|
||||||
|
const payload = props.resolveSuggestionRequest
|
||||||
|
? await props.resolveSuggestionRequest({ prefix, suffix, languageId })
|
||||||
|
: { prefix, suffix, languageId, blocked: false }
|
||||||
|
if (payload?.blocked) return ''
|
||||||
|
return fetchSuggestion(payload?.prefix ?? prefix, payload?.suffix ?? suffix, payload?.languageId ?? languageId, signal)
|
||||||
|
},
|
||||||
|
debounceMs: getRecommendedDebounce(900),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
crepe.editor.use(copilotConfigCtx)
|
||||||
|
crepe.editor.use(copilotGhostMark)
|
||||||
|
crepe.editor.use(copilotPlugin)
|
||||||
|
crepe.editor.use(hiddenTextRemark)
|
||||||
|
crepe.editor.use(hiddenTextNode)
|
||||||
|
crepe.editor.use(hiddenTextView)
|
||||||
|
crepe.editor.use(hiddenTextInputPlugin)
|
||||||
|
await crepe.create()
|
||||||
|
|
||||||
|
crepe.on((listener) => {
|
||||||
|
listener.updated(() => {
|
||||||
|
syncContent()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
crepe.editor.action((ctx) => {
|
||||||
|
const view = ctx.get(editorViewCtx)
|
||||||
|
setCopilotEnabled(view, true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasContent = computed(() => Boolean((props.content || '').trim()))
|
||||||
|
const hasError = computed(() => Boolean((props.errorMessage || '').trim()))
|
||||||
|
const isBusy = computed(() => props.stage !== 'idle' && props.stage !== 'done' && props.stage !== 'error' && props.stage !== 'cancelled')
|
||||||
|
const isPendingOnly = computed(() => !hasContent.value && !isBusy.value && !hasError.value)
|
||||||
|
const startLabel = computed(() => props.t('webSearchStart') || '开始联网搜索')
|
||||||
|
const titleLabel = computed(() => hasContent.value ? (props.t('webSearchResultTitle') || '搜索结果') : (props.t('webSearchTitle') || '联网搜索'))
|
||||||
|
const progressText = computed(() => props.progressText || props.t('webSearchWorking') || '正在联网搜索')
|
||||||
|
const subtitleLabel = computed(() => {
|
||||||
|
if (hasContent.value) {
|
||||||
|
if (!props.createdAt) return props.t('webSearchDone') || '搜索已完成'
|
||||||
|
const date = new Date(props.createdAt)
|
||||||
|
if (Number.isNaN(date.getTime())) return props.t('webSearchDone') || '搜索已完成'
|
||||||
|
return date.toLocaleString('zh-CN', { hour12: false })
|
||||||
|
}
|
||||||
|
if (hasError.value) return props.t('webSearchError') || '搜索失败'
|
||||||
|
if (isBusy.value) return progressText.value
|
||||||
|
return props.t('webSearchIdleHint') || '点击后自动生成关键词并抓取网页'
|
||||||
|
})
|
||||||
|
|
||||||
|
const toggleCollapse = () => {
|
||||||
|
collapsedState.value = !collapsedState.value
|
||||||
|
props.onUpdateCollapsed?.(collapsedState.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCompress = () => {
|
||||||
|
if (compressState.value !== 'idle' || !crepe) return
|
||||||
|
const content = crepe.getMarkdown?.() || ''
|
||||||
|
if (!String(content || '').trim()) {
|
||||||
|
compressState.value = 'error'
|
||||||
|
setTimeout(() => { compressState.value = 'idle' }, 2000)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
submitCompress(content, 'txt').then((result) => {
|
||||||
|
compressState.value = 'queued'
|
||||||
|
if (compressPoller) compressPoller.stop()
|
||||||
|
compressPoller = pollCompressStatus(result.task_id, (status, compressedContent) => {
|
||||||
|
compressState.value = status
|
||||||
|
if (status === 'completed' && compressedContent) {
|
||||||
|
crepe.editor.action(replaceAll(compressedContent))
|
||||||
|
} else if (status === 'error') {
|
||||||
|
setTimeout(() => { compressState.value = 'idle' }, 3000)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}).catch(() => {
|
||||||
|
compressState.value = 'error'
|
||||||
|
setTimeout(() => { compressState.value = 'idle' }, 3000)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const syncContent = () => {
|
||||||
|
if (!crepe) return
|
||||||
|
if (!isDocumentVisible()) return
|
||||||
|
if (compressState.value !== 'idle') return
|
||||||
|
if (syncTimer) clearTimeout(syncTimer)
|
||||||
|
syncTimer = setTimeout(async () => {
|
||||||
|
if (!crepe || syncingExternal) return
|
||||||
|
const markdown = await crepe.getMarkdown()
|
||||||
|
currentContent.value = markdown
|
||||||
|
props.onUpdateContent?.(markdown)
|
||||||
|
}, getRecommendedSyncInterval(120))
|
||||||
|
}
|
||||||
|
|
||||||
|
const syncExternalContent = async (nextValue) => {
|
||||||
|
const value = nextValue || ''
|
||||||
|
if (!crepe) {
|
||||||
|
currentContent.value = value
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (value === currentContent.value) return
|
||||||
|
if (syncTimer) {
|
||||||
|
clearTimeout(syncTimer)
|
||||||
|
syncTimer = null
|
||||||
|
}
|
||||||
|
syncingExternal = true
|
||||||
|
try {
|
||||||
|
crepe.editor.action(replaceAll(value))
|
||||||
|
currentContent.value = value
|
||||||
|
} finally {
|
||||||
|
syncingExternal = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.content, (nextValue) => {
|
||||||
|
if (nextValue && !crepe) {
|
||||||
|
void ensureEditor()
|
||||||
|
}
|
||||||
|
void syncExternalContent(nextValue)
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.collapsed, (nextValue) => {
|
||||||
|
collapsedState.value = Boolean(nextValue)
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await ensureEditor()
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (syncTimer) {
|
||||||
|
clearTimeout(syncTimer)
|
||||||
|
syncTimer = null
|
||||||
|
}
|
||||||
|
if (compressPoller) {
|
||||||
|
compressPoller.stop()
|
||||||
|
compressPoller = null
|
||||||
|
}
|
||||||
|
if (crepe) {
|
||||||
|
crepe.editor.action((ctx) => {
|
||||||
|
const view = ctx.get(editorViewCtx)
|
||||||
|
clearGhostSuggestion(view)
|
||||||
|
})
|
||||||
|
crepe.destroy()
|
||||||
|
crepe = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.web-search-card {
|
||||||
|
width: 100%;
|
||||||
|
margin: 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.web-search-capsule,
|
||||||
|
.web-search-card__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 48px;
|
||||||
|
padding: 11px 14px;
|
||||||
|
border: 1px solid rgba(14, 116, 144, 0.24);
|
||||||
|
background: linear-gradient(135deg, rgba(239, 246, 255, 0.96), rgba(224, 242, 254, 0.94));
|
||||||
|
color: #0f172a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.web-search-capsule {
|
||||||
|
border-radius: 999px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.web-search-card__header {
|
||||||
|
border-radius: 14px 14px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.web-search-card:not(.is-pending) {
|
||||||
|
border: 1px solid rgba(14, 116, 144, 0.16);
|
||||||
|
border-radius: 14px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme='dark'] .web-search-capsule,
|
||||||
|
:root[data-theme='dark'] .web-search-card__header {
|
||||||
|
background: linear-gradient(135deg, rgba(8, 47, 73, 0.92), rgba(17, 24, 39, 0.96));
|
||||||
|
color: #e2e8f0;
|
||||||
|
border-color: rgba(34, 211, 238, 0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme='dark'] .web-search-card:not(.is-pending) {
|
||||||
|
background: rgba(15, 23, 42, 0.88);
|
||||||
|
border-color: rgba(34, 211, 238, 0.16);
|
||||||
|
}
|
||||||
|
|
||||||
|
.web-search-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 44px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: linear-gradient(135deg, #0ea5e9, #14b8a6);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.web-search-label,
|
||||||
|
.web-search-card__title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.web-search-card__meta {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.web-search-card__subtitle {
|
||||||
|
margin-top: 2px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(15, 23, 42, 0.72);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme='dark'] .web-search-card__subtitle {
|
||||||
|
color: rgba(226, 232, 240, 0.78);
|
||||||
|
}
|
||||||
|
|
||||||
|
.web-search-card__actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.web-search-card__btn,
|
||||||
|
.web-search-card__text-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(255, 255, 255, 0.82);
|
||||||
|
color: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.web-search-card__text-btn {
|
||||||
|
width: auto;
|
||||||
|
padding: 0 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme='dark'] .web-search-card__btn,
|
||||||
|
:root[data-theme='dark'] .web-search-card__text-btn {
|
||||||
|
background: rgba(15, 23, 42, 0.72);
|
||||||
|
}
|
||||||
|
|
||||||
|
.web-search-card__btn--danger {
|
||||||
|
color: #dc2626;
|
||||||
|
}
|
||||||
|
|
||||||
|
.web-search-card__progress {
|
||||||
|
padding: 14px;
|
||||||
|
border-top: 1px solid rgba(14, 116, 144, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.web-search-card__progress-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.web-search-card__error {
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #b91c1c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.web-search-card__body {
|
||||||
|
border-top: 1px solid rgba(14, 116, 144, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.web-search-card__editor {
|
||||||
|
min-height: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.web-search-card__spinner {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
border: 2px solid rgba(14, 116, 144, 0.18);
|
||||||
|
border-top-color: #0ea5e9;
|
||||||
|
border-radius: 999px;
|
||||||
|
animation: web-search-spin 0.8s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes web-search-spin {
|
||||||
|
from { transform: rotate(0deg); }
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -5,6 +5,7 @@ import { Node as ProseNode, Slice } from '@milkdown/prose/model'
|
|||||||
import type { Ctx } from '@milkdown/kit/core'
|
import type { Ctx } from '@milkdown/kit/core'
|
||||||
import { Decoration, DecorationSet, type EditorView } from '@milkdown/prose/view'
|
import { Decoration, DecorationSet, type EditorView } from '@milkdown/prose/view'
|
||||||
import { extractDocBlockContextFromMarkdown } from '../utils/docBlock.js'
|
import { extractDocBlockContextFromMarkdown } from '../utils/docBlock.js'
|
||||||
|
import { extractWebSearchContextFromMarkdown, WEB_SEARCH_NODE_TYPE } from '../utils/webSearch.js'
|
||||||
import { getOcrCache, OCR_SIZE_LIMIT, extractTextFromOCR, buildOcrContextForDoc } from '../utils/ocrCache'
|
import { getOcrCache, OCR_SIZE_LIMIT, extractTextFromOCR, buildOcrContextForDoc } from '../utils/ocrCache'
|
||||||
import { isDocumentVisible } from '../composables/useVisibility.js'
|
import { isDocumentVisible } from '../composables/useVisibility.js'
|
||||||
|
|
||||||
@@ -369,9 +370,10 @@ function scheduleFetch(view: EditorView, runtime: CopilotRuntime, pos: number) {
|
|||||||
|
|
||||||
// 从markdown中提取文档块内容用于AI补全上下文
|
// 从markdown中提取文档块内容用于AI补全上下文
|
||||||
const docContext = extractDocBlockContextFromMarkdown(prefixMarkdown + suffixMarkdown, 500)
|
const docContext = extractDocBlockContextFromMarkdown(prefixMarkdown + suffixMarkdown, 500)
|
||||||
|
const webSearchContext = extractWebSearchContextFromMarkdown(prefixMarkdown + suffixMarkdown, 500)
|
||||||
|
|
||||||
// 组合所有上下文到prefix前面
|
// 组合所有上下文到prefix前面
|
||||||
const fullPrefixWithContext = [ocrContext, docContext, prefixMarkdown].filter(Boolean).join('\n\n')
|
const fullPrefixWithContext = [ocrContext, docContext, webSearchContext, prefixMarkdown].filter(Boolean).join('\n\n')
|
||||||
|
|
||||||
const totalTextLen = (prefixMarkdown + suffixMarkdown).length
|
const totalTextLen = (prefixMarkdown + suffixMarkdown).length
|
||||||
const contextLen = fullPrefixWithContext.length - prefixMarkdown.length
|
const contextLen = fullPrefixWithContext.length - prefixMarkdown.length
|
||||||
@@ -687,6 +689,9 @@ export function checkSizeLimit(view: EditorView): { size: number; overLimit: boo
|
|||||||
if (node.type.name === 'doc_block' && node.attrs.content) {
|
if (node.type.name === 'doc_block' && node.attrs.content) {
|
||||||
size += String(node.attrs.content).length
|
size += String(node.attrs.content).length
|
||||||
}
|
}
|
||||||
|
if (node.type.name === WEB_SEARCH_NODE_TYPE && node.attrs.content) {
|
||||||
|
size += String(node.attrs.content).length
|
||||||
|
}
|
||||||
})
|
})
|
||||||
return { size, overLimit: size > SIZE_LIMIT }
|
return { size, overLimit: size > SIZE_LIMIT }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
parseDocBlockValue,
|
parseDocBlockValue,
|
||||||
stripDocBlockMarkdown,
|
stripDocBlockMarkdown,
|
||||||
} from '../utils/docBlock.js'
|
} from '../utils/docBlock.js'
|
||||||
|
import { WEB_SEARCH_NODE_TYPE } from '../utils/webSearch.js'
|
||||||
|
|
||||||
const FALLBACK_BLOCK_SEPARATOR = '\n\n'
|
const FALLBACK_BLOCK_SEPARATOR = '\n\n'
|
||||||
const FALLBACK_LEAF_TEXT = '\n'
|
const FALLBACK_LEAF_TEXT = '\n'
|
||||||
@@ -379,6 +380,8 @@ export function checkSizeLimit(view: EditorView): { size: number; overLimit: boo
|
|||||||
if (node.type.name === 'doc_block' && node.attrs.content) {
|
if (node.type.name === 'doc_block' && node.attrs.content) {
|
||||||
// 文档块内容是隐藏的,单独统计
|
// 文档块内容是隐藏的,单独统计
|
||||||
hiddenChars += String(node.attrs.content).length
|
hiddenChars += String(node.attrs.content).length
|
||||||
|
} else if (node.type.name === WEB_SEARCH_NODE_TYPE && node.attrs.content) {
|
||||||
|
hiddenChars += String(node.attrs.content).length
|
||||||
} else if (node.text) {
|
} else if (node.text) {
|
||||||
// 普通文本节点
|
// 普通文本节点
|
||||||
visibleChars += node.text.length
|
visibleChars += node.text.length
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { extractDocBlockContextFromMarkdown } from '../utils/docBlock.js'
|
|||||||
import { buildOcrContextForDoc } from '../utils/ocrCache'
|
import { buildOcrContextForDoc } from '../utils/ocrCache'
|
||||||
import { normalizeProAcceptMarkdown, splitPlainTextFallbackBlocks } from '../utils/proAccept.js'
|
import { normalizeProAcceptMarkdown, splitPlainTextFallbackBlocks } from '../utils/proAccept.js'
|
||||||
import { PRO_BLOCK_NODE_TYPE, PRO_DISPLAY_LABEL, PRO_TRIGGER_TEXT, parseProBlockSyntax, serializeProBlockSyntax } from '../utils/proBlock.js'
|
import { PRO_BLOCK_NODE_TYPE, PRO_DISPLAY_LABEL, PRO_TRIGGER_TEXT, parseProBlockSyntax, serializeProBlockSyntax } from '../utils/proBlock.js'
|
||||||
|
import { extractWebSearchContextFromMarkdown } from '../utils/webSearch.js'
|
||||||
|
|
||||||
const PRO_BLOCK_INPUT_PLUGIN_KEY = new PluginKey('milkdown-pro-block-input')
|
const PRO_BLOCK_INPUT_PLUGIN_KEY = new PluginKey('milkdown-pro-block-input')
|
||||||
const PRO_BLOCK_HIGHLIGHT_PLUGIN_KEY = new PluginKey<DecorationSet>('milkdown-pro-block-highlight')
|
const PRO_BLOCK_HIGHLIGHT_PLUGIN_KEY = new PluginKey<DecorationSet>('milkdown-pro-block-highlight')
|
||||||
@@ -441,7 +442,8 @@ class ProBlockNodeView implements NodeView {
|
|||||||
|| doc.textBetween(suffixStart, doc.content.size, FALLBACK_BLOCK_SEPARATOR, FALLBACK_LEAF_TEXT)
|
|| doc.textBetween(suffixStart, doc.content.size, FALLBACK_BLOCK_SEPARATOR, FALLBACK_LEAF_TEXT)
|
||||||
const ocrContext = buildOcrContextForDoc(doc, 120)
|
const ocrContext = buildOcrContextForDoc(doc, 120)
|
||||||
const docContext = extractDocBlockContextFromMarkdown(`${prefixMarkdown}\n\n${suffixMarkdown}`, 1600)
|
const docContext = extractDocBlockContextFromMarkdown(`${prefixMarkdown}\n\n${suffixMarkdown}`, 1600)
|
||||||
const fullPrefix = [ocrContext, docContext, prefixMarkdown].filter(Boolean).join('\n\n')
|
const webSearchContext = extractWebSearchContextFromMarkdown(`${prefixMarkdown}\n\n${suffixMarkdown}`, 1600)
|
||||||
|
const fullPrefix = [ocrContext, docContext, webSearchContext, prefixMarkdown].filter(Boolean).join('\n\n')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
prefix: fullPrefix,
|
prefix: fullPrefix,
|
||||||
|
|||||||
@@ -0,0 +1,662 @@
|
|||||||
|
import { createApp, h, reactive } from 'vue'
|
||||||
|
import { serializerCtx, type Ctx } from '@milkdown/kit/core'
|
||||||
|
import { $ctx, $node, $prose, $remark, $view } from '@milkdown/kit/utils'
|
||||||
|
import { Plugin, PluginKey, Selection } from '@milkdown/prose/state'
|
||||||
|
import { type Node as ProseNode, type Schema } from '@milkdown/prose/model'
|
||||||
|
import type { EditorView, NodeView } from '@milkdown/prose/view'
|
||||||
|
import WebSearchBlockCrepe from '../components/WebSearchBlockCrepe.vue'
|
||||||
|
import { extractDocBlockContextFromMarkdown } from '../utils/docBlock.js'
|
||||||
|
import { buildOcrContextForDoc } from '../utils/ocrCache'
|
||||||
|
import {
|
||||||
|
WEB_SEARCH_CONTEXT_LIMIT,
|
||||||
|
WEB_SEARCH_NODE_TYPE,
|
||||||
|
WEB_SEARCH_RESULT_FENCE_LANG,
|
||||||
|
WEB_SEARCH_TRIGGER_TEXT,
|
||||||
|
buildWebSearchResultMarkdown,
|
||||||
|
extractWebSearchContextFromMarkdown,
|
||||||
|
parseWebSearchResultMarkdown,
|
||||||
|
parseWebSearchTriggerSyntax,
|
||||||
|
} from '../utils/webSearch.js'
|
||||||
|
|
||||||
|
const WEB_SEARCH_BLOCK_INPUT_PLUGIN_KEY = new PluginKey('milkdown-web-search-block-input')
|
||||||
|
const FALLBACK_BLOCK_SEPARATOR = '\n\n'
|
||||||
|
const FALLBACK_LEAF_TEXT = '\n'
|
||||||
|
const CONTEXT_SEPARATOR = '\n\n'
|
||||||
|
const MAX_SUFFIX_RATIO = 0.35
|
||||||
|
|
||||||
|
interface WebSearchBlockConfig {
|
||||||
|
fetchWebSearchStream: (payload: {
|
||||||
|
prefix: string
|
||||||
|
suffix: string
|
||||||
|
languageId: string
|
||||||
|
signal?: AbortSignal
|
||||||
|
onEvent?: (event: string, data?: Record<string, any>) => void
|
||||||
|
}) => Promise<{ content: string; createdAt: string }>
|
||||||
|
t: (key: string) => string
|
||||||
|
showError: (message: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
function serializeRangeToMarkdown(
|
||||||
|
doc: ProseNode,
|
||||||
|
from: number,
|
||||||
|
to: number,
|
||||||
|
schema: Schema,
|
||||||
|
serializer: (content: ProseNode) => string
|
||||||
|
): string {
|
||||||
|
if (from >= to) return ''
|
||||||
|
const fallback = doc.textBetween(from, to, FALLBACK_BLOCK_SEPARATOR, FALLBACK_LEAF_TEXT)
|
||||||
|
if (typeof serializer !== 'function') return fallback
|
||||||
|
|
||||||
|
const slice = doc.slice(from, to)
|
||||||
|
if (slice.content.size <= 0) return ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const sliceDoc = schema.topNodeType.createAndFill(undefined, slice.content)
|
||||||
|
return sliceDoc ? serializer(sliceDoc) : fallback
|
||||||
|
} catch {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getJoinedLength(parts: string[]) {
|
||||||
|
let total = 0
|
||||||
|
let hasContent = false
|
||||||
|
for (const part of parts) {
|
||||||
|
if (!part) continue
|
||||||
|
if (hasContent) total += CONTEXT_SEPARATOR.length
|
||||||
|
total += part.length
|
||||||
|
hasContent = true
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
function takeTail(text: string, limit: number) {
|
||||||
|
if (!text || limit <= 0) return ''
|
||||||
|
if (text.length <= limit) return text
|
||||||
|
return text.slice(-limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
function takeHead(text: string, limit: number) {
|
||||||
|
if (!text || limit <= 0) return ''
|
||||||
|
if (text.length <= limit) return text
|
||||||
|
return text.slice(0, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
function fitPrefixSections(parts: string[], limit: number) {
|
||||||
|
if (limit <= 0) return ''
|
||||||
|
|
||||||
|
const fitted: string[] = []
|
||||||
|
let remaining = limit
|
||||||
|
|
||||||
|
for (let index = parts.length - 1; index >= 0; index -= 1) {
|
||||||
|
const part = parts[index]
|
||||||
|
if (!part || remaining <= 0) continue
|
||||||
|
|
||||||
|
const separatorCost = fitted.length > 0 ? CONTEXT_SEPARATOR.length : 0
|
||||||
|
if (remaining <= separatorCost) break
|
||||||
|
|
||||||
|
const nextPart = takeTail(part, remaining - separatorCost)
|
||||||
|
if (!nextPart) continue
|
||||||
|
|
||||||
|
fitted.unshift(nextPart)
|
||||||
|
remaining -= nextPart.length + separatorCost
|
||||||
|
}
|
||||||
|
|
||||||
|
return fitted.join(CONTEXT_SEPARATOR)
|
||||||
|
}
|
||||||
|
|
||||||
|
function fitSuffixSections(parts: string[], limit: number) {
|
||||||
|
if (limit <= 0) return ''
|
||||||
|
|
||||||
|
const fitted: string[] = []
|
||||||
|
let remaining = limit
|
||||||
|
|
||||||
|
for (const part of parts) {
|
||||||
|
if (!part || remaining <= 0) continue
|
||||||
|
|
||||||
|
const separatorCost = fitted.length > 0 ? CONTEXT_SEPARATOR.length : 0
|
||||||
|
if (remaining <= separatorCost) break
|
||||||
|
|
||||||
|
const nextPart = takeHead(part, remaining - separatorCost)
|
||||||
|
if (!nextPart) continue
|
||||||
|
|
||||||
|
fitted.push(nextPart)
|
||||||
|
remaining -= nextPart.length + separatorCost
|
||||||
|
}
|
||||||
|
|
||||||
|
return fitted.join(CONTEXT_SEPARATOR)
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformWebSearchChildren(node: any) {
|
||||||
|
if (!node || !Array.isArray(node.children)) return
|
||||||
|
|
||||||
|
node.children = node.children.map((child: any) => {
|
||||||
|
if (child?.type === 'paragraph' && Array.isArray(child.children) && child.children.length === 1) {
|
||||||
|
const textNode = child.children[0]
|
||||||
|
if (textNode?.type === 'text' && typeof textNode.value === 'string') {
|
||||||
|
const parsed = parseWebSearchTriggerSyntax(textNode.value)
|
||||||
|
if (parsed) {
|
||||||
|
return {
|
||||||
|
type: 'webSearchTrigger',
|
||||||
|
...parsed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (child?.type === 'code' && child.lang === WEB_SEARCH_RESULT_FENCE_LANG) {
|
||||||
|
return {
|
||||||
|
type: 'webSearchResult',
|
||||||
|
value: String(child.value || ''),
|
||||||
|
meta: String(child.meta || ''),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transformWebSearchChildren(child)
|
||||||
|
return child
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function findWebSearchReplacements(doc: ProseNode) {
|
||||||
|
const replacements: Array<{ from: number; to: number }> = []
|
||||||
|
|
||||||
|
doc.descendants((node, pos) => {
|
||||||
|
if (node.type.name !== 'paragraph' || node.childCount !== 1) return true
|
||||||
|
const firstChild = node.firstChild
|
||||||
|
if (!firstChild?.isText) return true
|
||||||
|
if (!parseWebSearchTriggerSyntax(node.textContent)) return true
|
||||||
|
|
||||||
|
replacements.push({
|
||||||
|
from: pos,
|
||||||
|
to: pos + node.nodeSize,
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
return replacements
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceParagraphWithWebSearchBlock(view: EditorView, from: number, to: number) {
|
||||||
|
const blockType = view.state.schema.nodes[WEB_SEARCH_NODE_TYPE]
|
||||||
|
if (!blockType) return false
|
||||||
|
|
||||||
|
const blockNode = blockType.create({
|
||||||
|
content: '',
|
||||||
|
createdAt: '',
|
||||||
|
collapsed: false,
|
||||||
|
autoStart: false,
|
||||||
|
})
|
||||||
|
const tr = view.state.tr.replaceWith(from, to, blockNode)
|
||||||
|
const nextPos = Math.min(from + blockNode.nodeSize, tr.doc.content.size)
|
||||||
|
tr.setSelection(Selection.near(tr.doc.resolve(nextPos), 1))
|
||||||
|
view.dispatch(tr.scrollIntoView())
|
||||||
|
view.focus()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function tryHandleWebSearchTriggerTextInput(view: EditorView, insertedText: string) {
|
||||||
|
if (!insertedText || !insertedText.includes(']')) return false
|
||||||
|
|
||||||
|
const { state } = view
|
||||||
|
const { $from, from, to } = state.selection
|
||||||
|
const paragraph = $from.parent
|
||||||
|
if (!paragraph || paragraph.type.name !== 'paragraph') return false
|
||||||
|
if (!$from.sameParent(state.selection.$to)) return false
|
||||||
|
|
||||||
|
const paragraphDepth = $from.depth
|
||||||
|
const paragraphStart = $from.start(paragraphDepth)
|
||||||
|
const startOffset = from - paragraphStart
|
||||||
|
const endOffset = to - paragraphStart
|
||||||
|
const nextText = `${paragraph.textContent.slice(0, startOffset)}${insertedText}${paragraph.textContent.slice(endOffset)}`
|
||||||
|
if (!parseWebSearchTriggerSyntax(nextText)) return false
|
||||||
|
|
||||||
|
const blockFrom = $from.before(paragraphDepth)
|
||||||
|
const blockTo = blockFrom + paragraph.nodeSize
|
||||||
|
return replaceParagraphWithWebSearchBlock(view, blockFrom, blockTo)
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildResultStatusText(stage: string, t: (key: string) => string) {
|
||||||
|
if (stage === 'queued') return t('webSearchQueued') || '已进入搜索队列'
|
||||||
|
if (stage === 'keywords') return t('webSearchKeywords') || '正在生成搜索关键词'
|
||||||
|
if (stage === 'searching') return t('webSearchSearching') || '正在通过 SearXNG 搜索'
|
||||||
|
if (stage === 'selecting_urls') return t('webSearchSelecting') || '正在筛选可信网址'
|
||||||
|
if (stage === 'crawling') return t('webSearchCrawling') || '正在抓取网页内容'
|
||||||
|
if (stage === 'synthesizing') return t('webSearchSynthesizing') || '正在整理搜索结果'
|
||||||
|
if (stage === 'cancelled') return t('webSearchCancelled') || '搜索已取消'
|
||||||
|
if (stage === 'error') return t('webSearchError') || '搜索失败'
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
export const webSearchBlockConfigCtx = $ctx<WebSearchBlockConfig, 'webSearchBlockConfig'>({
|
||||||
|
fetchWebSearchStream: async () => ({ content: '', createdAt: '' }),
|
||||||
|
t: (key: string) => key,
|
||||||
|
showError: () => {},
|
||||||
|
}, 'webSearchBlockConfig')
|
||||||
|
|
||||||
|
class WebSearchBlockNodeView implements NodeView {
|
||||||
|
node: ProseNode
|
||||||
|
view: EditorView
|
||||||
|
getPos: (() => number) | boolean
|
||||||
|
dom: HTMLElement
|
||||||
|
app: ReturnType<typeof createApp> | null = null
|
||||||
|
props: Record<string, any>
|
||||||
|
ctx: Ctx
|
||||||
|
config: WebSearchBlockConfig
|
||||||
|
abortController: AbortController | null = null
|
||||||
|
requestSeq = 0
|
||||||
|
destroyed = false
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
node: ProseNode,
|
||||||
|
view: EditorView,
|
||||||
|
getPos: (() => number) | boolean,
|
||||||
|
ctx: Ctx,
|
||||||
|
config: WebSearchBlockConfig
|
||||||
|
) {
|
||||||
|
this.node = node
|
||||||
|
this.view = view
|
||||||
|
this.getPos = getPos
|
||||||
|
this.ctx = ctx
|
||||||
|
this.config = config
|
||||||
|
this.dom = document.createElement('div')
|
||||||
|
this.dom.className = 'web-search-block-node-view'
|
||||||
|
this.props = reactive({
|
||||||
|
stage: 'idle',
|
||||||
|
progressText: '',
|
||||||
|
errorMessage: '',
|
||||||
|
content: node.attrs.content || '',
|
||||||
|
createdAt: node.attrs.createdAt || '',
|
||||||
|
collapsed: Boolean(node.attrs.collapsed),
|
||||||
|
t: (key: string) => this.config.t(key),
|
||||||
|
onActivate: () => {
|
||||||
|
void this.startSearch()
|
||||||
|
},
|
||||||
|
onCancel: () => {
|
||||||
|
this.cancelSearch()
|
||||||
|
},
|
||||||
|
onDelete: () => {
|
||||||
|
this.deleteNode()
|
||||||
|
},
|
||||||
|
onUpdateContent: (content: string) => {
|
||||||
|
this.updateAttrs({ content })
|
||||||
|
},
|
||||||
|
onUpdateCollapsed: (collapsed: boolean) => {
|
||||||
|
this.updateAttrs({ collapsed })
|
||||||
|
},
|
||||||
|
resolveSuggestionRequest: (payload: { prefix: string; suffix: string; languageId: string }) => this.resolveSuggestionRequest(payload),
|
||||||
|
})
|
||||||
|
this.mount()
|
||||||
|
}
|
||||||
|
|
||||||
|
mount() {
|
||||||
|
this.app = createApp({
|
||||||
|
render: () => h(WebSearchBlockCrepe, { ...this.props }),
|
||||||
|
})
|
||||||
|
this.app.mount(this.dom)
|
||||||
|
}
|
||||||
|
|
||||||
|
getPosValue() {
|
||||||
|
if (typeof this.getPos === 'function') {
|
||||||
|
try {
|
||||||
|
const pos = this.getPos()
|
||||||
|
if (typeof pos === 'number') return pos
|
||||||
|
} catch {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAttrs(patch: Record<string, any>) {
|
||||||
|
const pos = this.getPosValue()
|
||||||
|
if (pos === undefined) return
|
||||||
|
const nextAttrs = { ...this.node.attrs, ...patch }
|
||||||
|
this.view.dispatch(this.view.state.tr.setNodeMarkup(pos, undefined, nextAttrs))
|
||||||
|
}
|
||||||
|
|
||||||
|
setStage(stage: string, progressText = '') {
|
||||||
|
this.props.stage = stage
|
||||||
|
this.props.progressText = progressText || buildResultStatusText(stage, this.config.t)
|
||||||
|
}
|
||||||
|
|
||||||
|
buildRequestPayload() {
|
||||||
|
const pos = this.getPosValue()
|
||||||
|
if (pos === undefined) {
|
||||||
|
return {
|
||||||
|
prefix: '',
|
||||||
|
suffix: '',
|
||||||
|
languageId: 'markdown',
|
||||||
|
blocked: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const doc = this.view.state.doc
|
||||||
|
const schema = this.view.state.schema
|
||||||
|
const serializer = this.ctx.get(serializerCtx)
|
||||||
|
const prefixMarkdown = serializeRangeToMarkdown(doc, 0, pos, schema, serializer)
|
||||||
|
|| doc.textBetween(0, pos, FALLBACK_BLOCK_SEPARATOR, FALLBACK_LEAF_TEXT)
|
||||||
|
const suffixStart = Math.min(pos + this.node.nodeSize, doc.content.size)
|
||||||
|
const suffixMarkdown = serializeRangeToMarkdown(doc, suffixStart, doc.content.size, schema, serializer)
|
||||||
|
|| doc.textBetween(suffixStart, doc.content.size, FALLBACK_BLOCK_SEPARATOR, FALLBACK_LEAF_TEXT)
|
||||||
|
const ocrContext = buildOcrContextForDoc(doc, 120)
|
||||||
|
const docContext = extractDocBlockContextFromMarkdown(`${prefixMarkdown}\n\n${suffixMarkdown}`, 1600)
|
||||||
|
const webSearchContext = extractWebSearchContextFromMarkdown(`${prefixMarkdown}\n\n${suffixMarkdown}`, 1600)
|
||||||
|
const prefixParts = [ocrContext, docContext, webSearchContext, prefixMarkdown].filter(Boolean)
|
||||||
|
const suffixParts = [suffixMarkdown].filter(Boolean)
|
||||||
|
const mergedPrefix = prefixParts.join(CONTEXT_SEPARATOR)
|
||||||
|
const mergedSuffix = suffixParts.join(CONTEXT_SEPARATOR)
|
||||||
|
|
||||||
|
if (mergedPrefix.length + mergedSuffix.length <= WEB_SEARCH_CONTEXT_LIMIT) {
|
||||||
|
return {
|
||||||
|
prefix: mergedPrefix,
|
||||||
|
suffix: mergedSuffix,
|
||||||
|
languageId: 'markdown',
|
||||||
|
blocked: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const prefixCapacity = getJoinedLength(prefixParts)
|
||||||
|
const suffixCapacity = getJoinedLength(suffixParts)
|
||||||
|
const maxSuffixBudget = Math.min(suffixCapacity, Math.floor(WEB_SEARCH_CONTEXT_LIMIT * MAX_SUFFIX_RATIO))
|
||||||
|
let suffixBudget = maxSuffixBudget
|
||||||
|
let prefixBudget = WEB_SEARCH_CONTEXT_LIMIT - suffixBudget
|
||||||
|
|
||||||
|
if (prefixCapacity < prefixBudget) {
|
||||||
|
const transferable = prefixBudget - prefixCapacity
|
||||||
|
suffixBudget = Math.min(suffixCapacity, suffixBudget + transferable)
|
||||||
|
prefixBudget = WEB_SEARCH_CONTEXT_LIMIT - suffixBudget
|
||||||
|
} else if (suffixCapacity < suffixBudget) {
|
||||||
|
const transferable = suffixBudget - suffixCapacity
|
||||||
|
prefixBudget = Math.min(prefixCapacity, prefixBudget + transferable)
|
||||||
|
suffixBudget = WEB_SEARCH_CONTEXT_LIMIT - prefixBudget
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
prefix: fitPrefixSections(prefixParts, prefixBudget),
|
||||||
|
suffix: fitSuffixSections(suffixParts, suffixBudget),
|
||||||
|
languageId: 'markdown',
|
||||||
|
blocked: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async startSearch() {
|
||||||
|
if (this.abortController) {
|
||||||
|
this.abortController.abort('restart')
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = this.buildRequestPayload()
|
||||||
|
if (payload.blocked) {
|
||||||
|
this.config.showError('联网搜索上下文过长,无法继续。')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestSeq = this.requestSeq + 1
|
||||||
|
this.requestSeq = requestSeq
|
||||||
|
this.abortController = new AbortController()
|
||||||
|
this.props.errorMessage = ''
|
||||||
|
this.setStage('queued')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await this.config.fetchWebSearchStream({
|
||||||
|
...payload,
|
||||||
|
signal: this.abortController.signal,
|
||||||
|
onEvent: (event, data) => {
|
||||||
|
if (this.destroyed || this.requestSeq !== requestSeq) return
|
||||||
|
if (event === 'error') {
|
||||||
|
this.props.errorMessage = String(data?.error || this.config.t('webSearchErrorActionable') || '联网搜索失败,请重试。')
|
||||||
|
this.setStage('error')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.setStage(event, String(data?.message || ''))
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (this.destroyed || this.requestSeq !== requestSeq) return
|
||||||
|
|
||||||
|
const content = String(result?.content || '').trim()
|
||||||
|
if (!content) {
|
||||||
|
this.props.errorMessage = this.config.t('webSearchEmptyResult') || '联网搜索返回空结果,请重试。'
|
||||||
|
this.setStage('error')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdAt = String(result?.createdAt || new Date().toISOString())
|
||||||
|
this.props.content = content
|
||||||
|
this.props.createdAt = createdAt
|
||||||
|
this.props.collapsed = false
|
||||||
|
this.setStage('done', '')
|
||||||
|
this.updateAttrs({
|
||||||
|
content,
|
||||||
|
createdAt,
|
||||||
|
collapsed: false,
|
||||||
|
autoStart: false,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
if (this.destroyed || this.requestSeq !== requestSeq) return
|
||||||
|
if (error && typeof error === 'object' && 'name' in error && error.name === 'AbortError') {
|
||||||
|
this.setStage('cancelled')
|
||||||
|
} else {
|
||||||
|
this.props.errorMessage = error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: (this.config.t('webSearchErrorActionable') || '联网搜索失败,请重试。')
|
||||||
|
this.setStage('error')
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (this.requestSeq === requestSeq) {
|
||||||
|
this.abortController = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelSearch() {
|
||||||
|
if (!this.abortController) return
|
||||||
|
this.abortController.abort('cancelled')
|
||||||
|
this.abortController = null
|
||||||
|
this.setStage('cancelled')
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteNode() {
|
||||||
|
const pos = this.getPosValue()
|
||||||
|
if (pos === undefined) return
|
||||||
|
const tr = this.view.state.tr.delete(pos, pos + this.node.nodeSize).scrollIntoView()
|
||||||
|
this.view.dispatch(tr)
|
||||||
|
this.view.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveSuggestionRequest(payload: { prefix: string; suffix: string; languageId: string }) {
|
||||||
|
const pos = this.getPosValue()
|
||||||
|
if (pos === undefined) return payload
|
||||||
|
const doc = this.view.state.doc
|
||||||
|
const schema = this.view.state.schema
|
||||||
|
const serializer = this.ctx.get(serializerCtx)
|
||||||
|
const before = serializeRangeToMarkdown(doc, 0, pos, schema, serializer)
|
||||||
|
const after = serializeRangeToMarkdown(doc, pos + this.node.nodeSize, doc.content.size, schema, serializer)
|
||||||
|
const docContext = extractDocBlockContextFromMarkdown(`${before}\n\n${after}`, 1600)
|
||||||
|
const webSearchContext = extractWebSearchContextFromMarkdown(`${before}\n\n${after}`, 1600)
|
||||||
|
const prefixParts = [docContext, webSearchContext, before, payload.prefix].filter(Boolean)
|
||||||
|
const suffixParts = [payload.suffix, after].filter(Boolean)
|
||||||
|
const mergedPrefix = prefixParts.join(CONTEXT_SEPARATOR)
|
||||||
|
const mergedSuffix = suffixParts.join(CONTEXT_SEPARATOR)
|
||||||
|
|
||||||
|
if (mergedPrefix.length + mergedSuffix.length <= WEB_SEARCH_CONTEXT_LIMIT) {
|
||||||
|
return {
|
||||||
|
prefix: mergedPrefix,
|
||||||
|
suffix: mergedSuffix,
|
||||||
|
languageId: payload.languageId,
|
||||||
|
blocked: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const prefixCapacity = getJoinedLength(prefixParts)
|
||||||
|
const suffixCapacity = getJoinedLength(suffixParts)
|
||||||
|
const maxSuffixBudget = Math.min(suffixCapacity, Math.floor(WEB_SEARCH_CONTEXT_LIMIT * MAX_SUFFIX_RATIO))
|
||||||
|
let suffixBudget = maxSuffixBudget
|
||||||
|
let prefixBudget = WEB_SEARCH_CONTEXT_LIMIT - suffixBudget
|
||||||
|
|
||||||
|
if (prefixCapacity < prefixBudget) {
|
||||||
|
const transferable = prefixBudget - prefixCapacity
|
||||||
|
suffixBudget = Math.min(suffixCapacity, suffixBudget + transferable)
|
||||||
|
prefixBudget = WEB_SEARCH_CONTEXT_LIMIT - suffixBudget
|
||||||
|
} else if (suffixCapacity < suffixBudget) {
|
||||||
|
const transferable = suffixBudget - suffixCapacity
|
||||||
|
prefixBudget = Math.min(prefixCapacity, prefixBudget + transferable)
|
||||||
|
suffixBudget = WEB_SEARCH_CONTEXT_LIMIT - prefixBudget
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
prefix: fitPrefixSections(prefixParts, prefixBudget),
|
||||||
|
suffix: fitSuffixSections(suffixParts, suffixBudget),
|
||||||
|
languageId: payload.languageId,
|
||||||
|
blocked: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update(node: ProseNode) {
|
||||||
|
if (node.type !== this.node.type) return false
|
||||||
|
this.node = node
|
||||||
|
this.props.content = node.attrs.content || ''
|
||||||
|
this.props.createdAt = node.attrs.createdAt || ''
|
||||||
|
this.props.collapsed = Boolean(node.attrs.collapsed)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
stopEvent(event: Event) {
|
||||||
|
const target = event.target as Node | null
|
||||||
|
return Boolean(target && this.dom.contains(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
ignoreMutation() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.destroyed = true
|
||||||
|
if (this.abortController) {
|
||||||
|
this.abortController.abort('destroy')
|
||||||
|
this.abortController = null
|
||||||
|
}
|
||||||
|
this.app?.unmount()
|
||||||
|
this.app = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const webSearchBlockRemark = $remark('webSearchBlockRemark', () => () => {
|
||||||
|
return (tree: any) => {
|
||||||
|
transformWebSearchChildren(tree)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const webSearchBlockNode = $node(WEB_SEARCH_NODE_TYPE, () => ({
|
||||||
|
group: 'block',
|
||||||
|
atom: true,
|
||||||
|
isolating: true,
|
||||||
|
selectable: true,
|
||||||
|
draggable: false,
|
||||||
|
marks: '',
|
||||||
|
attrs: {
|
||||||
|
content: { default: '' },
|
||||||
|
createdAt: { default: '' },
|
||||||
|
collapsed: { default: false },
|
||||||
|
autoStart: { default: false },
|
||||||
|
},
|
||||||
|
parseDOM: [
|
||||||
|
{
|
||||||
|
tag: 'div[data-web-search-block="true"]',
|
||||||
|
getAttrs: (dom) => ({
|
||||||
|
content: '',
|
||||||
|
createdAt: (dom as HTMLElement).getAttribute('data-created-at') || '',
|
||||||
|
collapsed: ((dom as HTMLElement).getAttribute('data-collapsed') || '') === 'true',
|
||||||
|
autoStart: false,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
toDOM: (node) => [
|
||||||
|
'div',
|
||||||
|
{
|
||||||
|
'data-web-search-block': 'true',
|
||||||
|
'data-created-at': node.attrs.createdAt || '',
|
||||||
|
'data-collapsed': String(Boolean(node.attrs.collapsed)),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
parseMarkdown: {
|
||||||
|
match: (node) => node.type === 'webSearchTrigger' || node.type === 'webSearchResult',
|
||||||
|
runner: (state, node, type) => {
|
||||||
|
if (node.type === 'webSearchTrigger') {
|
||||||
|
state.addNode(type, {
|
||||||
|
content: '',
|
||||||
|
createdAt: '',
|
||||||
|
collapsed: false,
|
||||||
|
autoStart: false,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const attrs = parseWebSearchResultMarkdown(String(node.value || ''), String(node.meta || ''))
|
||||||
|
state.addNode(type, attrs)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
toMarkdown: {
|
||||||
|
match: (node) => node.type.name === WEB_SEARCH_NODE_TYPE,
|
||||||
|
runner: (state, node) => {
|
||||||
|
const content = String(node.attrs.content || '').trim()
|
||||||
|
if (!content) {
|
||||||
|
state.addNode('paragraph', [{
|
||||||
|
type: 'text',
|
||||||
|
value: WEB_SEARCH_TRIGGER_TEXT,
|
||||||
|
}])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
state.addNode('code', {
|
||||||
|
lang: WEB_SEARCH_RESULT_FENCE_LANG,
|
||||||
|
meta: `date=${node.attrs.createdAt || new Date().toISOString()}`,
|
||||||
|
value: content,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
leafText: (node) => {
|
||||||
|
const content = String(node.attrs.content || '').trim()
|
||||||
|
if (!content) return WEB_SEARCH_TRIGGER_TEXT
|
||||||
|
return buildWebSearchResultMarkdown(node.attrs)
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
export const webSearchBlockView = $view(webSearchBlockNode, (ctx) => {
|
||||||
|
const config = ctx.get(webSearchBlockConfigCtx.key)
|
||||||
|
return (node, view, getPos) => new WebSearchBlockNodeView(node, view, getPos, ctx, config)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const webSearchBlockInputPlugin = $prose(() => {
|
||||||
|
return new Plugin({
|
||||||
|
key: WEB_SEARCH_BLOCK_INPUT_PLUGIN_KEY,
|
||||||
|
props: {
|
||||||
|
handleTextInput: (view, _from, _to, text) => {
|
||||||
|
return tryHandleWebSearchTriggerTextInput(view, text)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
appendTransaction: (transactions, _oldState, newState) => {
|
||||||
|
if (!transactions.some((transaction) => transaction.docChanged)) return null
|
||||||
|
const blockType = newState.schema.nodes[WEB_SEARCH_NODE_TYPE]
|
||||||
|
if (!blockType) return null
|
||||||
|
|
||||||
|
const replacements = findWebSearchReplacements(newState.doc)
|
||||||
|
if (replacements.length === 0) return null
|
||||||
|
|
||||||
|
let tr = newState.tr
|
||||||
|
for (let index = replacements.length - 1; index >= 0; index -= 1) {
|
||||||
|
const replacement = replacements[index]
|
||||||
|
tr = tr.replaceWith(
|
||||||
|
replacement.from,
|
||||||
|
replacement.to,
|
||||||
|
blockType.create({
|
||||||
|
content: '',
|
||||||
|
createdAt: '',
|
||||||
|
collapsed: false,
|
||||||
|
autoStart: false,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return tr.docChanged ? tr : null
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
@@ -3,6 +3,8 @@ import {
|
|||||||
API_KEY,
|
API_KEY,
|
||||||
PRO_URL,
|
PRO_URL,
|
||||||
PRO_FRONTEND_TIMEOUT_MS,
|
PRO_FRONTEND_TIMEOUT_MS,
|
||||||
|
WEB_SEARCH_URL,
|
||||||
|
WEB_SEARCH_FRONTEND_TIMEOUT_MS,
|
||||||
TTS_URL,
|
TTS_URL,
|
||||||
TTS_STATUS_URL,
|
TTS_STATUS_URL,
|
||||||
TTS_CONFIG_URL,
|
TTS_CONFIG_URL,
|
||||||
@@ -81,6 +83,9 @@ function getCancelUrl(apiUrl) {
|
|||||||
if (/\/v1\/pro\/completions$/i.test(normalized)) {
|
if (/\/v1\/pro\/completions$/i.test(normalized)) {
|
||||||
return normalized.replace(/\/v1\/pro\/completions$/i, '/v1/pro/completions/cancel')
|
return normalized.replace(/\/v1\/pro\/completions$/i, '/v1/pro/completions/cancel')
|
||||||
}
|
}
|
||||||
|
if (/\/v1\/web-search$/i.test(normalized)) {
|
||||||
|
return normalized.replace(/\/v1\/web-search$/i, '/v1/web-search/cancel')
|
||||||
|
}
|
||||||
if (/\/v1\/completions$/i.test(normalized)) {
|
if (/\/v1\/completions$/i.test(normalized)) {
|
||||||
return normalized.replace(/\/v1\/completions$/i, '/v1/completions/cancel')
|
return normalized.replace(/\/v1\/completions$/i, '/v1/completions/cancel')
|
||||||
}
|
}
|
||||||
@@ -298,6 +303,57 @@ export async function fetchProSuggestionStream(payload, apiUrl = PRO_URL) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchWebSearchStream(payload, apiUrl = WEB_SEARCH_URL) {
|
||||||
|
const {
|
||||||
|
prefix = '',
|
||||||
|
suffix = '',
|
||||||
|
languageId = 'markdown',
|
||||||
|
signal,
|
||||||
|
timeoutMs = WEB_SEARCH_FRONTEND_TIMEOUT_MS,
|
||||||
|
onEvent,
|
||||||
|
} = payload || {}
|
||||||
|
|
||||||
|
const settings = useSettingsStore()
|
||||||
|
const requestId = generateRequestId()
|
||||||
|
|
||||||
|
return consumeSseJson({
|
||||||
|
url: apiUrl,
|
||||||
|
requestId,
|
||||||
|
signal,
|
||||||
|
timeoutMs,
|
||||||
|
body: {
|
||||||
|
prefix,
|
||||||
|
suffix,
|
||||||
|
languageId: String(languageId || 'markdown').trim() || 'markdown',
|
||||||
|
privacy_mode: settings.privacyMode,
|
||||||
|
user_preferences: {
|
||||||
|
language: settings.language,
|
||||||
|
currency: settings.currency,
|
||||||
|
timezone: settings.detectedTimezone,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
onEvent(event, data) {
|
||||||
|
if (event === 'queued' || event === 'started' || event === 'resource') {
|
||||||
|
onEvent?.(event, data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (event === 'progress') {
|
||||||
|
onEvent?.(String(data?.phase || ''), data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (event === 'error') {
|
||||||
|
onEvent?.('error', data)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDone(data) {
|
||||||
|
return {
|
||||||
|
content: String(data?.content || ''),
|
||||||
|
createdAt: String(data?.created_at || data?.createdAt || ''),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export async function fetchTTS(text, instruct = '', apiUrl = TTS_URL) {
|
export async function fetchTTS(text, instruct = '', apiUrl = TTS_URL) {
|
||||||
const requestId = generateRequestId()
|
const requestId = generateRequestId()
|
||||||
return consumeSseJson({
|
return consumeSseJson({
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ const API_BASE_URL = (import.meta.env.VITE_API_BASE_URL || DEFAULT_API_BASE_URL)
|
|||||||
export const API_URL = import.meta.env.VITE_API_URL || `${API_BASE_URL}/v1/completions`
|
export const API_URL = import.meta.env.VITE_API_URL || `${API_BASE_URL}/v1/completions`
|
||||||
export const PRO_URL = import.meta.env.VITE_PRO_URL || `${API_BASE_URL}/v1/pro/completions`
|
export const PRO_URL = import.meta.env.VITE_PRO_URL || `${API_BASE_URL}/v1/pro/completions`
|
||||||
export const PRO_FRONTEND_TIMEOUT_MS = Number(import.meta.env.VITE_PRO_FRONTEND_TIMEOUT_MS || 3660000)
|
export const PRO_FRONTEND_TIMEOUT_MS = Number(import.meta.env.VITE_PRO_FRONTEND_TIMEOUT_MS || 3660000)
|
||||||
|
export const WEB_SEARCH_URL = import.meta.env.VITE_WEB_SEARCH_URL || `${API_BASE_URL}/v1/web-search`
|
||||||
|
export const WEB_SEARCH_FRONTEND_TIMEOUT_MS = Number(import.meta.env.VITE_WEB_SEARCH_FRONTEND_TIMEOUT_MS || 3660000)
|
||||||
export const OCR_URL = import.meta.env.VITE_OCR_URL || `${API_BASE_URL}/v1/ocr`
|
export const OCR_URL = import.meta.env.VITE_OCR_URL || `${API_BASE_URL}/v1/ocr`
|
||||||
export const CONVERT_URL = import.meta.env.VITE_CONVERT_URL || `${API_BASE_URL}/v1/convert`
|
export const CONVERT_URL = import.meta.env.VITE_CONVERT_URL || `${API_BASE_URL}/v1/convert`
|
||||||
export const EXPORT_PDF_URL = import.meta.env.VITE_EXPORT_PDF_URL || `${API_BASE_URL}/v1/export/pdf`
|
export const EXPORT_PDF_URL = import.meta.env.VITE_EXPORT_PDF_URL || `${API_BASE_URL}/v1/export/pdf`
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
export const WEB_SEARCH_NODE_TYPE = 'web_search_block'
|
||||||
|
export const WEB_SEARCH_TRIGGER_TEXT = '[WEBSEARCH]'
|
||||||
|
export const WEB_SEARCH_RESULT_FENCE_LANG = 'llm-websearch'
|
||||||
|
export const WEB_SEARCH_CONTEXT_LIMIT = 32 * 1024
|
||||||
|
|
||||||
|
const WEB_SEARCH_TRIGGER_RE = /^\[websearch\]$/i
|
||||||
|
const WEB_SEARCH_RESULT_RE = /(^|\n)(`{3,})llm-websearch(?:\s+date=([^\s`]+))?[^\n]*\n([\s\S]*?)\n\2(?=\n|$)/g
|
||||||
|
|
||||||
|
function normalizeMarkdownText(value = '') {
|
||||||
|
return String(value || '').replace(/\r\n?/g, '\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickFence(content = '') {
|
||||||
|
const matches = String(content || '').match(/`{3,}/g) || []
|
||||||
|
const maxLen = matches.reduce((max, item) => Math.max(max, item.length), 2)
|
||||||
|
return '`'.repeat(maxLen + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeDate(value = '') {
|
||||||
|
const text = String(value || '').trim()
|
||||||
|
if (!text) return ''
|
||||||
|
const parsed = new Date(text)
|
||||||
|
if (Number.isNaN(parsed.getTime())) return ''
|
||||||
|
return parsed.toISOString()
|
||||||
|
}
|
||||||
|
|
||||||
|
function clipText(value = '', limit = 0) {
|
||||||
|
if (!limit || value.length <= limit) return value
|
||||||
|
return `${value.slice(0, limit)}...`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseWebSearchTriggerSyntax(value = '') {
|
||||||
|
const text = normalizeMarkdownText(value).trim()
|
||||||
|
if (!WEB_SEARCH_TRIGGER_RE.test(text)) return null
|
||||||
|
return {
|
||||||
|
content: '',
|
||||||
|
createdAt: '',
|
||||||
|
collapsed: false,
|
||||||
|
autoStart: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildWebSearchResultMarkdown(attrs = {}) {
|
||||||
|
const content = normalizeMarkdownText(attrs.content || '').trimEnd()
|
||||||
|
const createdAt = sanitizeDate(attrs.createdAt) || new Date().toISOString()
|
||||||
|
const fence = pickFence(content)
|
||||||
|
return `${fence}${WEB_SEARCH_RESULT_FENCE_LANG} date=${createdAt}\n${content}\n${fence}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseWebSearchResultMarkdown(value = '', meta = '') {
|
||||||
|
const content = normalizeMarkdownText(value).replace(/\s+$/, '')
|
||||||
|
const dateMatch = String(meta || '').match(/(?:^|\s)date=([^\s`]+)/i)
|
||||||
|
return {
|
||||||
|
content,
|
||||||
|
createdAt: sanitizeDate(dateMatch?.[1] || ''),
|
||||||
|
collapsed: false,
|
||||||
|
autoStart: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildWebSearchContextFence(attrs = {}) {
|
||||||
|
const content = normalizeMarkdownText(attrs.content || '').trim()
|
||||||
|
if (!content) return ''
|
||||||
|
const fence = pickFence(content)
|
||||||
|
return `${fence}markdown\n${content}\n${fence}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extractWebSearchContextFromMarkdown(markdown = '', contentLimit = 0) {
|
||||||
|
const normalized = normalizeMarkdownText(markdown)
|
||||||
|
const contexts = []
|
||||||
|
|
||||||
|
normalized.replace(WEB_SEARCH_RESULT_RE, (_full, _prefix, _fence, date, content) => {
|
||||||
|
const fence = buildWebSearchContextFence({
|
||||||
|
content: clipText(normalizeMarkdownText(content || '').trim(), contentLimit),
|
||||||
|
createdAt: date || '',
|
||||||
|
})
|
||||||
|
if (fence) contexts.push(fence)
|
||||||
|
return _full
|
||||||
|
})
|
||||||
|
|
||||||
|
return contexts.join('\n\n')
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user