mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 23:23:55 +08:00
230 lines
7.5 KiB
Python
230 lines
7.5 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import base64
|
|
import hashlib
|
|
import json
|
|
from collections.abc import Mapping
|
|
from typing import Annotated, Any
|
|
|
|
from fastapi import FastAPI, File, Request, UploadFile
|
|
from fastapi.responses import JSONResponse
|
|
|
|
app = FastAPI(title="manager-api repeatable external-service mock")
|
|
|
|
_requests: list[dict[str, Any]] = []
|
|
_datasets: dict[str, dict[str, Any]] = {}
|
|
_documents: dict[str, dict[str, Any]] = {}
|
|
|
|
|
|
async def _capture(request: Request, body: Any = None) -> None:
|
|
_requests.append(
|
|
{
|
|
"method": request.method,
|
|
"path": request.url.path,
|
|
"query": dict(request.query_params),
|
|
"headers": {
|
|
key.lower(): value
|
|
for key, value in request.headers.items()
|
|
if key.lower() in {"authorization", "content-type"}
|
|
},
|
|
"body": body,
|
|
}
|
|
)
|
|
|
|
|
|
@app.get("/health")
|
|
async def health() -> dict[str, str]:
|
|
return {"status": "UP"}
|
|
|
|
|
|
@app.get("/__requests")
|
|
async def requests() -> dict[str, Any]:
|
|
return {"requests": list(_requests)}
|
|
|
|
|
|
@app.delete("/__requests")
|
|
async def reset_requests() -> dict[str, bool]:
|
|
_requests.clear()
|
|
return {"reset": True}
|
|
|
|
|
|
@app.post("/api/commands/{client_id}")
|
|
async def mqtt_command(client_id: str, request: Request) -> JSONResponse:
|
|
body = await request.json()
|
|
await _capture(request, body)
|
|
method = body.get("payload", {}).get("method") if isinstance(body, Mapping) else None
|
|
if method == "tools/list":
|
|
response = {
|
|
"success": True,
|
|
"data": {
|
|
"tools": [
|
|
{
|
|
"name": "contract.echo",
|
|
"description": "repeatable contract fixture",
|
|
"inputSchema": {"type": "object"},
|
|
}
|
|
]
|
|
},
|
|
}
|
|
elif method == "tools/call":
|
|
response = {"success": True, "data": {"content": [{"type": "text", "text": '{"echo":true}'}]}}
|
|
else:
|
|
response = {"success": True, "data": {"clientId": client_id}}
|
|
return JSONResponse(response)
|
|
|
|
|
|
@app.post("/api/devices/status")
|
|
async def mqtt_status(request: Request) -> JSONResponse:
|
|
body = await request.json()
|
|
await _capture(request, body)
|
|
client_ids = body.get("clientIds", []) if isinstance(body, Mapping) else []
|
|
return JSONResponse({"success": True, "data": {str(value): True for value in client_ids}})
|
|
|
|
|
|
@app.post("/api/call/{operation}")
|
|
async def mqtt_call(operation: str, request: Request) -> JSONResponse:
|
|
body = await request.json()
|
|
await _capture(request, body)
|
|
return JSONResponse({"status": "success", "message": operation})
|
|
|
|
|
|
@app.post("/v1/chat/completions")
|
|
async def chat_completions(request: Request) -> JSONResponse:
|
|
body = await request.json()
|
|
await _capture(request, body)
|
|
return JSONResponse(
|
|
{
|
|
"id": "chatcmpl-contract",
|
|
"choices": [{"index": 0, "message": {"role": "assistant", "content": "contract summary"}}],
|
|
}
|
|
)
|
|
|
|
|
|
@app.get("/api/v1/datasets")
|
|
async def list_datasets(request: Request) -> JSONResponse:
|
|
await _capture(request)
|
|
dataset_id = request.query_params.get("id")
|
|
values = list(_datasets.values())
|
|
if dataset_id:
|
|
values = [value for value in values if value["id"] == dataset_id]
|
|
return JSONResponse({"code": 0, "data": values})
|
|
|
|
|
|
@app.post("/api/v1/datasets")
|
|
async def create_dataset(request: Request) -> JSONResponse:
|
|
body = await request.json()
|
|
await _capture(request, body)
|
|
identifier = "rag-contract-" + hashlib.sha256(
|
|
json.dumps(body, ensure_ascii=False, sort_keys=True).encode()
|
|
).hexdigest()[:12]
|
|
value = {
|
|
"id": identifier,
|
|
"name": body.get("name"),
|
|
"description": body.get("description"),
|
|
"tenant_id": "tenant-contract",
|
|
"avatar": body.get("avatar"),
|
|
"document_count": 0,
|
|
"chunk_count": 0,
|
|
"token_num": 0,
|
|
}
|
|
_datasets[identifier] = value
|
|
return JSONResponse({"code": 0, "data": value})
|
|
|
|
|
|
@app.put("/api/v1/datasets/{dataset_id}")
|
|
async def update_dataset(dataset_id: str, request: Request) -> JSONResponse:
|
|
body = await request.json()
|
|
await _capture(request, body)
|
|
value = _datasets.setdefault(dataset_id, {"id": dataset_id})
|
|
value.update(body)
|
|
return JSONResponse({"code": 0, "data": value})
|
|
|
|
|
|
@app.delete("/api/v1/datasets/{dataset_id}")
|
|
async def delete_dataset(dataset_id: str, request: Request) -> JSONResponse:
|
|
await _capture(request)
|
|
_datasets.pop(dataset_id, None)
|
|
return JSONResponse({"code": 0, "data": True})
|
|
|
|
|
|
@app.post("/api/v1/datasets/{dataset_id}/documents")
|
|
async def upload_document(
|
|
dataset_id: str,
|
|
request: Request,
|
|
file: Annotated[UploadFile, File()],
|
|
) -> JSONResponse:
|
|
content = await file.read()
|
|
body: dict[str, Any] = {
|
|
"filename": file.filename,
|
|
"size": len(content),
|
|
"sha256": hashlib.sha256(content).hexdigest(),
|
|
}
|
|
await _capture(request, body)
|
|
identifier = "doc-contract-" + str(body["sha256"])[:12]
|
|
value = {
|
|
"id": identifier,
|
|
"dataset_id": dataset_id,
|
|
"name": file.filename,
|
|
"size": len(content),
|
|
"run": "UNSTART",
|
|
"progress": 0.0,
|
|
"chunk_count": 0,
|
|
"token_count": 0,
|
|
}
|
|
_documents[identifier] = value
|
|
return JSONResponse({"code": 0, "data": [value]})
|
|
|
|
|
|
@app.get("/api/v1/datasets/{dataset_id}/documents")
|
|
async def list_documents(dataset_id: str, request: Request) -> JSONResponse:
|
|
await _capture(request)
|
|
document_id = request.query_params.get("id")
|
|
values = [value for value in _documents.values() if value["dataset_id"] == dataset_id]
|
|
if document_id:
|
|
values = [value for value in values if value["id"] == document_id]
|
|
return JSONResponse({"code": 0, "data": {"docs": values, "total": len(values)}})
|
|
|
|
|
|
@app.post("/api/v1/datasets/{dataset_id}/chunks")
|
|
async def parse_documents(dataset_id: str, request: Request) -> JSONResponse:
|
|
body = await request.json()
|
|
await _capture(request, body)
|
|
for document_id in body.get("document_ids", []):
|
|
if document_id in _documents:
|
|
_documents[document_id].update(run="DONE", progress=1.0, chunk_count=2, token_count=17)
|
|
return JSONResponse({"code": 0, "data": True})
|
|
|
|
|
|
@app.delete("/api/v1/datasets/{dataset_id}/documents")
|
|
async def delete_documents(dataset_id: str, request: Request) -> JSONResponse:
|
|
body = await request.json()
|
|
await _capture(request, body)
|
|
for document_id in body.get("ids", body.get("document_ids", [])):
|
|
_documents.pop(document_id, None)
|
|
return JSONResponse({"code": 0, "data": True})
|
|
|
|
|
|
@app.post("/voice-clone")
|
|
async def clone_voice(request: Request) -> JSONResponse:
|
|
content = await request.body()
|
|
await _capture(request, {"size": len(content), "sha256": hashlib.sha256(content).hexdigest()})
|
|
return JSONResponse({"voice_id": "voice-contract", "status": "success"})
|
|
|
|
|
|
@app.post("/transient/{failures}")
|
|
async def transient(failures: int, request: Request) -> JSONResponse:
|
|
await _capture(request)
|
|
count = sum(1 for item in _requests if item["path"] == request.url.path)
|
|
if count <= failures:
|
|
await asyncio.sleep(0)
|
|
return JSONResponse({"error": "retryable"}, status_code=503)
|
|
return JSONResponse({"ok": True})
|
|
|
|
|
|
@app.get("/binary")
|
|
async def binary(request: Request) -> JSONResponse:
|
|
await _capture(request)
|
|
content = b"contract-binary\x00\xff"
|
|
return JSONResponse({"base64": base64.b64encode(content).decode(), "size": len(content)})
|