diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6429ddb..4879c64 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,20 +1,31 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v4.6.0 hooks: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/psf/black - rev: 24.3.0 + rev: 24.4.2 hooks: - id: black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.4 + rev: v0.4.3 hooks: - id: ruff - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.9.0' + rev: 'v1.10.0' hooks: - id: mypy - additional_dependencies: [pydantic, types-requests, types-pytz, types-setuptools, types-urllib3, StrEnum] + args: [., --strict, --ignore-missing-imports, --exclude=^codegen] + pass_filenames: false + additional_dependencies: [ + pytest, + pydantic, + types-Pillow, + types-requests, + types-pytz, + types-setuptools, + types-urllib3, + StrEnum + ] diff --git a/codegen/README.md b/codegen/ai_horde/README.md similarity index 89% rename from codegen/README.md rename to codegen/ai_horde/README.md index 9894aa2..4dfc436 100644 --- a/codegen/README.md +++ b/codegen/ai_horde/README.md @@ -40,9 +40,9 @@ python codegen/codegen_regex_fixes.py codegen/ai_horde_codegen.py Format again, this time truncating the lines with `--preview`, and auto-fix lint problems ```bash -black codegen/ai_horde_codegen.py --preview +black codegen/ai_horde_codegen.py --unstable --enable-unstable-feature string_processing ruff codegen/ai_horde_codegen.py --fix -black codegen/ai_horde_codegen.py +black codegen/ai_horde_codegen.py --unstable --enable-unstable-feature string_processing # for good measure ruff codegen/ai_horde_codegen.py --fix # for good measure ``` diff --git a/codegen/__init__.py b/codegen/ai_horde/__init__.py similarity index 100% rename from codegen/__init__.py rename to codegen/ai_horde/__init__.py diff --git a/codegen/codegen_regex_fixes.py b/codegen/ai_horde/codegen_regex_fixes.py similarity index 89% rename from codegen/codegen_regex_fixes.py rename to codegen/ai_horde/codegen_regex_fixes.py index ca9082c..3f4a9c1 100644 --- a/codegen/codegen_regex_fixes.py +++ b/codegen/ai_horde/codegen_regex_fixes.py @@ -12,8 +12,9 @@ def main(path: str) -> None: contents = re.sub(r"example=\"(.*)\"(,)", r'examples=["\1"]\2', contents) contents = re.sub(r"example=(\d*)(,|$|\))", r"examples=[\1]\2", contents) contents = re.sub(r"example=(\d*\.\d)(,|$|\))", r"examples=[\1]\2", contents) - contents = re.sub(r"id: ", r"id_: ", contents) - contents = re.sub(r"type: ", r"type_: ", contents) + + contents = contents.replace("id: ", "id_: ") + contents = contents.replace("type: ", "type_: ") # Replace the literal string `\n` with a newline with open(path, "w", encoding="utf-8") as f: diff --git a/codegen/ai_horde/swagger.json b/codegen/ai_horde/swagger.json new file mode 100644 index 0000000..bc639ec --- /dev/null +++ b/codegen/ai_horde/swagger.json @@ -0,0 +1,6743 @@ +{ + "swagger": "2.0", + "basePath": "/api", + "paths": { + "/v2/filters": { + "post": { + "responses": { + "403": { + "description": "Access Denied", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Returns the suspicion of the provided prompt. A suspicion of 2 or more means it would be blocked.", + "schema": { + "$ref": "#/definitions/FilterPromptSuspicion" + } + } + }, + "summary": "Moderator Only: Check The suspicion of the provided prompt", + "operationId": "post_filters", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "required": true, + "description": "A mod API key." + }, + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "payload", + "required": true, + "in": "body", + "schema": { + "type": "object", + "properties": { + "prompt": { + "type": "string" + }, + "filter_type": { + "type": "integer" + } + } + } + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + }, + "put": { + "responses": { + "403": { + "description": "Access Denied", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "201": { + "description": "New Filter details", + "schema": { + "$ref": "#/definitions/FilterDetails" + } + } + }, + "summary": "Moderator Only: Add a new regex filter", + "operationId": "put_filters", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "required": true, + "description": "A mod API key." + }, + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "payload", + "required": true, + "in": "body", + "schema": { + "$ref": "#/definitions/PutNewFilter" + } + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + }, + "get": { + "responses": { + "403": { + "description": "Access Denied", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Filters List", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/FilterDetails" + } + } + } + }, + "summary": "Moderator Only: A List all filters, or filtered by the query", + "operationId": "get_filters", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "required": true, + "description": "A mod API key." + }, + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "filter_type", + "in": "query", + "type": "integer", + "description": "The filter type." + }, + { + "name": "contains", + "in": "query", + "type": "string", + "description": "Only return filter containing this word." + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/filters/regex": { + "get": { + "responses": { + "403": { + "description": "Access Denied", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Filters Regex", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/FilterRegex" + } + } + } + }, + "summary": "Moderator Only: A List all filters, or filtered by the query", + "operationId": "get_filter_regex", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "required": true, + "description": "A mod API key." + }, + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "filter_type", + "in": "query", + "type": "integer", + "description": "The filter type." + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/filters/{filter_id}": { + "parameters": [ + { + "name": "filter_id", + "in": "path", + "required": true, + "type": "string" + } + ], + "get": { + "responses": { + "403": { + "description": "Access Denied", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Filters List", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/FilterDetails" + } + } + } + }, + "summary": "Moderator Only: Display a single filter", + "operationId": "get_filter_single", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "required": true, + "description": "A mod API key." + }, + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + }, + "delete": { + "responses": { + "403": { + "description": "Access Denied", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Filter Deleted", + "schema": { + "$ref": "#/definitions/SimpleResponse" + } + } + }, + "summary": "Moderator Only: Delete a regex filter", + "operationId": "delete_filter_single", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "required": true, + "description": "A mod API key." + }, + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + }, + "patch": { + "responses": { + "403": { + "description": "Access Denied", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Patched Filter details", + "schema": { + "$ref": "#/definitions/FilterDetails" + } + } + }, + "summary": "Moderator Only: Modify an existing regex filter", + "operationId": "patch_filter_single", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "required": true, + "description": "A mod API key." + }, + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "payload", + "required": true, + "in": "body", + "schema": { + "$ref": "#/definitions/PatchExistingFilter" + } + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/find_user": { + "get": { + "responses": { + "404": { + "description": "User Not Found", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Worker Details", + "schema": { + "$ref": "#/definitions/UserDetails" + } + } + }, + "summary": "Lookup user details based on their API key", + "description": "This can be used to verify a user exists", + "operationId": "get_find_user", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "description": "User API key we're looking for." + }, + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/generate/async": { + "post": { + "responses": { + "429": { + "description": "Too Many Prompts", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "503": { + "description": "Maintenance Mode", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/RequestValidationError" + } + }, + "202": { + "description": "Generation Queued", + "schema": { + "$ref": "#/definitions/RequestAsync" + } + } + }, + "summary": "Initiate an Asynchronous request to generate images", + "description": "This endpoint will immediately return with the UUID of the request for generation.\nThis endpoint will always be accepted, even if there are no workers available currently to fulfill this request.\nPerhaps some will appear in the next 10 minutes.\nAsynchronous requests live for 10 minutes before being considered stale and being deleted.", + "operationId": "post_image_async_generate", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "required": true, + "description": "The API Key corresponding to a registered user." + }, + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version", + "default": "unknown:0:unknown" + }, + { + "name": "payload", + "required": true, + "in": "body", + "schema": { + "$ref": "#/definitions/GenerationInputStable" + } + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/generate/check/{id}": { + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + } + ], + "get": { + "responses": { + "404": { + "description": "Request Not found", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Async Request Status Check", + "schema": { + "$ref": "#/definitions/RequestStatusCheck" + } + } + }, + "summary": "Retrieve the status of an Asynchronous generation request without images", + "description": "Use this request to check the status of a currently running asynchronous request without consuming bandwidth.", + "operationId": "get_image_async_check", + "parameters": [ + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version", + "default": "unknown:0:unknown" + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/generate/pop": { + "post": { + "responses": { + "403": { + "description": "Access Denied", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Generation Popped", + "schema": { + "$ref": "#/definitions/GenerationPayloadStable" + } + } + }, + "summary": "Check if there are generation requests queued for fulfillment", + "description": "This endpoint is used by registered workers only", + "operationId": "post_image_job_pop", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "required": true, + "description": "The API Key corresponding to a registered user." + }, + { + "name": "payload", + "required": true, + "in": "body", + "schema": { + "$ref": "#/definitions/PopInputStable" + } + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/generate/rate/{id}": { + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + } + ], + "post": { + "responses": { + "404": { + "description": "Generation Request Not Found", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "400": { + "description": "Aesthetics Already Submitted", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Aesthetics Submitted", + "schema": { + "$ref": "#/definitions/GenerationSubmitted" + } + } + }, + "summary": "Submit aesthetic ratings for generated images to be used by LAION and Stability", + "description": "AI\nThe request has to have been sent as shared: true.\nYou can select the best image in the set, and/or provide a rating for each or some images in the set.\nIf you select best-of image, you will gain 4 kudos. Each rating is 5 kudos. Best-of will be ignored when ratings conflict with it.\nYou can never gain more kudos than you spent for this generation. Your reward at max will be your kudos consumption - 1.", + "operationId": "post_aesthetics", + "parameters": [ + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version", + "default": "unknown:0:unknown" + }, + { + "name": "payload", + "required": true, + "in": "body", + "schema": { + "$ref": "#/definitions/AestheticsPayload" + } + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/generate/status/{id}": { + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + } + ], + "get": { + "responses": { + "404": { + "description": "Request Not found", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Async Request Full Status", + "schema": { + "$ref": "#/definitions/RequestStatusStable" + } + } + }, + "summary": "Retrieve the full status of an Asynchronous generation request", + "description": "This request will include all already generated images in download URL or base64 encoded .webp files.\nAs such, you are requested to not retrieve this endpoint often. Instead use the /check/ endpoint first\nThis endpoint is limited to 10 request per minute", + "operationId": "get_image_async_status", + "parameters": [ + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version", + "default": "unknown:0:unknown" + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + }, + "delete": { + "responses": { + "404": { + "description": "Request Not found", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Async Request Full Status", + "schema": { + "$ref": "#/definitions/RequestStatusStable" + } + } + }, + "summary": "Cancel an unfinished request", + "description": "This request will include all already generated images in base64 encoded .webp files.", + "operationId": "delete_image_async_status", + "parameters": [ + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version", + "default": "unknown:0:unknown" + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/generate/submit": { + "post": { + "responses": { + "404": { + "description": "Request Not Found", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "403": { + "description": "Access Denied", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "400": { + "description": "Generation Already Submitted", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Generation Submitted", + "schema": { + "$ref": "#/definitions/GenerationSubmitted" + } + } + }, + "summary": "Submit a generated image", + "description": "This endpoint is used by registered workers only", + "operationId": "post_image_job_submit", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "required": true, + "description": "The worker's owner API key." + }, + { + "name": "payload", + "required": true, + "in": "body", + "schema": { + "$ref": "#/definitions/SubmitInputStable" + } + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/generate/text/async": { + "post": { + "responses": { + "429": { + "description": "Too Many Prompts", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "503": { + "description": "Maintenance Mode", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/RequestValidationError" + } + }, + "202": { + "description": "Generation Queued", + "schema": { + "$ref": "#/definitions/RequestAsync" + } + } + }, + "summary": "Initiate an Asynchronous request to generate text", + "description": "This endpoint will immediately return with the UUID of the request for generation.\nThis endpoint will always be accepted, even if there are no workers available currently to fulfill this request.\nPerhaps some will appear in the next 20 minutes.\nAsynchronous requests live for 20 minutes before being considered stale and being deleted.", + "operationId": "post_text_async_generate", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "required": true, + "description": "The API Key corresponding to a registered user." + }, + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version", + "default": "unknown:0:unknown" + }, + { + "name": "payload", + "required": true, + "in": "body", + "schema": { + "$ref": "#/definitions/GenerationInputKobold" + } + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/generate/text/pop": { + "post": { + "responses": { + "403": { + "description": "Access Denied", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Generation Popped", + "schema": { + "$ref": "#/definitions/GenerationPayload" + } + } + }, + "summary": "Check if there are generation requests queued for fulfillment", + "description": "This endpoint is used by registered workers only", + "operationId": "post_text_job_pop", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "required": true, + "description": "The API Key corresponding to a registered user." + }, + { + "name": "payload", + "required": true, + "in": "body", + "schema": { + "$ref": "#/definitions/PopInputKobold" + } + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/generate/text/status/{id}": { + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + } + ], + "get": { + "responses": { + "404": { + "description": "Request Not found", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Async Request Full Status", + "schema": { + "$ref": "#/definitions/RequestStatusKobold" + } + } + }, + "summary": "Retrieve the full status of an Asynchronous generation request", + "description": "This request will include all already generated texts.", + "operationId": "get_text_async_status", + "parameters": [ + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version", + "default": "unknown:0:unknown" + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + }, + "delete": { + "responses": { + "404": { + "description": "Request Not found", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Async Request Full Status", + "schema": { + "$ref": "#/definitions/RequestStatusKobold" + } + } + }, + "summary": "Cancel an unfinished request", + "description": "This request will include all already generated texts.", + "operationId": "delete_text_async_status", + "parameters": [ + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version", + "default": "unknown:0:unknown" + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/generate/text/submit": { + "post": { + "responses": { + "404": { + "description": "Request Not Found", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "403": { + "description": "Access Denied", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "400": { + "description": "Generation Already Submitted", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Generation Submitted", + "schema": { + "$ref": "#/definitions/GenerationSubmitted" + } + } + }, + "summary": "Submit generated text", + "description": "This endpoint is used by registered workers only", + "operationId": "post_text_job_submit", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "required": true, + "description": "The worker's owner API key." + }, + { + "name": "payload", + "required": true, + "in": "body", + "schema": { + "$ref": "#/definitions/SubmitInputKobold" + } + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/interrogate/async": { + "post": { + "responses": { + "429": { + "description": "Too Many Prompts", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "503": { + "description": "Maintenance Mode", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "202": { + "description": "Interrogation Queued", + "schema": { + "$ref": "#/definitions/RequestInterrogationResponse" + } + } + }, + "summary": "Initiate an Asynchronous request to interrogate an image", + "description": "This endpoint will immediately return with the UUID of the request for interrogation.\nThis endpoint will always be accepted, even if there are no workers available currently to fulfill this request.\nPerhaps some will appear in the next 20 minutes.\nAsynchronous requests live for 20 minutes before being considered stale and being deleted.", + "operationId": "post_interrogate", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "required": true, + "description": "A User API key" + }, + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version", + "default": "unknown:0:unknown" + }, + { + "name": "payload", + "required": true, + "in": "body", + "schema": { + "$ref": "#/definitions/ModelInterrogationInputStable" + } + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/interrogate/pop": { + "post": { + "responses": { + "403": { + "description": "Access Denied", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Interrogation Popped", + "schema": { + "$ref": "#/definitions/InterrogationPopPayload" + } + } + }, + "summary": "Check if there are interrogation requests queued for fulfillment", + "description": "This endpoint is used by registered workers only", + "operationId": "post_interrogate_pop", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "required": true, + "description": "The API Key corresponding to a registered user" + }, + { + "name": "payload", + "required": true, + "in": "body", + "schema": { + "$ref": "#/definitions/InterrogationPopInput" + } + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/interrogate/status/{id}": { + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + } + ], + "get": { + "responses": { + "404": { + "description": "Request Not found", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Interrogation Request Status", + "schema": { + "$ref": "#/definitions/InterrogationStatus" + } + } + }, + "summary": "Retrieve the full status of an interrogation request", + "description": "This request will include all already generated images.\nAs such, you are requested to not retrieve this endpoint often. Instead use the /check/ endpoint first", + "operationId": "get_interrogation_status", + "parameters": [ + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version", + "default": "unknown:0:unknown" + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + }, + "delete": { + "responses": { + "404": { + "description": "Request Not found", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Interrogation Request Status", + "schema": { + "$ref": "#/definitions/InterrogationStatus" + } + } + }, + "summary": "Cancel an unfinished interrogation request", + "description": "This request will return all already interrogated image results.", + "operationId": "delete_interrogation_status", + "parameters": [ + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version", + "default": "unknown:0:unknown" + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/interrogate/submit": { + "post": { + "responses": { + "404": { + "description": "Request Not Found", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "403": { + "description": "Access Denied", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "400": { + "description": "Generation Already Submitted", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Interrogation Submitted", + "schema": { + "$ref": "#/definitions/GenerationSubmitted" + } + } + }, + "summary": "Submit the results of an interrogated image", + "description": "This endpoint is used by registered workers only", + "operationId": "post_interrogate_submit", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "required": true, + "description": "The worker's owner API key" + }, + { + "name": "payload", + "required": true, + "in": "body", + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "result": { + "type": "string" + }, + "state": { + "type": "string" + } + } + } + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/kudos/award": { + "post": { + "responses": { + "403": { + "description": "Access Denied", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Kudos Awarded", + "schema": { + "$ref": "#/definitions/KudosAwarded" + } + } + }, + "summary": "Awards Kudos to registed user", + "description": "This API can only be used through privileged access.", + "operationId": "post_award_kudos", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "required": true, + "description": "The sending user's API key." + }, + { + "name": "payload", + "required": true, + "in": "body", + "schema": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "amount": { + "type": "integer" + } + } + } + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/kudos/transfer": { + "post": { + "responses": { + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Kudos Transferred", + "schema": { + "$ref": "#/definitions/KudosTransferred" + } + } + }, + "summary": "Transfer Kudos to another registed user", + "operationId": "post_transfer_kudos", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "required": true, + "description": "The sending user's API key." + }, + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "payload", + "required": true, + "in": "body", + "schema": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "amount": { + "type": "integer" + } + } + } + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/operations/block_worker_ipaddr/{worker_id}": { + "parameters": [ + { + "name": "worker_id", + "in": "path", + "required": true, + "type": "string" + } + ], + "put": { + "responses": { + "403": { + "description": "Access Denied", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Operation Completed", + "schema": { + "$ref": "#/definitions/SimpleResponse" + } + } + }, + "summary": "Block worker's from a specific IP for 24 hours", + "description": "Only usable by horde moderators", + "operationId": "put_operations_block_worker_ip", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "required": true, + "description": "A mod API key." + }, + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "payload", + "required": true, + "in": "body", + "schema": { + "$ref": "#/definitions/AddWorkerTimeout" + } + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + }, + "delete": { + "responses": { + "403": { + "description": "Access Denied", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Operation Completed", + "schema": { + "$ref": "#/definitions/SimpleResponse" + } + } + }, + "summary": "Remove a worker's IP block", + "description": "Only usable by horde moderators", + "operationId": "delete_operations_block_worker_ip", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "required": true, + "description": "A mod API key." + }, + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/operations/ipaddr": { + "post": { + "responses": { + "403": { + "description": "Access Denied", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Operation Completed", + "schema": { + "$ref": "#/definitions/SimpleResponse" + } + } + }, + "summary": "Add an IP or CIDR to timeout", + "description": "Only usable by horde moderators", + "operationId": "post_operations_ip", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "required": true, + "description": "A mod API key." + }, + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "payload", + "required": true, + "in": "body", + "schema": { + "$ref": "#/definitions/AddTimeoutIPInput" + } + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + }, + "get": { + "responses": { + "403": { + "description": "Access Denied", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "An IP timeout entry", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/IPTimeout" + } + } + } + }, + "summary": "Return all existing IP Block timeouts", + "operationId": "get_operations_ip", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "required": true, + "description": "A mod API key." + }, + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + }, + "delete": { + "responses": { + "403": { + "description": "Access Denied", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Operation Completed", + "schema": { + "$ref": "#/definitions/SimpleResponse" + } + } + }, + "summary": "Remove an IP from timeout", + "description": "Only usable by horde moderators", + "operationId": "delete_operations_ip", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "required": true, + "description": "A mod API key." + }, + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "payload", + "required": true, + "in": "body", + "schema": { + "$ref": "#/definitions/DeleteTimeoutIPInput" + } + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/operations/ipaddr/{ipaddr}": { + "parameters": [ + { + "name": "ipaddr", + "in": "path", + "required": true, + "type": "string" + } + ], + "get": { + "responses": { + "403": { + "description": "Access Denied", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "IP timeout entries that match IP", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/IPTimeout" + } + } + } + }, + "summary": "Check if an IP or CIDR is in timeout", + "operationId": "get_operations_ip_single", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "required": true, + "description": "A mod API key." + }, + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/sharedkeys": { + "put": { + "responses": { + "404": { + "description": "Shared Key Not Found", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "403": { + "description": "Access Denied", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "SharedKey Details", + "schema": { + "$ref": "#/definitions/SharedKeyDetails" + } + } + }, + "summary": "Create a new SharedKey for this user", + "operationId": "put_shared_key", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "required": true, + "description": "User API key." + }, + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "payload", + "required": true, + "in": "body", + "schema": { + "$ref": "#/definitions/SharedKeyInput" + } + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/sharedkeys/{sharedkey_id}": { + "parameters": [ + { + "name": "sharedkey_id", + "in": "path", + "required": true, + "type": "string" + } + ], + "get": { + "responses": { + "404": { + "description": "Shared Key Not Found", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Shared Key Details", + "schema": { + "$ref": "#/definitions/SharedKeyDetails" + } + } + }, + "summary": "Get details about an existing Shared Key", + "operationId": "get_shared_key_single", + "parameters": [ + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + }, + "delete": { + "responses": { + "404": { + "description": "Shared Key Not Found", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Shared Key Deleted", + "schema": { + "$ref": "#/definitions/SimpleResponse" + } + } + }, + "summary": "Delete an existing SharedKey for this user", + "operationId": "delete_shared_key_single", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "required": true, + "description": "User API key." + }, + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + }, + "patch": { + "responses": { + "404": { + "description": "Shared Key Not Found", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "403": { + "description": "Access Denied", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Shared Key Details", + "schema": { + "$ref": "#/definitions/SharedKeyDetails" + } + } + }, + "summary": "Modify an existing Shared Key", + "operationId": "patch_shared_key_single", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "required": true, + "description": "User API key." + }, + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version", + "default": "unknown:0:unknown" + }, + { + "name": "payload", + "required": true, + "in": "body", + "schema": { + "$ref": "#/definitions/SharedKeyInput" + } + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/stats/img/models": { + "get": { + "responses": { + "200": { + "description": "AI Horde generated images statistics per model", + "schema": { + "$ref": "#/definitions/ImgModelStats" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/RequestError" + } + } + }, + "summary": "Details how many images were generated per model for the past day, month and total", + "operationId": "get_image_horde_stats_models", + "parameters": [ + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version", + "default": "unknown:0:unknown" + }, + { + "name": "model_state", + "in": "query", + "type": "string", + "description": "If 'known', only show stats for known models in the model reference. If 'custom' only show stats for custom models. If 'all' shows stats for all models.", + "default": "known" + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/stats/img/totals": { + "get": { + "responses": { + "200": { + "description": "AI Horde generated images statistics", + "schema": { + "$ref": "#/definitions/StatsImgTotals" + } + } + }, + "summary": "Details how many images have been generated in the past minux,hour,day,month and total", + "description": "Also shows the amount of pixelsteps for the same timeframe.", + "operationId": "get_image_horde_stats_totals", + "parameters": [ + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version", + "default": "unknown:0:unknown" + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/stats/text/models": { + "get": { + "responses": { + "200": { + "description": "AI Horde generated text statistics per model", + "schema": { + "$ref": "#/definitions/TxtModelStats" + } + } + }, + "summary": "Details how many texts were generated per model for the past day, month and total", + "operationId": "get_text_horde_stats_models", + "parameters": [ + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version", + "default": "unknown:0:unknown" + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/stats/text/totals": { + "get": { + "responses": { + "200": { + "description": "AI Horde generated text statistics", + "schema": { + "$ref": "#/definitions/StatsTxtTotals" + } + } + }, + "summary": "Details how many texts have been generated in the past minux,hour,day,month and total", + "description": "Also shows the amount of pixelsteps for the same timeframe.", + "operationId": "get_text_horde_stats_totals", + "parameters": [ + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version", + "default": "unknown:0:unknown" + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/status/heartbeat": { + "get": { + "responses": { + "200": { + "description": "Success" + } + }, + "summary": "If this loads, this node is available", + "operationId": "get_heartbeat", + "parameters": [ + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/status/models": { + "get": { + "responses": { + "200": { + "description": "List All Active Models", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/ActiveModel" + } + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/RequestError" + } + } + }, + "summary": "Returns a list of models active currently in this horde", + "operationId": "get_models", + "parameters": [ + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "type", + "in": "query", + "type": "string", + "description": "Filter the models by type (image or text).", + "default": "image" + }, + { + "name": "min_count", + "in": "query", + "type": "integer", + "description": "Filter only models that have at least this amount of threads serving." + }, + { + "name": "max_count", + "in": "query", + "type": "integer", + "description": "Filter the models that have at most this amount of threads serving." + }, + { + "name": "model_state", + "in": "query", + "type": "string", + "description": "If 'known', only show stats for known models in the model reference. If 'custom' only show stats for custom models. If 'all' shows stats for all models.", + "default": "all" + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/status/models/{model_name}": { + "parameters": [ + { + "name": "model_name", + "in": "path", + "required": true, + "type": "string" + } + ], + "get": { + "responses": { + "200": { + "description": "Lists specific model stats", + "schema": { + "$ref": "#/definitions/ActiveModel" + } + } + }, + "summary": "Returns all the statistics of a specific model in this horde", + "operationId": "get_model_single", + "parameters": [ + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/status/modes": { + "put": { + "responses": { + "403": { + "description": "Access Denied", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Maintenance Mode Set", + "schema": { + "$ref": "#/definitions/HordeModes" + } + } + }, + "summary": "Change Horde Modes", + "description": "Endpoint for admins to (un)set the horde into maintenance, invite_only or raid modes.", + "operationId": "put_horde_modes", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "required": true, + "description": "The Admin API key." + }, + { + "name": "payload", + "required": true, + "in": "body", + "schema": { + "type": "object", + "properties": { + "maintenance": { + "type": "boolean" + }, + "invite_only": { + "type": "boolean" + }, + "raid": { + "type": "boolean" + } + } + } + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + }, + "get": { + "responses": { + "200": { + "description": "AI Horde Maintenance", + "schema": { + "$ref": "#/definitions/HordeModes" + } + } + }, + "summary": "Horde Maintenance Mode Status", + "description": "Use this endpoint to quicky determine if this horde is in maintenance, invite_only or raid mode.", + "operationId": "get_horde_modes", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "description": "The Admin or Owner API key." + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/status/news": { + "get": { + "responses": { + "200": { + "description": "AI Horde News", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Newspiece" + } + } + } + }, + "summary": "Read the latest happenings on the horde", + "operationId": "get_horde_news", + "parameters": [ + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/status/performance": { + "get": { + "responses": { + "200": { + "description": "AI Horde Performance", + "schema": { + "$ref": "#/definitions/HordePerformance" + } + } + }, + "summary": "Details about the current performance of this Horde", + "operationId": "get_horde_load", + "parameters": [ + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/teams": { + "post": { + "responses": { + "403": { + "description": "Access Denied", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Create Team", + "schema": { + "$ref": "#/definitions/ModifyTeam" + } + } + }, + "summary": "Create a new team", + "description": "Only trusted users can create new teams.", + "operationId": "post_teams", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "required": true, + "description": "A User API key." + }, + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "payload", + "required": true, + "in": "body", + "schema": { + "$ref": "#/definitions/CreateTeamInput" + } + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + }, + "get": { + "responses": { + "200": { + "description": "Teams List", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/TeamDetails" + } + } + } + }, + "summary": "A List with the details of all teams", + "operationId": "get_teams", + "parameters": [ + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/teams/{team_id}": { + "parameters": [ + { + "name": "team_id", + "in": "path", + "required": true, + "type": "string" + } + ], + "get": { + "responses": { + "404": { + "description": "Team Not Found", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "403": { + "description": "Access Denied", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Team Details", + "schema": { + "$ref": "#/definitions/TeamDetails" + } + } + }, + "summary": "Details of a worker Team", + "operationId": "get_team_single", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "description": "The Moderator or Owner API key." + }, + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + }, + "delete": { + "responses": { + "404": { + "description": "Team Not Found", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "403": { + "description": "Access Denied", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Delete Team", + "schema": { + "$ref": "#/definitions/DeletedTeam" + } + } + }, + "summary": "Delete the team entry", + "description": "Only the team's creator or a horde moderator can use this endpoint.\nThis action is unrecoverable!", + "operationId": "delete_team_single", + "parameters": [ + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "apikey", + "in": "header", + "type": "string", + "description": "The Moderator or Owner API key." + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + }, + "patch": { + "responses": { + "404": { + "description": "Team Not Found", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "403": { + "description": "Access Denied", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Modify Team", + "schema": { + "$ref": "#/definitions/ModifyTeam" + } + } + }, + "summary": "Update a Team's information", + "operationId": "patch_team_single", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "description": "The Moderator or Creator API key." + }, + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "payload", + "required": true, + "in": "body", + "schema": { + "$ref": "#/definitions/ModifyTeamInput" + } + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/users": { + "get": { + "responses": { + "200": { + "description": "Users List", + "schema": { + "$ref": "#/definitions/UserDetails" + } + } + }, + "summary": "A List with the details and statistic of all registered users", + "operationId": "get_users", + "parameters": [ + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "page", + "in": "query", + "type": "integer", + "description": "Which page of results to return. Each page has 25 users.", + "default": 1 + }, + { + "name": "sort", + "in": "query", + "type": "string", + "description": "How to sort the returned list.", + "default": "kudos" + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/users/{user_id}": { + "parameters": [ + { + "name": "user_id", + "in": "path", + "required": true, + "type": "string" + } + ], + "put": { + "responses": { + "404": { + "description": "Worker Not Found", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "403": { + "description": "Access Denied", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Modify User", + "schema": { + "$ref": "#/definitions/ModifyUser" + } + } + }, + "summary": "Endpoint for horde admins to perform operations on users", + "operationId": "put_user_single", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "required": true, + "description": "The Admin API ." + }, + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "payload", + "required": true, + "in": "body", + "schema": { + "$ref": "#/definitions/ModifyUserInput" + } + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + }, + "get": { + "responses": { + "404": { + "description": "User Not Found", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "User Details", + "schema": { + "$ref": "#/definitions/UserDetails" + } + } + }, + "summary": "Details and statistics about a specific user", + "operationId": "get_user_single", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "description": "The Admin, Mod or Owner API key." + }, + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/workers": { + "get": { + "responses": { + "200": { + "description": "Workers List", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/WorkerDetails" + } + } + } + }, + "summary": "A List with the details of all registered and active workers", + "operationId": "get_workers", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "description": "A Moderator API key." + }, + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "type", + "in": "query", + "type": "string", + "description": "Filter the workers by type (image, text or interrogation)." + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + }, + "/v2/workers/{worker_id}": { + "parameters": [ + { + "name": "worker_id", + "in": "path", + "required": true, + "type": "string" + } + ], + "put": { + "responses": { + "404": { + "description": "Worker Not Found", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "403": { + "description": "Access Denied", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "400": { + "description": "Validation Error", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Modify Worker", + "schema": { + "$ref": "#/definitions/ModifyWorker" + } + } + }, + "operationId": "put_worker_single", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "required": true, + "description": "The Moderator or Owner API key." + }, + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "payload", + "required": true, + "in": "body", + "schema": { + "$ref": "#/definitions/ModifyWorkerInput" + } + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + }, + "get": { + "responses": { + "404": { + "description": "Worker Not Found", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "403": { + "description": "Access Denied", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Worker Details", + "schema": { + "$ref": "#/definitions/WorkerDetails" + } + } + }, + "summary": "Details of a registered worker", + "description": "Can retrieve the details of a worker even if inactive\n(A worker is considered inactive if it has not checked in for 5 minutes)", + "operationId": "get_worker_single", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "description": "The Moderator or Owner API key." + }, + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + }, + "delete": { + "responses": { + "404": { + "description": "Worker Not Found", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "403": { + "description": "Access Denied", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "401": { + "description": "Invalid API Key", + "schema": { + "$ref": "#/definitions/RequestError" + } + }, + "200": { + "description": "Delete Worker", + "schema": { + "$ref": "#/definitions/DeletedWorker" + } + } + }, + "summary": "Delete the worker entry", + "description": "This will delete the worker and their statistics. Will not affect the kudos generated by that worker for their owner.\nOnly the worker's owner and an admin can use this endpoint.\nThis action is unrecoverable!", + "operationId": "delete_worker_single", + "parameters": [ + { + "name": "apikey", + "in": "header", + "type": "string", + "description": "The Moderator or Owner API key." + }, + { + "name": "Client-Agent", + "in": "header", + "type": "string", + "description": "The client name and version.", + "default": "unknown:0:unknown" + }, + { + "name": "X-Fields", + "in": "header", + "type": "string", + "format": "mask", + "description": "An optional fields mask" + } + ], + "tags": [ + "v2" + ] + } + } + }, + "info": { + "title": "AI Horde", + "version": "2.0", + "description": "The API documentation for the AI Horde" + }, + "produces": [ + "application/json" + ], + "consumes": [ + "application/json" + ], + "tags": [ + { + "name": "v2", + "description": "API Version 2" + } + ], + "definitions": { + "GenerationInputStable": { + "required": [ + "prompt" + ], + "properties": { + "prompt": { + "type": "string", + "description": "The prompt which will be sent to Stable Diffusion to generate an image.", + "minLength": 1 + }, + "params": { + "$ref": "#/definitions/ModelGenerationInputStable" + }, + "nsfw": { + "type": "boolean", + "description": "Set to true if this request is NSFW. This will skip workers which censor images.", + "default": false + }, + "trusted_workers": { + "type": "boolean", + "description": "When true, only trusted workers will serve this request. When False, Evaluating workers will also be used which can increase speed but adds more risk!", + "default": false + }, + "slow_workers": { + "type": "boolean", + "description": "When True, allows slower workers to pick up this request. Disabling this incurs an extra kudos cost.", + "default": true + }, + "censor_nsfw": { + "type": "boolean", + "description": "If the request is SFW, and the worker accidentally generates NSFW, it will send back a censored image.", + "default": false + }, + "workers": { + "type": "array", + "items": { + "type": "string", + "description": "Specify up to 5 workers which are allowed to service this request." + } + }, + "worker_blacklist": { + "type": "boolean", + "description": "If true, the worker list will be treated as a blacklist instead of a whitelist.", + "default": false + }, + "models": { + "type": "array", + "items": { + "type": "string", + "description": "Specify which models are allowed to be used for this request." + } + }, + "source_image": { + "type": "string", + "description": "The Base64-encoded webp to use for img2img." + }, + "source_processing": { + "type": "string", + "description": "If source_image is provided, specifies how to process it.", + "default": "img2img", + "example": "img2img", + "enum": [ + "img2img", + "inpainting", + "outpainting", + "remix" + ] + }, + "source_mask": { + "type": "string", + "description": "If source_processing is set to 'inpainting' or 'outpainting', this parameter can be optionally provided as the Base64-encoded webp mask of the areas to inpaint. If this arg is not passed, the inpainting/outpainting mask has to be embedded as alpha channel." + }, + "extra_source_images": { + "type": "array", + "items": { + "$ref": "#/definitions/ExtraSourceImage" + } + }, + "r2": { + "type": "boolean", + "description": "If True, the image will be sent via cloudflare r2 download link.", + "default": true + }, + "shared": { + "type": "boolean", + "description": "If True, The image will be shared with LAION for improving their dataset. This will also reduce your kudos consumption by 2. For anonymous users, this is always True.", + "default": false + }, + "replacement_filter": { + "type": "boolean", + "description": "If enabled, suspicious prompts are sanitized through a string replacement filter instead.", + "default": true + }, + "dry_run": { + "type": "boolean", + "description": "When true, the endpoint will simply return the cost of the request in kudos and exit.", + "default": false + }, + "proxied_account": { + "type": "string", + "description": "If using a service account as a proxy, provide this value to identify the actual account from which this request is coming from." + }, + "disable_batching": { + "type": "boolean", + "description": "When true, This request will not use batching. This will allow you to retrieve accurate seeds. Feature is restricted to Trusted users and Patreons.", + "default": false + }, + "allow_downgrade": { + "type": "boolean", + "description": "When true and the request requires upfront kudos and the account does not have enough The request will be downgraded in steps and resolution so that it does not need upfront kudos.", + "default": false + }, + "webhook": { + "type": "string", + "description": "Provide a URL where the AI Horde will send a POST call after each delivered generation. The request will include the details of the job as well as the request ID.", + "example": "https://haidra.net/00000000-0000-0000-0000-000000000000", + "minLength": 10, + "maxLength": 1024 + } + }, + "type": "object" + }, + "ModelGenerationInputStable": { + "allOf": [ + { + "$ref": "#/definitions/ModelPayloadRootStable" + }, + { + "properties": { + "steps": { + "type": "integer", + "default": 30, + "minimum": 1, + "maximum": 500 + }, + "n": { + "type": "integer", + "description": "The amount of images to generate.", + "default": 1, + "minimum": 1, + "maximum": 20 + } + }, + "type": "object" + } + ] + }, + "ModelPayloadRootStable": { + "properties": { + "sampler_name": { + "type": "string", + "default": "k_euler_a", + "example": "k_euler", + "enum": [ + "k_euler", + "lcm", + "k_euler_a", + "k_dpmpp_2s_a", + "DDIM", + "dpmsolver", + "k_lms", + "k_dpm_fast", + "k_dpmpp_sde", + "k_dpm_2", + "k_dpm_2_a", + "k_dpm_adaptive", + "k_heun", + "k_dpmpp_2m" + ] + }, + "cfg_scale": { + "type": "number", + "default": 7.5, + "minimum": 0, + "maximum": 100 + }, + "denoising_strength": { + "type": "number", + "example": 0.75, + "minimum": 0.01, + "maximum": 1 + }, + "seed": { + "type": "string", + "description": "The seed to use to generate this request. You can pass text as well as numbers.", + "example": "The little seed that could" + }, + "height": { + "type": "integer", + "description": "The height of the image to generate.", + "default": 512, + "minimum": 64, + "maximum": 3072, + "multipleOf": 64 + }, + "width": { + "type": "integer", + "description": "The width of the image to generate.", + "default": 512, + "minimum": 64, + "maximum": 3072, + "multipleOf": 64 + }, + "seed_variation": { + "type": "integer", + "description": "If passed with multiple n, the provided seed will be incremented every time by this value.", + "example": 1, + "minimum": 1, + "maximum": 1000 + }, + "post_processing": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "description": "The list of post-processors to apply to the image, in the order to be applied.", + "example": "GFPGAN", + "enum": [ + "GFPGAN", + "RealESRGAN_x4plus", + "RealESRGAN_x2plus", + "RealESRGAN_x4plus_anime_6B", + "NMKD_Siax", + "4x_AnimeSharp", + "CodeFormers", + "strip_background" + ] + } + }, + "karras": { + "type": "boolean", + "description": "Set to True to enable karras noise scheduling tweaks.", + "default": false + }, + "tiling": { + "type": "boolean", + "description": "Set to True to create images that stitch together seamlessly.", + "default": false + }, + "hires_fix": { + "type": "boolean", + "description": "Set to True to process the image at base resolution before upscaling and re-processing.", + "default": false + }, + "clip_skip": { + "type": "integer", + "description": "The number of CLIP language processor layers to skip.", + "example": 1, + "minimum": 1, + "maximum": 12 + }, + "control_type": { + "type": "string", + "example": "canny", + "enum": [ + "canny", + "hed", + "depth", + "normal", + "openpose", + "seg", + "scribble", + "fakescribbles", + "hough" + ] + }, + "image_is_control": { + "type": "boolean", + "description": "Set to True if the image submitted is a pre-generated control map for ControlNet use.", + "default": false + }, + "return_control_map": { + "type": "boolean", + "description": "Set to True if you want the ControlNet map returned instead of a generated image.", + "default": false + }, + "facefixer_strength": { + "type": "number", + "example": 0.75, + "minimum": 0, + "maximum": 1 + }, + "loras": { + "type": "array", + "items": { + "$ref": "#/definitions/ModelPayloadLorasStable" + } + }, + "tis": { + "type": "array", + "items": { + "$ref": "#/definitions/ModelPayloadTextualInversionsStable" + } + }, + "special": { + "$ref": "#/definitions/ModelSpecialPayloadStable" + } + }, + "type": "object" + }, + "ModelPayloadLorasStable": { + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The exact name or CivitAI Model Page ID of the LoRa. If is_version is true, this should be the CivitAI version ID.", + "example": "Magnagothica", + "minLength": 1, + "maxLength": 255 + }, + "model": { + "type": "number", + "description": "The strength of the LoRa to apply to the SD model.", + "default": 1, + "minimum": -5, + "maximum": 5 + }, + "clip": { + "type": "number", + "description": "The strength of the LoRa to apply to the clip model.", + "default": 1, + "minimum": -5, + "maximum": 5 + }, + "inject_trigger": { + "type": "string", + "description": "If set, will try to discover a trigger for this LoRa which matches or is similar to this string and inject it into the prompt. If 'any' is specified it will be pick the first trigger.", + "minLength": 1, + "maxLength": 30 + }, + "is_version": { + "type": "boolean", + "description": "If true, will consider the LoRa ID as a CivitAI version ID and search accordingly. Ensure the name is an integer.", + "default": false + } + }, + "type": "object" + }, + "ModelPayloadTextualInversionsStable": { + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The exact name or CivitAI ID of the Textual Inversion.", + "example": "7808", + "minLength": 1, + "maxLength": 255 + }, + "inject_ti": { + "type": "string", + "description": "If set, Will automatically add this TI filename to the prompt or negative prompt accordingly using the provided strength. If this is set to None, then the user will have to manually add the embed to the prompt themselves.", + "example": "prompt", + "enum": [ + "prompt", + "negprompt" + ] + }, + "strength": { + "type": "number", + "description": "The strength with which to apply the TI to the prompt. Only used when inject_ti is not None", + "default": 1, + "minimum": -5, + "maximum": 5 + } + }, + "type": "object" + }, + "ModelSpecialPayloadStable": { + "properties": { + "*": { + "type": "object", + "additionalProperties": { + "type": "object" + } + } + }, + "type": "object" + }, + "ExtraSourceImage": { + "properties": { + "image": { + "type": "string", + "description": "The Base64-encoded webp to use for further processing." + }, + "strength": { + "type": "number", + "description": "Optional field, determining the strength to use for the processing", + "default": 1 + } + }, + "type": "object" + }, + "RequestError": { + "required": [ + "rc" + ], + "properties": { + "message": { + "type": "string", + "description": "The error message for this status code." + }, + "rc": { + "type": "string", + "description": "The return code for this error. See: https://github.com/Haidra-Org/AI-Horde/blob/main/README_return_codes.md", + "example": "ExampleHordeError", + "enum": [ + "MissingPrompt", + "CorruptPrompt", + "KudosValidationError", + "NoValidActions", + "InvalidSize", + "InvalidPromptSize", + "TooManySteps", + "Profanity", + "ProfaneWorkerName", + "ProfaneBridgeAgent", + "ProfaneWorkerInfo", + "ProfaneUserName", + "ProfaneUserContact", + "ProfaneAdminComment", + "ProfaneTeamName", + "ProfaneTeamInfo", + "TooLong", + "TooLongWorkerName", + "TooLongUserName", + "NameAlreadyExists", + "WorkerNameAlreadyExists", + "TeamNameAlreadyExists", + "PolymorphicNameConflict", + "ImageValidationFailed", + "SourceImageResolutionExceeded", + "SourceImageSizeExceeded", + "SourceImageUrlInvalid", + "SourceImageUnreadable", + "InpaintingMissingMask", + "SourceMaskUnnecessary", + "UnsupportedSampler", + "UnsupportedModel", + "ControlNetUnsupported", + "ControlNetSourceMissing", + "ControlNetInvalidPayload", + "SourceImageRequiredForModel", + "UnexpectedModelName", + "TooManyUpscalers", + "ProcGenNotFound", + "InvalidAestheticAttempt", + "AestheticsNotCompleted", + "AestheticsNotPublic", + "AestheticsDuplicate", + "AestheticsMissing", + "AestheticsSolo", + "AestheticsConfused", + "AestheticsAlreadyExist", + "AestheticsServerRejected", + "AestheticsServerError", + "AestheticsServerDown", + "AestheticsServerTimeout", + "InvalidAPIKey", + "WrongCredentials", + "NotAdmin", + "NotModerator", + "NotOwner", + "NotPrivileged", + "AnonForbidden", + "AnonForbiddenWorker", + "AnonForbiddenUserMod", + "NotTrusted", + "UntrustedTeamCreation", + "UntrustedUnsafeIP", + "WorkerMaintenance", + "WorkerFlaggedMaintenance", + "TooManySameIPs", + "WorkerInviteOnly", + "UnsafeIP", + "TimeoutIP", + "TooManyNewIPs", + "KudosUpfront", + "SharedKeyEmpty", + "SharedKeyExpired", + "SharedKeyInsufficientKudos", + "InvalidJobID", + "RequestNotFound", + "WorkerNotFound", + "TeamNotFound", + "FilterNotFound", + "UserNotFound", + "DuplicateGen", + "AbortedGen", + "RequestExpired", + "TooManyPrompts", + "NoValidWorkers", + "MaintenanceMode", + "TargetAccountFlagged", + "SourceAccountFlagged", + "FaultWhenKudosReceiving", + "FaultWhenKudosSending", + "TooFastKudosTransfers", + "KudosTransferToAnon", + "KudosTransferToSelf", + "KudosTransferNotEnough", + "NegativeKudosTransfer", + "KudosTransferFromAnon", + "InvalidAwardUsername", + "KudosAwardToAnon", + "NotAllowedAwards", + "NoWorkerModSelected", + "NoUserModSelected", + "NoHordeModSelected", + "NoTeamModSelected", + "NoFilterModSelected", + "NoSharedKeyModSelected", + "BadRequest", + "Forbidden", + "Locked", + "ControlNetMismatch", + "HiResFixMismatch", + "TooManyLoras", + "BadLoraVersion", + "TooManyTIs", + "BetaAnonForbidden", + "BetaComparisonFault", + "BadCFGDecimals", + "BadCFGNumber", + "BadClientAgent", + "SpecialMissingPayload", + "SpecialForbidden", + "SpecialMissingUsername", + "SpecialModelNeedsSpecialUser", + "SpecialFieldNeedsSpecialUser", + "Img2ImgMismatch", + "TilingMismatch", + "EducationCannotSendKudos", + "InvalidPriorityUsername", + "OnlyServiceAccountProxy", + "RequiresTrust", + "InvalidRemixModel", + "InvalidExtraSourceImages", + "TooManyExtraSourceImages", + "MissingFullSamplerOrder", + "TooManyStopSequences", + "ExcessiveStopSequence", + "TokenOverflow", + "MoreThanMinExtraSourceImage" + ] + } + }, + "type": "object" + }, + "RequestValidationError": { + "allOf": [ + { + "$ref": "#/definitions/RequestError" + }, + { + "properties": { + "errors": { + "type": "object", + "additionalProperties": { + "type": "string", + "description": "The details of the validation error" + } + } + }, + "type": "object" + } + ] + }, + "RequestAsync": { + "properties": { + "id": { + "type": "string", + "description": "The UUID of the request. Use this to retrieve the request status in the future." + }, + "kudos": { + "type": "number", + "description": "The expected kudos consumption for this request." + }, + "message": { + "type": "string", + "description": "Any extra information from the horde about this request." + }, + "warnings": { + "type": "array", + "items": { + "$ref": "#/definitions/RequestSingleWarning" + } + } + }, + "type": "object" + }, + "RequestSingleWarning": { + "properties": { + "code": { + "type": "string", + "description": "A unique identifier for this warning.", + "example": "NoAvailableWorker", + "enum": [ + "NoAvailableWorker", + "ClipSkipMismatch", + "StepsTooFew", + "StepsTooMany", + "CfgScaleMismatch", + "CfgScaleTooSmall", + "CfgScaleTooLarge", + "SamplerMismatch", + "SchedulerMismatch" + ] + }, + "message": { + "type": "string", + "description": "Something that you should be aware about this request, in plain text.", + "minLength": 1 + } + }, + "type": "object" + }, + "RequestStatusStable": { + "allOf": [ + { + "$ref": "#/definitions/RequestStatusCheck" + }, + { + "properties": { + "generations": { + "type": "array", + "items": { + "$ref": "#/definitions/GenerationStable" + } + }, + "shared": { + "type": "boolean", + "description": "If True, These images have been shared with LAION." + } + }, + "type": "object" + } + ] + }, + "RequestStatusCheck": { + "properties": { + "finished": { + "type": "integer", + "description": "The amount of finished jobs in this request." + }, + "processing": { + "type": "integer", + "description": "The amount of still processing jobs in this request." + }, + "restarted": { + "type": "integer", + "description": "The amount of jobs that timed out and had to be restarted or were reported as failed by a worker." + }, + "waiting": { + "type": "integer", + "description": "The amount of jobs waiting to be picked up by a worker." + }, + "done": { + "type": "boolean", + "description": "True when all jobs in this request are done. Else False." + }, + "faulted": { + "type": "boolean", + "description": "True when this request caused an internal server error and could not be completed.", + "default": false + }, + "wait_time": { + "type": "integer", + "description": "The expected amount to wait (in seconds) to generate all jobs in this request." + }, + "queue_position": { + "type": "integer", + "description": "The position in the requests queue. This position is determined by relative Kudos amounts." + }, + "kudos": { + "type": "number", + "description": "The amount of total Kudos this request has consumed until now." + }, + "is_possible": { + "type": "boolean", + "description": "If False, this request will not be able to be completed with the pool of workers currently available.", + "default": true + } + }, + "type": "object" + }, + "GenerationStable": { + "allOf": [ + { + "$ref": "#/definitions/Generation" + }, + { + "properties": { + "img": { + "type": "string", + "title": "Generated Image", + "description": "The generated image as a Base64-encoded .webp file." + }, + "seed": { + "type": "string", + "title": "Generation Seed", + "description": "The seed which generated this image." + }, + "id": { + "type": "string", + "title": "Generation ID", + "description": "The ID for this image." + }, + "censored": { + "type": "boolean", + "description": "When true this image has been censored by the worker's safety filter." + }, + "gen_metadata": { + "type": "array", + "items": { + "$ref": "#/definitions/GenerationMetadataStable" + } + } + }, + "type": "object" + } + ] + }, + "Generation": { + "required": [ + "state" + ], + "properties": { + "worker_id": { + "type": "string", + "title": "Worker ID", + "description": "The UUID of the worker which generated this image." + }, + "worker_name": { + "type": "string", + "title": "Worker Name", + "description": "The name of the worker which generated this image." + }, + "model": { + "type": "string", + "title": "Generation Model", + "description": "The model which generated this image." + }, + "state": { + "type": "string", + "title": "Generation State", + "description": "OBSOLETE (Use the gen_metadata field). The state of this generation.", + "default": "ok", + "example": "ok", + "enum": [ + "ok", + "censored" + ] + } + }, + "type": "object" + }, + "GenerationMetadataStable": { + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "type": "string", + "description": "The relevance of the metadata field", + "example": "lora", + "enum": [ + "lora", + "ti", + "censorship", + "source_image", + "source_mask", + "extra_source_images", + "batch_index" + ] + }, + "value": { + "type": "string", + "description": "The value of the metadata field", + "example": "download_failed", + "enum": [ + "download_failed", + "parse_failed", + "baseline_mismatch", + "csam", + "nsfw", + "see_ref" + ] + }, + "ref": { + "type": "string", + "description": "Optionally a reference for the metadata (e.g. a lora ID)", + "maxLength": 255 + } + }, + "type": "object" + }, + "AestheticsPayload": { + "properties": { + "best": { + "type": "string", + "description": "The UUID of the best image in this generation batch (only used when 2+ images generated). If 2+ aesthetic ratings are also provided, then they take precedence if they're not tied.", + "example": "6038971e-f0b0-4fdd-a3bb-148f561f815e", + "minLength": 36, + "maxLength": 36 + }, + "ratings": { + "type": "array", + "items": { + "$ref": "#/definitions/AestheticRating" + } + } + }, + "type": "object" + }, + "AestheticRating": { + "required": [ + "id", + "rating" + ], + "properties": { + "id": { + "type": "string", + "description": "The UUID of image being rated.", + "example": "6038971e-f0b0-4fdd-a3bb-148f561f815e", + "minLength": 36, + "maxLength": 36 + }, + "rating": { + "type": "integer", + "description": "The aesthetic rating 1-10 for this image.", + "minimum": 1, + "maximum": 10 + }, + "artifacts": { + "type": "integer", + "description": "The artifacts rating for this image.\n0 for flawless generation that perfectly fits to the prompt.\n1 for small, hardly recognizable flaws.\n2 small flaws that can easily be spotted, but don not harm the aesthetic experience.\n3 for flaws that look obviously wrong, but only mildly harm the aesthetic experience.\n4 for flaws that look obviously wrong & significantly harm the aesthetic experience.\n5 for flaws that make the image look like total garbage.", + "example": 1, + "minimum": 0, + "maximum": 5 + } + }, + "type": "object" + }, + "GenerationSubmitted": { + "properties": { + "reward": { + "type": "number", + "description": "The amount of kudos gained for submitting this request.", + "example": 10 + } + }, + "type": "object" + }, + "PopInputStable": { + "allOf": [ + { + "$ref": "#/definitions/PopInput" + }, + { + "properties": { + "max_pixels": { + "type": "integer", + "description": "The maximum amount of pixels this worker can generate.", + "default": 262144 + }, + "blacklist": { + "type": "array", + "items": { + "type": "string", + "description": "Words which, when detected will refuste to pick up any jobs." + } + }, + "allow_img2img": { + "type": "boolean", + "description": "If True, this worker will pick up img2img requests.", + "default": true + }, + "allow_painting": { + "type": "boolean", + "description": "If True, this worker will pick up inpainting/outpainting requests.", + "default": true + }, + "allow_unsafe_ipaddr": { + "type": "boolean", + "description": "If True, this worker will pick up img2img requests coming from clients with an unsafe IP.", + "default": true + }, + "allow_post_processing": { + "type": "boolean", + "description": "If True, this worker will pick up requests requesting post-processing.", + "default": true + }, + "allow_controlnet": { + "type": "boolean", + "description": "If True, this worker will pick up requests requesting ControlNet.", + "default": true + }, + "allow_lora": { + "type": "boolean", + "description": "If True, this worker will pick up requests requesting LoRas.", + "default": true + } + }, + "type": "object" + } + ] + }, + "PopInput": { + "properties": { + "name": { + "type": "string", + "description": "The Name of the Worker." + }, + "priority_usernames": { + "type": "array", + "items": { + "type": "string", + "description": "Users with priority to use this worker." + } + }, + "nsfw": { + "type": "boolean", + "description": "Whether this worker can generate NSFW requests or not.", + "default": false + }, + "models": { + "type": "array", + "items": { + "type": "string", + "description": "Which models this worker is serving.", + "minLength": 3, + "maxLength": 255 + } + }, + "bridge_agent": { + "type": "string", + "description": "The worker name, version and website.", + "default": "unknown:0:unknown", + "example": "AI Horde Worker reGen:4.1.0:https://github.com/Haidra-Org/horde-worker-reGen", + "maxLength": 1000 + }, + "threads": { + "type": "integer", + "description": "How many threads this worker is running. This is used to accurately the current power available in the horde.", + "default": 1, + "minimum": 1, + "maximum": 50 + }, + "require_upfront_kudos": { + "type": "boolean", + "description": "If True, this worker will only pick up requests where the owner has the required kudos to consume already available.", + "default": false, + "example": false + }, + "amount": { + "type": "integer", + "description": "How many jobvs to pop at the same time", + "default": 1, + "minimum": 1, + "maximum": 20 + } + }, + "type": "object" + }, + "GenerationPayloadStable": { + "properties": { + "payload": { + "$ref": "#/definitions/ModelPayloadStable" + }, + "id": { + "type": "string", + "description": "The UUID for this image generation." + }, + "ids": { + "type": "array", + "items": { + "type": "string", + "description": "The UUID for this image generation.", + "example": "00000000-0000-0000-0000-000000000000" + } + }, + "skipped": { + "$ref": "#/definitions/NoValidRequestFoundStable" + }, + "model": { + "type": "string", + "description": "Which of the available models to use for this request." + }, + "source_image": { + "type": "string", + "description": "The Base64-encoded webp to use for img2img." + }, + "source_processing": { + "type": "string", + "description": "If source_image is provided, specifies how to process it.", + "default": "img2img", + "example": "img2img", + "enum": [ + "img2img", + "inpainting", + "outpainting", + "remix" + ] + }, + "source_mask": { + "type": "string", + "description": "If img_processing is set to 'inpainting' or 'outpainting', this parameter can be optionally provided as the mask of the areas to inpaint. If this arg is not passed, the inpainting/outpainting mask has to be embedded as alpha channel." + }, + "extra_source_images": { + "type": "array", + "items": { + "$ref": "#/definitions/ExtraSourceImage" + } + }, + "r2_upload": { + "type": "string", + "description": "The r2 upload link to use to upload this image." + }, + "r2_uploads": { + "type": "array", + "items": { + "type": "string", + "description": "The r2 upload link to use to upload this image." + } + } + }, + "type": "object" + }, + "ModelPayloadStable": { + "allOf": [ + { + "$ref": "#/definitions/ModelPayloadRootStable" + }, + { + "properties": { + "prompt": { + "type": "string", + "description": "The prompt which will be sent to Stable Diffusion to generate an image." + }, + "ddim_steps": { + "type": "integer", + "default": 30 + }, + "n_iter": { + "type": "integer", + "description": "The amount of images to generate.", + "default": 1 + }, + "use_nsfw_censor": { + "type": "boolean", + "description": "When true will apply NSFW censoring model on the generation." + } + }, + "type": "object" + } + ] + }, + "NoValidRequestFoundStable": { + "allOf": [ + { + "$ref": "#/definitions/NoValidRequestFound" + }, + { + "properties": { + "max_pixels": { + "type": "integer", + "description": "How many waiting requests were skipped because they demanded a higher size than this worker provides." + }, + "unsafe_ip": { + "type": "integer", + "description": "How many waiting requests were skipped because they came from an unsafe IP." + }, + "img2img": { + "type": "integer", + "description": "How many waiting requests were skipped because they requested img2img." + }, + "painting": { + "type": "integer", + "description": "How many waiting requests were skipped because they requested inpainting/outpainting." + }, + "post-processing": { + "type": "integer", + "description": "How many waiting requests were skipped because they requested post-processing." + }, + "lora": { + "type": "integer", + "description": "How many waiting requests were skipped because they requested loras." + }, + "controlnet": { + "type": "integer", + "description": "How many waiting requests were skipped because they requested a controlnet." + } + }, + "type": "object" + } + ] + }, + "NoValidRequestFound": { + "properties": { + "worker_id": { + "type": "integer", + "description": "How many waiting requests were skipped because they demanded a specific worker.", + "minimum": 0 + }, + "performance": { + "type": "integer", + "description": "How many waiting requests were skipped because they required higher performance.", + "minimum": 0 + }, + "nsfw": { + "type": "integer", + "description": "How many waiting requests were skipped because they demanded a nsfw generation which this worker does not provide.", + "minimum": 0 + }, + "blacklist": { + "type": "integer", + "description": "How many waiting requests were skipped because they demanded a generation with a word that this worker does not accept.", + "minimum": 0 + }, + "untrusted": { + "type": "integer", + "description": "How many waiting requests were skipped because they demanded a trusted worker which this worker is not.", + "minimum": 0 + }, + "models": { + "type": "integer", + "description": "How many waiting requests were skipped because they demanded a different model than what this worker provides.", + "example": 0, + "minimum": 0 + }, + "bridge_version": { + "type": "integer", + "description": "How many waiting requests were skipped because they require a higher version of the bridge than this worker is running (upgrade if you see this in your skipped list).", + "example": 0, + "minimum": 0 + }, + "kudos": { + "type": "integer", + "description": "How many waiting requests were skipped because the user didn't have enough kudos when this worker requires upfront kudos." + } + }, + "type": "object" + }, + "SubmitInputStable": { + "allOf": [ + { + "$ref": "#/definitions/SubmitInput" + }, + { + "required": [ + "seed" + ], + "properties": { + "seed": { + "type": "integer", + "description": "The seed for this generation." + }, + "censored": { + "type": "boolean", + "description": "OBSOLETE (start using meta): If True, this resulting image has been censored.", + "default": false + }, + "gen_metadata": { + "type": "array", + "items": { + "$ref": "#/definitions/GenerationMetadataStable" + } + } + }, + "type": "object" + } + ] + }, + "SubmitInput": { + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "description": "The UUID of this generation.", + "example": "00000000-0000-0000-0000-000000000000" + }, + "generation": { + "type": "string", + "description": "R2 result was uploaded to R2, else the string of the result.", + "example": "R2" + }, + "state": { + "type": "string", + "title": "Generation State", + "description": "The state of this generation.", + "default": "ok", + "example": "ok", + "enum": [ + "ok", + "censored", + "faulted", + "csam" + ] + } + }, + "type": "object" + }, + "GenerationInputKobold": { + "properties": { + "prompt": { + "type": "string", + "description": "The prompt which will be sent to KoboldAI to generate text." + }, + "params": { + "$ref": "#/definitions/ModelGenerationInputKobold" + }, + "softprompt": { + "type": "string", + "description": "Specify which softpompt needs to be used to service this request.", + "minLength": 1 + }, + "trusted_workers": { + "type": "boolean", + "description": "When true, only trusted workers will serve this request. When False, Evaluating workers will also be used which can increase speed but adds more risk!", + "default": false + }, + "slow_workers": { + "type": "boolean", + "description": "When True, allows slower workers to pick up this request. Disabling this incurs an extra kudos cost.", + "default": true + }, + "workers": { + "type": "array", + "items": { + "type": "string", + "description": "Specify up to 5 workers which are allowed to service this request." + } + }, + "worker_blacklist": { + "type": "boolean", + "description": "If true, the worker list will be treated as a blacklist instead of a whitelist.", + "default": false + }, + "models": { + "type": "array", + "items": { + "type": "string", + "description": "Specify which models are allowed to be used for this request." + } + }, + "dry_run": { + "type": "boolean", + "description": "When true, the endpoint will simply return the cost of the request in kudos and exit.", + "default": false + }, + "proxied_account": { + "type": "string", + "description": "If using a service account as a proxy, provide this value to identify the actual account from which this request is coming from." + }, + "extra_source_images": { + "type": "array", + "items": { + "$ref": "#/definitions/ExtraSourceImage" + } + }, + "disable_batching": { + "type": "boolean", + "description": "When true, This request will not use batching. This will allow you to retrieve accurate seeds. Feature is restricted to Trusted users and Patreons.", + "default": false + }, + "allow_downgrade": { + "type": "boolean", + "description": "When true and the request requires upfront kudos and the account does not have enough The request will be downgraded in max context and max tokens so that it does not need upfront kudos.", + "default": false + }, + "webhook": { + "type": "string", + "description": "Provide a URL where the AI Horde will send a POST call after each delivered generation. The request will include the details of the job as well as the request ID." + } + }, + "type": "object" + }, + "ModelGenerationInputKobold": { + "allOf": [ + { + "$ref": "#/definitions/ModelPayloadRootKobold" + }, + { + "properties": { + + }, + "type": "object" + } + ] + }, + "ModelPayloadRootKobold": { + "properties": { + "n": { + "type": "integer", + "example": 1, + "minimum": 1, + "maximum": 20 + }, + "frmtadsnsp": { + "type": "boolean", + "description": "Input formatting option. When enabled, adds a leading space to your input if there is no trailing whitespace at the end of the previous action.", + "example": false + }, + "frmtrmblln": { + "type": "boolean", + "description": "Output formatting option. When enabled, replaces all occurrences of two or more consecutive newlines in the output with one newline.", + "example": false + }, + "frmtrmspch": { + "type": "boolean", + "description": "Output formatting option. When enabled, removes #/@%}{+=~|\\^\u003C\u003E from the output.", + "example": false + }, + "frmttriminc": { + "type": "boolean", + "description": "Output formatting option. When enabled, removes some characters from the end of the output such that the output doesn't end in the middle of a sentence. If the output is less than one sentence long, does nothing.", + "example": false + }, + "max_context_length": { + "type": "integer", + "description": "Maximum number of tokens to send to the model.", + "default": 1024, + "minimum": 80, + "maximum": 32000 + }, + "max_length": { + "type": "integer", + "description": "Number of tokens to generate.", + "default": 80, + "minimum": 16, + "maximum": 1024 + }, + "rep_pen": { + "type": "number", + "description": "Base repetition penalty value.", + "minimum": 1, + "maximum": 3 + }, + "rep_pen_range": { + "type": "integer", + "description": "Repetition penalty range.", + "minimum": 0, + "maximum": 4096 + }, + "rep_pen_slope": { + "type": "number", + "description": "Repetition penalty slope.", + "minimum": 0, + "maximum": 10 + }, + "singleline": { + "type": "boolean", + "description": "Output formatting option. When enabled, removes everything after the first line of the output, including the newline.", + "example": false + }, + "temperature": { + "type": "number", + "description": "Temperature value.", + "minimum": 0, + "maximum": 5 + }, + "tfs": { + "type": "number", + "description": "Tail free sampling value.", + "minimum": 0, + "maximum": 1 + }, + "top_a": { + "type": "number", + "description": "Top-a sampling value.", + "minimum": 0, + "maximum": 1 + }, + "top_k": { + "type": "integer", + "description": "Top-k sampling value.", + "minimum": 0, + "maximum": 100 + }, + "top_p": { + "type": "number", + "description": "Top-p sampling value.", + "minimum": 0.001, + "maximum": 1 + }, + "typical": { + "type": "number", + "description": "Typical sampling value.", + "minimum": 0, + "maximum": 1 + }, + "sampler_order": { + "type": "array", + "items": { + "type": "integer", + "description": "Array of integers representing the sampler order to be used." + } + }, + "use_default_badwordsids": { + "type": "boolean", + "description": "When True, uses the default KoboldAI bad word IDs.", + "example": true + }, + "stop_sequence": { + "type": "array", + "items": { + "type": "string", + "description": "An array of string sequences whereby the model will stop generating further tokens. The returned text WILL contain the stop sequence." + } + }, + "min_p": { + "type": "number", + "description": "Min-p sampling value.", + "default": 0, + "minimum": 0, + "maximum": 1 + }, + "smoothing_factor": { + "type": "number", + "description": "Quadratic sampling value.", + "default": 0, + "minimum": 0, + "maximum": 10 + }, + "dynatemp_range": { + "type": "number", + "description": "Dynamic temperature range value.", + "default": 0, + "minimum": 0, + "maximum": 5 + }, + "dynatemp_exponent": { + "type": "number", + "description": "Dynamic temperature exponent value.", + "default": 1, + "minimum": 0, + "maximum": 5 + } + }, + "type": "object" + }, + "RequestStatusKobold": { + "allOf": [ + { + "$ref": "#/definitions/RequestStatusCheck" + }, + { + "properties": { + "generations": { + "type": "array", + "items": { + "$ref": "#/definitions/GenerationKobold" + } + } + }, + "type": "object" + } + ] + }, + "GenerationKobold": { + "allOf": [ + { + "$ref": "#/definitions/Generation" + }, + { + "properties": { + "text": { + "type": "string", + "title": "Generated Text", + "description": "The generated text.", + "minLength": 0 + }, + "seed": { + "type": "integer", + "title": "Generation Seed", + "description": "The seed which generated this text.", + "default": 0 + }, + "gen_metadata": { + "type": "array", + "items": { + "$ref": "#/definitions/GenerationMetadataKobold" + } + } + }, + "type": "object" + } + ] + }, + "GenerationMetadataKobold": { + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "type": "string", + "description": "The relevance of the metadata field", + "example": "censorship", + "enum": [ + "censorship" + ] + }, + "value": { + "type": "string", + "description": "The value of the metadata field", + "example": "csam", + "enum": [ + "csam" + ] + }, + "ref": { + "type": "string", + "description": "Optionally a reference for the metadata (e.g. a lora ID)", + "maxLength": 255 + } + }, + "type": "object" + }, + "PopInputKobold": { + "allOf": [ + { + "$ref": "#/definitions/PopInput" + }, + { + "properties": { + "max_length": { + "type": "integer", + "description": "The maximum amount of tokens this worker can generate.", + "default": 512 + }, + "max_context_length": { + "type": "integer", + "description": "The max amount of context to submit to this AI for sampling.", + "default": 2048 + }, + "softprompts": { + "type": "array", + "items": { + "type": "string", + "description": "The available softprompt files on this worker for the currently running model." + } + } + }, + "type": "object" + } + ] + }, + "GenerationPayload": { + "properties": { + "payload": { + "$ref": "#/definitions/ModelPayload" + }, + "id": { + "type": "string", + "description": "The UUID for this generation." + }, + "skipped": { + "$ref": "#/definitions/NoValidRequestFound" + } + }, + "type": "object" + }, + "ModelPayload": { + "properties": { + "prompt": { + "type": "string", + "description": "The prompt which will be sent to the horde against which to run inference." + }, + "n": { + "type": "integer", + "description": "The amount of images to generate.", + "example": 1 + }, + "seed": { + "type": "string", + "description": "The seed to use to generete this request." + } + }, + "type": "object" + }, + "SubmitInputKobold": { + "allOf": [ + { + "$ref": "#/definitions/SubmitInput" + }, + { + "properties": { + "gen_metadata": { + "type": "array", + "items": { + "$ref": "#/definitions/GenerationMetadataKobold" + } + } + }, + "type": "object" + } + ] + }, + "UserDetails": { + "properties": { + "username": { + "type": "string", + "description": "The user's unique Username. It is a combination of their chosen alias plus their ID." + }, + "id": { + "type": "integer", + "description": "The user unique ID. It is always an integer." + }, + "kudos": { + "type": "number", + "description": "The amount of Kudos this user has. The amount of Kudos determines the priority when requesting image generations." + }, + "evaluating_kudos": { + "type": "number", + "description": "(Privileged) The amount of Evaluating Kudos this untrusted user has from generations and uptime. When this number reaches a prespecified threshold, they automatically become trusted." + }, + "concurrency": { + "type": "integer", + "description": "How many concurrent generations this user may request." + }, + "worker_invited": { + "type": "integer", + "description": "Whether this user has been invited to join a worker to the AI Horde and how many of them. When 0, this user cannot add (new) workers to the horde." + }, + "moderator": { + "type": "boolean", + "description": "This user is a AI Horde moderator.", + "example": false + }, + "kudos_details": { + "$ref": "#/definitions/UserKudosDetails" + }, + "worker_count": { + "type": "integer", + "description": "How many workers this user has created (active or inactive)." + }, + "worker_ids": { + "type": "array", + "items": { + "type": "string", + "description": "Privileged or public when the user has explicitly allows it to be public.", + "example": "00000000-0000-0000-0000-000000000000" + } + }, + "sharedkey_ids": { + "type": "array", + "items": { + "type": "string", + "description": "(Privileged) The list of shared key IDs created by this user.", + "example": "00000000-0000-0000-0000-000000000000" + } + }, + "monthly_kudos": { + "$ref": "#/definitions/MonthlyKudos" + }, + "trusted": { + "type": "boolean", + "description": "This user is a trusted member of the AI Horde.", + "example": false + }, + "flagged": { + "type": "boolean", + "description": "(Privileged) This user has been flagged for suspicious activity.", + "example": false + }, + "vpn": { + "type": "boolean", + "description": "(Privileged) This user has been given the VPN role.", + "example": false + }, + "service": { + "type": "boolean", + "description": "This is a service account used by a horde proxy.", + "example": false + }, + "education": { + "type": "boolean", + "description": "This is an education account used schools and universities.", + "example": false + }, + "special": { + "type": "boolean", + "description": "(Privileged) This user has been given the Special role.", + "example": false + }, + "suspicious": { + "type": "integer", + "description": "(Privileged) How much suspicion this user has accumulated.", + "example": 0 + }, + "pseudonymous": { + "type": "boolean", + "description": "If true, this user has not registered using an oauth service.", + "example": false + }, + "contact": { + "type": "string", + "description": "(Privileged) Contact details for the horde admins to reach the user in case of emergency.", + "example": "email@example.com" + }, + "admin_comment": { + "type": "string", + "description": "(Privileged) Information about this users by the admins", + "example": "User is sus" + }, + "account_age": { + "type": "integer", + "description": "How many seconds since this account was created.", + "example": 60 + }, + "usage": { + "$ref": "#/definitions/UsageDetails" + }, + "contributions": { + "$ref": "#/definitions/ContributionsDetails" + }, + "records": { + "$ref": "#/definitions/UserRecords" + } + }, + "type": "object" + }, + "UserKudosDetails": { + "properties": { + "accumulated": { + "type": "number", + "description": "The ammount of Kudos accumulated or used for generating images.", + "default": 0 + }, + "gifted": { + "type": "number", + "description": "The amount of Kudos this user has given to other users.", + "default": 0 + }, + "donated": { + "type": "number", + "description": "The amount of Kudos this user has donated to public goods accounts like education.", + "default": 0 + }, + "admin": { + "type": "number", + "description": "The amount of Kudos this user has been given by the AI Horde admins.", + "default": 0 + }, + "received": { + "type": "number", + "description": "The amount of Kudos this user has been given by other users.", + "default": 0 + }, + "recurring": { + "type": "number", + "description": "The amount of Kudos this user has received from recurring rewards.", + "default": 0 + }, + "awarded": { + "type": "number", + "description": "The amount of Kudos this user has been awarded from things like rating images.", + "default": 0 + } + }, + "type": "object" + }, + "MonthlyKudos": { + "properties": { + "amount": { + "type": "integer", + "description": "How much recurring Kudos this user receives monthly." + }, + "last_received": { + "type": "string", + "format": "date-time", + "description": "Last date this user received monthly Kudos." + } + }, + "type": "object" + }, + "UsageDetails": { + "properties": { + "megapixelsteps": { + "type": "number", + "description": "How many megapixelsteps this user has requested." + }, + "requests": { + "type": "integer", + "description": "How many images this user has requested." + } + }, + "type": "object" + }, + "ContributionsDetails": { + "properties": { + "megapixelsteps": { + "type": "number", + "description": "How many megapixelsteps this user has generated." + }, + "fulfillments": { + "type": "integer", + "description": "How many images this user has generated." + } + }, + "type": "object" + }, + "UserRecords": { + "properties": { + "usage": { + "$ref": "#/definitions/UserThingRecords" + }, + "contribution": { + "$ref": "#/definitions/UserThingRecords" + }, + "fulfillment": { + "$ref": "#/definitions/UserAmountRecords" + }, + "request": { + "$ref": "#/definitions/UserAmountRecords" + } + }, + "type": "object" + }, + "UserThingRecords": { + "properties": { + "megapixelsteps": { + "type": "number", + "description": "How many megapixelsteps this user has generated or requested.", + "default": 0 + }, + "tokens": { + "type": "integer", + "description": "How many token this user has generated or requested.", + "default": 0 + } + }, + "type": "object" + }, + "UserAmountRecords": { + "properties": { + "image": { + "type": "integer", + "description": "How many images this user has generated or requested.", + "default": 0 + }, + "text": { + "type": "integer", + "description": "How many texts this user has generated or requested.", + "default": 0 + }, + "interrogation": { + "type": "integer", + "description": "How many texts this user has generated or requested.", + "default": 0 + } + }, + "type": "object" + }, + "ModifyUserInput": { + "properties": { + "kudos": { + "type": "number", + "description": "The amount of kudos to modify (can be negative)." + }, + "concurrency": { + "type": "integer", + "description": "The amount of concurrent request this user can have.", + "minimum": 0, + "maximum": 500 + }, + "usage_multiplier": { + "type": "number", + "description": "The amount by which to multiply the users kudos consumption.", + "minimum": 0.1, + "maximum": 10 + }, + "worker_invited": { + "type": "integer", + "description": "Set to the amount of workers this user is allowed to join to the horde when in worker invite-only mode." + }, + "moderator": { + "type": "boolean", + "description": "Set to true to make this user a horde moderator.", + "example": false + }, + "public_workers": { + "type": "boolean", + "description": "Set to true to make this user display their worker IDs.", + "example": false + }, + "monthly_kudos": { + "type": "integer", + "description": "When specified, will start assigning the user monthly kudos, starting now!" + }, + "username": { + "type": "string", + "description": "When specified, will change the username. No profanity allowed!", + "minLength": 3, + "maxLength": 100 + }, + "trusted": { + "type": "boolean", + "description": "When set to true,the user and their servers will not be affected by suspicion.", + "example": false + }, + "flagged": { + "type": "boolean", + "description": "When set to true, the user cannot tranfer kudos and all their workers are put into permanent maintenance.", + "example": false + }, + "customizer": { + "type": "boolean", + "description": "When set to true, the user will be able to serve custom Stable Diffusion models which do not exist in the Official AI Horde Model Reference.", + "example": false + }, + "vpn": { + "type": "boolean", + "description": "When set to true, the user will be able to onboard workers behind a VPN. This should be used as a temporary solution until the user is trusted.", + "example": false + }, + "service": { + "type": "boolean", + "description": "When set to true, the user is considered a service account proxying the requests for other users.", + "example": false + }, + "education": { + "type": "boolean", + "description": "When set to true, the user is considered an education account and some options become more restrictive.", + "example": false + }, + "special": { + "type": "boolean", + "description": "When set to true, The user can send special payloads.", + "example": false + }, + "filtered": { + "type": "boolean", + "description": "When set to true, the replacement filter will always be applied against this user", + "example": false + }, + "reset_suspicion": { + "type": "boolean", + "description": "Set the user's suspicion back to 0." + }, + "contact": { + "type": "string", + "description": "Contact details for the horde admins to reach the user in case of emergency. This is only visible to horde moderators.", + "example": "email@example.com", + "minLength": 5, + "maxLength": 500 + }, + "admin_comment": { + "type": "string", + "description": "Add further information about this user for the other admins.", + "example": "User is sus", + "minLength": 5, + "maxLength": 500 + } + }, + "type": "object" + }, + "ModifyUser": { + "properties": { + "new_kudos": { + "type": "number", + "description": "The new total Kudos this user has after this request." + }, + "concurrency": { + "type": "integer", + "description": "The request concurrency this user has after this request.", + "example": 30 + }, + "usage_multiplier": { + "type": "number", + "description": "Multiplies the amount of kudos lost when generating images.", + "example": 1 + }, + "worker_invited": { + "type": "integer", + "description": "Whether this user has been invited to join a worker to the horde and how many of them. When 0, this user cannot add (new) workers to the horde.", + "example": 1 + }, + "moderator": { + "type": "boolean", + "description": "The user's new moderator status.", + "example": false + }, + "public_workers": { + "type": "boolean", + "description": "The user's new public_workers status.", + "example": false + }, + "username": { + "type": "string", + "description": "The user's new username.", + "example": "username#1" + }, + "monthly_kudos": { + "type": "integer", + "description": "The user's new monthly kudos total.", + "example": 0 + }, + "trusted": { + "type": "boolean", + "description": "The user's new trusted status." + }, + "flagged": { + "type": "boolean", + "description": "The user's new flagged status." + }, + "customizer": { + "type": "boolean", + "description": "The user's new customizer status." + }, + "vpn": { + "type": "boolean", + "description": "The user's new vpn status." + }, + "service": { + "type": "boolean", + "description": "The user's new service status." + }, + "education": { + "type": "boolean", + "description": "The user's new education status." + }, + "special": { + "type": "boolean", + "description": "The user's new special status." + }, + "new_suspicion": { + "type": "integer", + "description": "The user's new suspiciousness rating." + }, + "contact": { + "type": "string", + "description": "The new contact details.", + "example": "email@example.com" + }, + "admin_comment": { + "type": "string", + "description": "The new admin comment.", + "example": "User is sus", + "minLength": 5, + "maxLength": 500 + } + }, + "type": "object" + }, + "SharedKeyInput": { + "properties": { + "kudos": { + "type": "integer", + "description": "The Kudos limit assigned to this key. If -1, then anyone with this key can use an unlimited amount of kudos from this account.", + "default": 5000, + "minimum": -1, + "maximum": 50000000 + }, + "expiry": { + "type": "integer", + "description": "The amount of days after which this key will expire. If -1, this key will not expire.", + "default": -1, + "example": 30, + "minimum": -1 + }, + "name": { + "type": "string", + "description": "A descriptive name for this key.", + "example": "Mutual Aid", + "minLength": 3, + "maxLength": 255 + }, + "max_image_pixels": { + "type": "integer", + "description": "The maximum amount of image pixels this key can generate per job. -1 means unlimited.", + "default": -1, + "minimum": -1, + "maximum": 4194304 + }, + "max_image_steps": { + "type": "integer", + "description": "The maximum amount of image steps this key can use per job. -1 means unlimited.", + "default": -1, + "minimum": -1, + "maximum": 500 + }, + "max_text_tokens": { + "type": "integer", + "description": "The maximum amount of text tokens this key can generate per job. -1 means unlimited.", + "default": -1, + "minimum": -1, + "maximum": 500 + } + }, + "type": "object" + }, + "SharedKeyDetails": { + "properties": { + "id": { + "type": "string", + "description": "The SharedKey ID." + }, + "username": { + "type": "string", + "description": "The owning user's unique Username. It is a combination of their chosen alias plus their ID." + }, + "name": { + "type": "string", + "description": "The Shared Key Name." + }, + "kudos": { + "type": "integer", + "description": "The Kudos limit assigned to this key." + }, + "expiry": { + "type": "string", + "format": "date-time", + "description": "The date at which this API key will expire." + }, + "utilized": { + "type": "integer", + "description": "How much kudos has been utilized via this shared key until now." + }, + "max_image_pixels": { + "type": "integer", + "description": "The maximum amount of image pixels this key can generate per job. -1 means unlimited." + }, + "max_image_steps": { + "type": "integer", + "description": "The maximum amount of image steps this key can use per job. -1 means unlimited." + }, + "max_text_tokens": { + "type": "integer", + "description": "The maximum amount of text tokens this key can generate per job. -1 means unlimited." + } + }, + "type": "object" + }, + "SimpleResponse": { + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string", + "description": "The result of this operation.", + "default": "OK" + } + }, + "type": "object" + }, + "WorkerDetails": { + "allOf": [ + { + "$ref": "#/definitions/WorkerDetailsLite" + }, + { + "required": [ + "bridge_agent" + ], + "properties": { + "requests_fulfilled": { + "type": "integer", + "description": "How many images this worker has generated." + }, + "kudos_rewards": { + "type": "number", + "description": "How many Kudos this worker has been rewarded in total." + }, + "kudos_details": { + "$ref": "#/definitions/WorkerKudosDetails" + }, + "performance": { + "type": "string", + "description": "The average performance of this worker in human readable form." + }, + "threads": { + "type": "integer", + "description": "How many threads this worker is running." + }, + "uptime": { + "type": "integer", + "description": "The amount of seconds this worker has been online for this AI Horde." + }, + "maintenance_mode": { + "type": "boolean", + "description": "When True, this worker will not pick up any new requests.", + "example": false + }, + "paused": { + "type": "boolean", + "description": "(Privileged) When True, this worker not be given any new requests.", + "example": false + }, + "info": { + "type": "string", + "description": "Extra information or comments about this worker provided by its owner.", + "example": "https://dbzer0.com" + }, + "nsfw": { + "type": "boolean", + "description": "Whether this worker can generate NSFW requests or not.", + "default": false + }, + "owner": { + "type": "string", + "description": "Privileged or public if the owner has allowed it. The alias of the owner of this worker.", + "example": "username#1" + }, + "ipaddr": { + "type": "string", + "description": "Privileged. The last known IP this worker has connected from.", + "example": "username#1" + }, + "trusted": { + "type": "boolean", + "description": "The worker is trusted to return valid generations." + }, + "flagged": { + "type": "boolean", + "description": "The worker's owner has been flagged for suspicious activity. This worker will not be given any jobs to process." + }, + "suspicious": { + "type": "integer", + "description": "(Privileged) How much suspicion this worker has accumulated.", + "example": 0 + }, + "uncompleted_jobs": { + "type": "integer", + "description": "How many jobs this worker has left uncompleted after it started them.", + "example": 0 + }, + "models": { + "type": "array", + "items": { + "type": "string", + "description": "Which models this worker if offering." + } + }, + "forms": { + "type": "array", + "items": { + "type": "string", + "description": "Which forms this worker if offering." + } + }, + "team": { + "$ref": "#/definitions/TeamDetailsLite" + }, + "contact": { + "type": "string", + "description": "(Privileged) Contact details for the horde admins to reach the owner of this worker in emergencies.", + "example": "email@example.com", + "minLength": 5, + "maxLength": 500 + }, + "bridge_agent": { + "type": "string", + "description": "The bridge agent name, version and website.", + "default": "unknown:0:unknown", + "example": "AI Horde Worker reGen:4.1.0:https://github.com/Haidra-Org/horde-worker-reGen", + "maxLength": 1000 + }, + "max_pixels": { + "type": "integer", + "description": "The maximum pixels in resolution this worker can generate.", + "example": 262144 + }, + "megapixelsteps_generated": { + "type": "number", + "description": "How many megapixelsteps this worker has generated until now." + }, + "img2img": { + "type": "boolean", + "description": "If True, this worker supports and allows img2img requests." + }, + "painting": { + "type": "boolean", + "description": "If True, this worker supports and allows inpainting requests." + }, + "post-processing": { + "type": "boolean", + "description": "If True, this worker supports and allows post-processing requests." + }, + "lora": { + "type": "boolean", + "description": "If True, this worker supports and allows lora requests." + }, + "max_length": { + "type": "integer", + "description": "The maximum tokens this worker can generate.", + "example": 80 + }, + "max_context_length": { + "type": "integer", + "description": "The maximum tokens this worker can read.", + "example": 80 + }, + "tokens_generated": { + "type": "number", + "description": "How many tokens this worker has generated until now." + } + }, + "type": "object" + } + ] + }, + "WorkerDetailsLite": { + "properties": { + "type": { + "type": "string", + "description": "The Type of worker this is.", + "example": "image", + "enum": [ + "image", + "text", + "interrogation" + ] + }, + "name": { + "type": "string", + "description": "The Name given to this worker." + }, + "id": { + "type": "string", + "description": "The UUID of this worker." + }, + "online": { + "type": "boolean", + "description": "True if the worker has checked-in the past 5 minutes." + } + }, + "type": "object" + }, + "WorkerKudosDetails": { + "properties": { + "generated": { + "type": "number", + "description": "How much Kudos this worker has received for generating images." + }, + "uptime": { + "type": "integer", + "description": "How much Kudos this worker has received for staying online longer." + } + }, + "type": "object" + }, + "TeamDetailsLite": { + "properties": { + "name": { + "type": "string", + "description": "The Name given to this team." + }, + "id": { + "type": "string", + "description": "The UUID of this team." + } + }, + "type": "object" + }, + "ModifyWorkerInput": { + "properties": { + "maintenance": { + "type": "boolean", + "description": "Set to true to put this worker into maintenance." + }, + "maintenance_msg": { + "type": "string", + "description": "if maintenance is True, you can optionally provide a message to be used instead of the default maintenance message, so that the owner is informed." + }, + "paused": { + "type": "boolean", + "description": "(Mods only) Set to true to pause this worker." + }, + "info": { + "type": "string", + "description": "You can optionally provide a server note which will be seen in the server details. No profanity allowed!", + "maxLength": 1000 + }, + "name": { + "type": "string", + "description": "When this is set, it will change the worker's name. No profanity allowed!", + "minLength": 5, + "maxLength": 100 + }, + "team": { + "type": "string", + "description": "The team towards which this worker contributes kudos. It an empty string ('') is passed, it will leave the worker without a team. No profanity allowed!", + "example": "0bed257b-e57c-4327-ac64-40cdfb1ac5e6", + "maxLength": 36 + } + }, + "type": "object" + }, + "ModifyWorker": { + "properties": { + "maintenance": { + "type": "boolean", + "description": "The new state of the 'maintenance' var for this worker. When True, this worker will not pick up any new requests." + }, + "paused": { + "type": "boolean", + "description": "The new state of the 'paused' var for this worker. When True, this worker will not be given any new requests." + }, + "info": { + "type": "string", + "description": "The new state of the 'info' var for this worker." + }, + "name": { + "type": "string", + "description": "The new name for this this worker." + }, + "team": { + "type": "string", + "description": "The new team of this worker.", + "example": "Direct Action" + } + }, + "type": "object" + }, + "DeletedWorker": { + "properties": { + "deleted_id": { + "type": "string", + "description": "The ID of the deleted worker." + }, + "deleted_name": { + "type": "string", + "description": "The Name of the deleted worker." + } + }, + "type": "object" + }, + "KudosTransferred": { + "properties": { + "transferred": { + "type": "number", + "description": "The amount of Kudos tranferred.", + "example": 100 + } + }, + "type": "object" + }, + "KudosAwarded": { + "properties": { + "awarded": { + "type": "number", + "description": "The amount of Kudos awarded.", + "example": 100 + } + }, + "type": "object" + }, + "HordeModes": { + "properties": { + "maintenance_mode": { + "type": "boolean", + "description": "When True, this horde will not accept new requests for image generation, but will finish processing the ones currently in the queue." + }, + "invite_only_mode": { + "type": "boolean", + "description": "When True, this horde will not only accept worker explicitly invited to join." + }, + "raid_mode": { + "type": "boolean", + "description": "When True, this horde will not always provide full information in order to throw off attackers." + } + }, + "type": "object" + }, + "HordePerformance": { + "properties": { + "queued_requests": { + "type": "integer", + "description": "The amount of waiting and processing image requests currently in this horde." + }, + "queued_text_requests": { + "type": "integer", + "description": "The amount of waiting and processing text requests currently in this horde." + }, + "worker_count": { + "type": "integer", + "description": "How many workers are actively processing prompt generations in this horde in the past 5 minutes." + }, + "text_worker_count": { + "type": "integer", + "description": "How many workers are actively processing prompt generations in this horde in the past 5 minutes." + }, + "thread_count": { + "type": "integer", + "description": "How many worker threads are actively processing prompt generations in this {horde_noun} in the past 5 minutes." + }, + "text_thread_count": { + "type": "integer", + "description": "How many worker threads are actively processing prompt generations in this {horde_noun} in the past 5 minutes." + }, + "queued_megapixelsteps": { + "type": "number", + "description": "The amount of megapixelsteps in waiting and processing requests currently in this horde." + }, + "past_minute_megapixelsteps": { + "type": "number", + "description": "How many megapixelsteps this horde generated in the last minute." + }, + "queued_forms": { + "type": "number", + "description": "The amount of image interrogations waiting and processing currently in this horde." + }, + "interrogator_count": { + "type": "integer", + "description": "How many workers are actively processing image interrogations in this {horde_noun} in the past 5 minutes." + }, + "interrogator_thread_count": { + "type": "integer", + "description": "How many worker threads are actively processing image interrogation in this {horde_noun} in the past 5 minutes." + }, + "queued_tokens": { + "type": "number", + "description": "The amount of tokens in waiting and processing requests currently in this horde." + }, + "past_minute_tokens": { + "type": "number", + "description": "How many tokens this horde generated in the last minute." + } + }, + "type": "object" + }, + "ActiveModel": { + "allOf": [ + { + "$ref": "#/definitions/ActiveModelLite" + }, + { + "properties": { + "performance": { + "type": "number", + "description": "The average speed of generation for this model." + }, + "queued": { + "type": "number", + "description": "The amount waiting to be generated by this model." + }, + "jobs": { + "type": "number", + "description": "The job count waiting to be generated by this model." + }, + "eta": { + "type": "integer", + "description": "Estimated time in seconds for this model's queue to be cleared." + }, + "type": { + "type": "string", + "description": "The model type (text or image).", + "example": "image", + "enum": [ + "image", + "text" + ] + } + }, + "type": "object" + } + ] + }, + "ActiveModelLite": { + "properties": { + "name": { + "type": "string", + "description": "The Name of a model available by workers in this horde." + }, + "count": { + "type": "integer", + "description": "How many of workers in this horde are running this model." + } + }, + "type": "object" + }, + "Newspiece": { + "properties": { + "date_published": { + "type": "string", + "description": "The date this newspiece was published." + }, + "newspiece": { + "type": "string", + "description": "The actual piece of news." + }, + "importance": { + "type": "string", + "description": "How critical this piece of news is.", + "example": "Information" + } + }, + "type": "object" + }, + "CreateTeamInput": { + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the team. No profanity allowed!", + "minLength": 3, + "maxLength": 100 + }, + "info": { + "type": "string", + "description": "Extra information or comments about this team.", + "example": "Anarchy is emergent order.", + "minLength": 3, + "maxLength": 1000 + } + }, + "type": "object" + }, + "ModifyTeam": { + "properties": { + "id": { + "type": "string", + "description": "The ID of the team." + }, + "name": { + "type": "string", + "description": "The Name of the team." + }, + "info": { + "type": "string", + "description": "The Info of the team." + } + }, + "type": "object" + }, + "TeamDetails": { + "allOf": [ + { + "$ref": "#/definitions/TeamDetailsLite" + }, + { + "properties": { + "info": { + "type": "string", + "description": "Extra information or comments about this team provided by its owner.", + "example": "Anarchy is emergent order." + }, + "requests_fulfilled": { + "type": "integer", + "description": "How many images this team's workers have generated." + }, + "kudos": { + "type": "number", + "description": "How many Kudos the workers in this team have been rewarded while part of this team." + }, + "uptime": { + "type": "integer", + "description": "The total amount of time workers have stayed online while on this team." + }, + "creator": { + "type": "string", + "description": "The alias of the user which created this team.", + "example": "db0#1" + }, + "worker_count": { + "type": "integer", + "description": "How many workers have been dedicated to this team.", + "example": 10 + }, + "workers": { + "type": "array", + "items": { + "$ref": "#/definitions/WorkerDetailsLite" + } + }, + "models": { + "type": "array", + "items": { + "$ref": "#/definitions/ActiveModelLite" + } + } + }, + "type": "object" + } + ] + }, + "ModifyTeamInput": { + "properties": { + "name": { + "type": "string", + "description": "The name of the team. No profanity allowed!", + "minLength": 3, + "maxLength": 100 + }, + "info": { + "type": "string", + "description": "Extra information or comments about this team.", + "example": "Anarchy is emergent order.", + "minLength": 3, + "maxLength": 1000 + } + }, + "type": "object" + }, + "DeletedTeam": { + "properties": { + "deleted_id": { + "type": "string", + "description": "The ID of the deleted team." + }, + "deleted_name": { + "type": "string", + "description": "The Name of the deleted team." + } + }, + "type": "object" + }, + "AddTimeoutIPInput": { + "required": [ + "hours", + "ipaddr" + ], + "properties": { + "ipaddr": { + "type": "string", + "description": "The IP address or CIDR to add from timeout.", + "example": "127.0.0.1", + "minLength": 7, + "maxLength": 40 + }, + "hours": { + "type": "integer", + "description": "For how many hours to put this IP in timeout.", + "example": 24, + "minimum": 1, + "maximum": 720 + } + }, + "type": "object" + }, + "DeleteTimeoutIPInput": { + "required": [ + "ipaddr" + ], + "properties": { + "ipaddr": { + "type": "string", + "description": "The IP address or CIDR to remove from timeout.", + "example": "127.0.0.1", + "minLength": 7, + "maxLength": 40 + } + }, + "type": "object" + }, + "IPTimeout": { + "required": [ + "ipaddr", + "seconds" + ], + "properties": { + "ipaddr": { + "type": "string", + "description": "The CIDR which is in timeout.", + "example": "127.0.0.1", + "minLength": 7, + "maxLength": 40 + }, + "seconds": { + "type": "integer", + "description": "How many more seconds this IP block is in timeout ", + "example": 1440 + } + }, + "type": "object" + }, + "AddWorkerTimeout": { + "required": [ + "days" + ], + "properties": { + "days": { + "type": "integer", + "description": "For how many days to put this worker's IP in timeout.", + "example": 7, + "minimum": 1, + "maximum": 30 + } + }, + "type": "object" + }, + "ModelInterrogationInputStable": { + "properties": { + "forms": { + "type": "array", + "items": { + "$ref": "#/definitions/ModelInterrogationFormStable" + } + }, + "source_image": { + "type": "string", + "description": "The public URL of the image to interrogate." + }, + "slow_workers": { + "type": "boolean", + "description": "When True, allows slower workers to pick up this request. Disabling this incurs an extra kudos cost.", + "default": true + }, + "webhook": { + "type": "string", + "description": "Provide a URL where the AI Horde will send a POST call after each delivered generation. The request will include the details of the job as well as the request ID.", + "example": "https://haidra.net/00000000-0000-0000-0000-000000000000", + "minLength": 10, + "maxLength": 1024 + } + }, + "type": "object" + }, + "ModelInterrogationFormStable": { + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The type of interrogation this is.", + "example": "caption", + "enum": [ + "caption", + "interrogation", + "nsfw", + "GFPGAN", + "RealESRGAN_x4plus", + "RealESRGAN_x2plus", + "RealESRGAN_x4plus_anime_6B", + "NMKD_Siax", + "4x_AnimeSharp", + "CodeFormers", + "strip_background" + ] + }, + "payload": { + "$ref": "#/definitions/ModelInterrogationFormPayloadStable" + } + }, + "type": "object" + }, + "ModelInterrogationFormPayloadStable": { + "properties": { + "*": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "type": "object" + }, + "RequestInterrogationResponse": { + "properties": { + "id": { + "type": "string", + "description": "The UUID of the request. Use this to retrieve the request status in the future." + }, + "message": { + "type": "string", + "description": "Any extra information from the horde about this request." + } + }, + "type": "object" + }, + "InterrogationStatus": { + "properties": { + "state": { + "type": "string", + "title": "Interrogation State", + "description": "The overall status of this interrogation." + }, + "forms": { + "type": "array", + "items": { + "$ref": "#/definitions/InterrogationFormStatus" + } + } + }, + "type": "object" + }, + "InterrogationFormStatus": { + "properties": { + "form": { + "type": "string", + "description": "The name of this interrogation form." + }, + "state": { + "type": "string", + "title": "Interrogation State", + "description": "The overall status of this interrogation." + }, + "result": { + "$ref": "#/definitions/InterrogationFormResult" + } + }, + "type": "object" + }, + "InterrogationFormResult": { + "properties": { + "*": { + "type": "object", + "additionalProperties": { + "type": "object" + } + } + }, + "type": "object" + }, + "InterrogationPopInput": { + "properties": { + "name": { + "type": "string", + "description": "The Name of the Worker." + }, + "priority_usernames": { + "type": "array", + "items": { + "type": "string", + "description": "Users with priority to use this worker." + } + }, + "forms": { + "type": "array", + "items": { + "type": "string", + "description": "The type of interrogation this worker can fulfil.", + "example": "caption", + "enum": [ + "caption", + "interrogation", + "nsfw", + "GFPGAN", + "RealESRGAN_x4plus", + "RealESRGAN_x2plus", + "RealESRGAN_x4plus_anime_6B", + "NMKD_Siax", + "4x_AnimeSharp", + "CodeFormers", + "strip_background" + ] + } + }, + "amount": { + "type": "integer", + "description": "The amount of forms to pop at the same time.", + "default": 1 + }, + "bridge_agent": { + "type": "string", + "description": "The worker name, version and website.", + "default": "unknown", + "example": "AI Horde Worker reGen:4.1.0:https://github.com/Haidra-Org/horde-worker-reGen", + "maxLength": 1000 + }, + "threads": { + "type": "integer", + "description": "How many threads this worker is running. This is used to accurately the current power available in the horde.", + "default": 1, + "minimum": 1, + "maximum": 100 + }, + "max_tiles": { + "type": "integer", + "description": "The maximum amount of 512x512 tiles this worker can post-process.", + "default": 16, + "minimum": 1, + "maximum": 256 + } + }, + "type": "object" + }, + "InterrogationPopPayload": { + "properties": { + "forms": { + "type": "array", + "items": { + "$ref": "#/definitions/InterrogationPopFormPayload" + } + }, + "skipped": { + "$ref": "#/definitions/NoValidInterrogationsFound" + } + }, + "type": "object" + }, + "InterrogationPopFormPayload": { + "properties": { + "id": { + "type": "string", + "description": "The UUID of the interrogation form. Use this to post the results in the future." + }, + "form": { + "type": "string", + "description": "The name of this interrogation form", + "example": "caption", + "enum": [ + "caption", + "interrogation", + "nsfw.", + "GFPGAN", + "RealESRGAN_x4plus", + "RealESRGAN_x2plus", + "RealESRGAN_x4plus_anime_6B", + "NMKD_Siax", + "4x_AnimeSharp", + "CodeFormers", + "strip_background" + ] + }, + "payload": { + "$ref": "#/definitions/ModelInterrogationFormPayloadStable" + }, + "source_image": { + "type": "string", + "description": "The URL From which the source image can be downloaded." + }, + "r2_upload": { + "type": "string", + "description": "The URL in which the post-processed image can be uploaded." + } + }, + "type": "object" + }, + "NoValidInterrogationsFound": { + "properties": { + "worker_id": { + "type": "integer", + "description": "How many waiting requests were skipped because they demanded a specific worker.", + "minimum": 0 + }, + "untrusted": { + "type": "integer", + "description": "How many waiting requests were skipped because they demanded a trusted worker which this worker is not.", + "minimum": 0 + }, + "bridge_version": { + "type": "integer", + "description": "How many waiting requests were skipped because they require a higher version of the bridge than this worker is running (upgrade if you see this in your skipped list).", + "example": 0, + "minimum": 0 + } + }, + "type": "object" + }, + "PutNewFilter": { + "required": [ + "filter_type", + "regex" + ], + "properties": { + "regex": { + "type": "string", + "description": "The regex for this filter.", + "example": "ac.*" + }, + "filter_type": { + "type": "integer", + "description": "The integer defining this filter type.", + "example": 10, + "minimum": 10, + "maximum": 29 + }, + "description": { + "type": "string", + "description": "Description about this regex." + }, + "replacement": { + "type": "string", + "description": "The replacement string for this regex.", + "default": "" + } + }, + "type": "object" + }, + "FilterPromptSuspicion": { + "required": [ + "suspicion" + ], + "properties": { + "suspicion": { + "type": "string", + "description": "Rates how suspicious the provided prompt is. A suspicion over 2 means it would be blocked.", + "default": 0 + }, + "matches": { + "type": "array", + "items": { + "type": "string", + "description": "Which words in the prompt matched the filters." + } + } + }, + "type": "object" + }, + "FilterDetails": { + "required": [ + "filter_type", + "id", + "regex", + "user" + ], + "properties": { + "id": { + "type": "string", + "description": "The UUID of this filter." + }, + "regex": { + "type": "string", + "description": "The regex for this filter.", + "example": "ac.*" + }, + "filter_type": { + "type": "integer", + "description": "The integer defining this filter type.", + "example": 10, + "minimum": 10, + "maximum": 29 + }, + "description": { + "type": "string", + "description": "Description about this regex." + }, + "replacement": { + "type": "string", + "description": "The replacement string for this regex.", + "default": "" + }, + "user": { + "type": "string", + "description": "The moderator which added or last updated this regex." + } + }, + "type": "object" + }, + "FilterRegex": { + "required": [ + "filter_type", + "regex" + ], + "properties": { + "filter_type": { + "type": "integer", + "description": "The integer defining this filter type.", + "example": 10, + "minimum": 10, + "maximum": 29 + }, + "regex": { + "type": "string", + "description": "The full regex for this filter type." + } + }, + "type": "object" + }, + "PatchExistingFilter": { + "properties": { + "regex": { + "type": "string", + "description": "The regex for this filter.", + "example": "ac.*" + }, + "filter_type": { + "type": "integer", + "description": "The integer defining this filter type.", + "example": 10, + "minimum": 10, + "maximum": 29 + }, + "description": { + "type": "string", + "description": "Description about this regex." + }, + "replacement": { + "type": "string", + "description": "The replacement string for this regex.", + "default": "" + } + }, + "type": "object" + }, + "StatsImgTotals": { + "properties": { + "minute": { + "$ref": "#/definitions/SinglePeriodImgStat" + }, + "hour": { + "$ref": "#/definitions/SinglePeriodImgStat" + }, + "day": { + "$ref": "#/definitions/SinglePeriodImgStat" + }, + "month": { + "$ref": "#/definitions/SinglePeriodImgStat" + }, + "total": { + "$ref": "#/definitions/SinglePeriodImgStat" + } + }, + "type": "object" + }, + "SinglePeriodImgStat": { + "properties": { + "images": { + "type": "integer", + "description": "The amount of images generated during this period." + }, + "ps": { + "type": "integer", + "description": "The amount of pixelsteps generated during this period." + } + }, + "type": "object" + }, + "ImgModelStats": { + "properties": { + "day": { + "$ref": "#/definitions/SinglePeriodImgModelStats" + }, + "month": { + "$ref": "#/definitions/SinglePeriodImgModelStats" + }, + "total": { + "$ref": "#/definitions/SinglePeriodImgModelStats" + } + }, + "type": "object" + }, + "SinglePeriodImgModelStats": { + "properties": { + "*": { + "type": "object", + "additionalProperties": { + "type": "integer", + "description": "The amount of requests fulfilled for this model." + } + } + }, + "type": "object" + }, + "StatsTxtTotals": { + "properties": { + "minute": { + "$ref": "#/definitions/SinglePeriodImgStat" + }, + "hour": { + "$ref": "#/definitions/SinglePeriodImgStat" + }, + "day": { + "$ref": "#/definitions/SinglePeriodImgStat" + }, + "month": { + "$ref": "#/definitions/SinglePeriodImgStat" + }, + "total": { + "$ref": "#/definitions/SinglePeriodImgStat" + } + }, + "type": "object" + }, + "TxtModelStats": { + "properties": { + "day": { + "$ref": "#/definitions/SinglePeriodTxtModelStats" + }, + "month": { + "$ref": "#/definitions/SinglePeriodTxtModelStats" + }, + "total": { + "$ref": "#/definitions/SinglePeriodTxtModelStats" + } + }, + "type": "object" + }, + "SinglePeriodTxtModelStats": { + "properties": { + "*": { + "type": "object", + "additionalProperties": { + "type": "integer", + "description": "The amount of requests fulfilled for this model." + } + } + }, + "type": "object" + } + }, + "responses": { + "ParseError": { + "description": "When a mask can't be parsed" + }, + "MaskError": { + "description": "When any error occurs on mask" + } + } + } diff --git a/codegen/swagger_openapi3.json b/codegen/ai_horde/swagger_openapi3.json similarity index 84% rename from codegen/swagger_openapi3.json rename to codegen/ai_horde/swagger_openapi3.json index bcd6227..341f3b5 100644 --- a/codegen/swagger_openapi3.json +++ b/codegen/ai_horde/swagger_openapi3.json @@ -760,7 +760,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/RequestError" + "$ref": "#/components/schemas/RequestValidationError" } } }, @@ -800,7 +800,7 @@ "tags": [ "v2" ], - "description": "This endpoint will immediately return with the UUID of the request for generation.\nThis endpoint will always be accepted, even if there are no workers available currently to fulfill this request. \nPerhaps some will appear in the next 10 minutes.\nAsynchronous requests live for 10 minutes before being considered stale and being deleted.", + "description": "This endpoint will immediately return with the UUID of the request for generation.\nThis endpoint will always be accepted, even if there are no workers available currently to fulfill this request.\nPerhaps some will appear in the next 10 minutes.\nAsynchronous requests live for 10 minutes before being considered stale and being deleted.", "operationId": "post_image_async_generate", "requestBody": { "content": { @@ -1036,7 +1036,7 @@ "tags": [ "v2" ], - "description": "The request has to have been sent as shared: true.\nYou can select the best image in the set, and/or provide a rating for each or some images in the set.\nIf you select best-of image, you will gain 4 kudos. Each rating is 5 kudos. Best-of will be ignored when ratings conflict with it.\nYou can never gain more kudos than you spent for this generation. Your reward at max will be your kudos consumption - 1.", + "description": "AI\nThe request has to have been sent as shared: true.\nYou can select the best image in the set, and/or provide a rating for each or some images in the set.\nIf you select best-of image, you will gain 4 kudos. Each rating is 5 kudos. Best-of will be ignored when ratings conflict with it.\nYou can never gain more kudos than you spent for this generation. Your reward at max will be your kudos consumption - 1.", "operationId": "post_aesthetics", "requestBody": { "content": { @@ -1048,7 +1048,7 @@ }, "required": true }, - "summary": "Submit aesthetic ratings for generated images to be used by LAION" + "summary": "Submit aesthetic ratings for generated images to be used by LAION and Stability" } }, "/v2/generate/status/{id}": { @@ -1301,7 +1301,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/RequestError" + "$ref": "#/components/schemas/RequestValidationError" } } }, @@ -1341,7 +1341,7 @@ "tags": [ "v2" ], - "description": "This endpoint will immediately return with the UUID of the request for generation.\nThis endpoint will always be accepted, even if there are no workers available currently to fulfill this request. \nPerhaps some will appear in the next 20 minutes.\nAsynchronous requests live for 20 minutes before being considered stale and being deleted.", + "description": "This endpoint will immediately return with the UUID of the request for generation.\nThis endpoint will always be accepted, even if there are no workers available currently to fulfill this request.\nPerhaps some will appear in the next 20 minutes.\nAsynchronous requests live for 20 minutes before being considered stale and being deleted.", "operationId": "post_text_async_generate", "requestBody": { "content": { @@ -1633,7 +1633,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SubmitInput" + "$ref": "#/components/schemas/SubmitInputKobold" } } }, @@ -1728,7 +1728,7 @@ "tags": [ "v2" ], - "description": "This endpoint will immediately return with the UUID of the request for interrogation.\nThis endpoint will always be accepted, even if there are no workers available currently to fulfill this request. \nPerhaps some will appear in the next 20 minutes.\nAsynchronous requests live for 20 minutes before being considered stale and being deleted.", + "description": "This endpoint will immediately return with the UUID of the request for interrogation.\nThis endpoint will always be accepted, even if there are no workers available currently to fulfill this request.\nPerhaps some will appear in the next 20 minutes.\nAsynchronous requests live for 20 minutes before being considered stale and being deleted.", "operationId": "post_interrogate", "requestBody": { "content": { @@ -2115,56 +2115,434 @@ "summary": "Awards Kudos to registed user" } }, - "/v2/kudos/kai/{user_id}": { + "/v2/kudos/transfer": { + "post": { + "parameters": [ + { + "description": "The sending user's API key.", + "in": "header", + "name": "apikey", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "The client name and version.", + "in": "header", + "name": "Client-Agent", + "schema": { + "default": "unknown:0:unknown", + "type": "string" + } + }, + { + "description": "An optional fields mask", + "in": "header", + "name": "X-Fields", + "schema": { + "format": "mask", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KudosTransferred" + } + } + }, + "description": "Kudos Transferred" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RequestError" + } + } + }, + "description": "Validation Error" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RequestError" + } + } + }, + "description": "Invalid API Key" + } + }, + "tags": [ + "v2" + ], + "operationId": "post_transfer_kudos", + "requestBody": { + "$ref": "#/components/requestBodies/post_award_kudosPayload" + }, + "summary": "Transfer Kudos to another registed user" + } + }, + "/v2/operations/block_worker_ipaddr/{worker_id}": { "parameters": [ { "in": "path", - "name": "user_id", + "name": "worker_id", "required": true, "schema": { "type": "string" } } ], - "post": { + "delete": { + "parameters": [ + { + "description": "A mod API key.", + "in": "header", + "name": "apikey", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "The client name and version.", + "in": "header", + "name": "Client-Agent", + "schema": { + "default": "unknown:0:unknown", + "type": "string" + } + }, + { + "description": "An optional fields mask", + "in": "header", + "name": "X-Fields", + "schema": { + "format": "mask", + "type": "string" + } + } + ], "responses": { "200": { - "description": "Success" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimpleResponse" + } + } + }, + "description": "Operation Completed" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RequestError" + } + } + }, + "description": "Validation Error" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RequestError" + } + } + }, + "description": "Invalid API Key" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RequestError" + } + } + }, + "description": "Access Denied" + } + }, + "tags": [ + "v2" + ], + "description": "Only usable by horde moderators", + "operationId": "delete_operations_block_worker_ip", + "summary": "Remove a worker's IP block" + }, + "put": { + "parameters": [ + { + "description": "A mod API key.", + "in": "header", + "name": "apikey", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "The client name and version.", + "in": "header", + "name": "Client-Agent", + "schema": { + "default": "unknown:0:unknown", + "type": "string" + } + }, + { + "description": "An optional fields mask", + "in": "header", + "name": "X-Fields", + "schema": { + "format": "mask", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimpleResponse" + } + } + }, + "description": "Operation Completed" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RequestError" + } + } + }, + "description": "Validation Error" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RequestError" + } + } + }, + "description": "Invalid API Key" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RequestError" + } + } + }, + "description": "Access Denied" + } + }, + "tags": [ + "v2" + ], + "description": "Only usable by horde moderators", + "operationId": "put_operations_block_worker_ip", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddWorkerTimeout" + } + } + }, + "required": true + }, + "summary": "Block worker's from a specific IP for 24 hours" + } + }, + "/v2/operations/ipaddr": { + "delete": { + "parameters": [ + { + "description": "A mod API key.", + "in": "header", + "name": "apikey", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "The client name and version.", + "in": "header", + "name": "Client-Agent", + "schema": { + "default": "unknown:0:unknown", + "type": "string" + } + }, + { + "description": "An optional fields mask", + "in": "header", + "name": "X-Fields", + "schema": { + "format": "mask", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimpleResponse" + } + } + }, + "description": "Operation Completed" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RequestError" + } + } + }, + "description": "Validation Error" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RequestError" + } + } + }, + "description": "Invalid API Key" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RequestError" + } + } + }, + "description": "Access Denied" + } + }, + "tags": [ + "v2" + ], + "description": "Only usable by horde moderators", + "operationId": "delete_operations_ip", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeleteTimeoutIPInput" + } + } + }, + "required": true + }, + "summary": "Remove an IP from timeout" + }, + "get": { + "parameters": [ + { + "description": "A mod API key.", + "in": "header", + "name": "apikey", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "The client name and version.", + "in": "header", + "name": "Client-Agent", + "schema": { + "default": "unknown:0:unknown", + "type": "string" + } + }, + { + "description": "An optional fields mask", + "in": "header", + "name": "X-Fields", + "schema": { + "format": "mask", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/IPTimeout" + }, + "type": "array" + } + } + }, + "description": "An IP timeout entry" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RequestError" + } + } + }, + "description": "Validation Error" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RequestError" + } + } + }, + "description": "Invalid API Key" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RequestError" + } + } + }, + "description": "Access Denied" } }, "tags": [ "v2" ], - "operationId": "post_kobold_kudos_transfer", - "requestBody": { - "content": { - "application/json": { - "schema": { - "properties": { - "kai_id": { - "type": "integer" - }, - "kudos_amount": { - "type": "integer" - }, - "trusted": { - "type": "boolean" - } - }, - "type": "object" - } - } - }, - "required": true - }, - "summary": "Receives kudos from the KoboldAI Horde" - } - }, - "/v2/kudos/transfer": { + "operationId": "get_operations_ip", + "summary": "Return all existing IP Block timeouts" + }, "post": { "parameters": [ { - "description": "The sending user's API key.", + "description": "A mod API key.", "in": "header", "name": "apikey", "required": true, @@ -2196,11 +2574,11 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/KudosTransferred" + "$ref": "#/components/schemas/SimpleResponse" } } }, - "description": "Kudos Transferred" + "description": "Operation Completed" }, "400": { "content": { @@ -2221,20 +2599,48 @@ } }, "description": "Invalid API Key" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RequestError" + } + } + }, + "description": "Access Denied" } }, "tags": [ "v2" ], - "operationId": "post_transfer_kudos", + "description": "Only usable by horde moderators", + "operationId": "post_operations_ip", "requestBody": { - "$ref": "#/components/requestBodies/post_award_kudosPayload" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddTimeoutIPInput" + } + } + }, + "required": true }, - "summary": "Transfer Kudos to another registed user" + "summary": "Add an IP or CIDR to timeout" } }, - "/v2/operations/ipaddr": { - "delete": { + "/v2/operations/ipaddr/{ipaddr}": { + "parameters": [ + { + "in": "path", + "name": "ipaddr", + "required": true, + "schema": { + "type": "string" + } + } + ], + "get": { "parameters": [ { "description": "A mod API key.", @@ -2269,11 +2675,14 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SimpleResponse" + "items": { + "$ref": "#/components/schemas/IPTimeout" + }, + "type": "array" } } }, - "description": "Operation Completed" + "description": "IP timeout entries that match IP" }, "400": { "content": { @@ -2309,19 +2718,8 @@ "tags": [ "v2" ], - "description": "Only usable by horde moderators", - "operationId": "delete_operations_ip", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DeleteTimeoutIPInput" - } - } - }, - "required": true - }, - "summary": "Remove an IP from timeout" + "operationId": "get_operations_ip_single", + "summary": "Check if an IP or CIDR is in timeout" } }, "/v2/sharedkeys": { @@ -2543,7 +2941,7 @@ "v2" ], "operationId": "get_shared_key_single", - "summary": "Get details about an existing Shared Key for this user" + "summary": "Get details about an existing Shared Key" }, "patch": { "parameters": [ @@ -2656,6 +3054,15 @@ "type": "string" } }, + { + "description": "If 'known', only show stats for known models in the model reference. If 'custom' only show stats for custom models. If 'all' shows stats for all models.", + "in": "query", + "name": "model_state", + "schema": { + "default": "known", + "type": "string" + } + }, { "description": "An optional fields mask", "in": "header", @@ -2675,7 +3082,17 @@ } } }, - "description": "Horde generated images statistics per model" + "description": "AI Horde generated images statistics per model" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RequestError" + } + } + }, + "description": "Validation Error" } }, "tags": [ @@ -2716,7 +3133,7 @@ } } }, - "description": "Horde generated images statistics" + "description": "AI Horde generated images statistics" } }, "tags": [ @@ -2758,7 +3175,7 @@ } } }, - "description": "Horde generated text statistics per model" + "description": "AI Horde generated text statistics per model" } }, "tags": [ @@ -2799,7 +3216,7 @@ } } }, - "description": "Horde generated text statistics" + "description": "AI Horde generated text statistics" } }, "tags": [ @@ -2872,6 +3289,15 @@ "type": "integer" } }, + { + "description": "If 'known', only show stats for known models in the model reference. If 'custom' only show stats for custom models. If 'all' shows stats for all models.", + "in": "query", + "name": "model_state", + "schema": { + "default": "all", + "type": "string" + } + }, { "description": "An optional fields mask", "in": "header", @@ -2895,6 +3321,16 @@ } }, "description": "List All Active Models" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RequestError" + } + } + }, + "description": "Validation Error" } }, "tags": [ @@ -2985,7 +3421,7 @@ } } }, - "description": "Horde Maintenance" + "description": "AI Horde Maintenance" } }, "tags": [ @@ -3111,7 +3547,7 @@ } } }, - "description": "Horde News" + "description": "AI Horde News" } }, "tags": [ @@ -3152,7 +3588,7 @@ } } }, - "description": "Horde Performance" + "description": "AI Horde Performance" } }, "tags": [ @@ -4107,7 +4543,6 @@ "tags": [ "v2" ], - "description": "Maintenance can be set by the owner of the serve or an admin. \nWhen in maintenance, the worker will receive a 503 request when trying to retrieve new requests. Use this to avoid disconnecting your worker in the middle of a generation\nPaused can be set only by the admins of this Horde.\nWhen in paused mode, the worker will not be given any requests to generate.", "operationId": "put_worker_single", "requestBody": { "content": { @@ -4118,8 +4553,7 @@ } }, "required": true - }, - "summary": "Put the worker into maintenance or pause mode" + } } } }, @@ -4190,7 +4624,7 @@ "description": "The model type (text or image).", "enum": [ "image", - "tex.t" + "text" ], "example": "image", "type": "string" @@ -4213,6 +4647,44 @@ }, "type": "object" }, + "AddTimeoutIPInput": { + "properties": { + "hours": { + "description": "For how many hours to put this IP in timeout.", + "example": 24, + "maximum": 720, + "minimum": 1, + "type": "integer" + }, + "ipaddr": { + "description": "The IP address or CIDR to add from timeout.", + "example": "127.0.0.1", + "maxLength": 40, + "minLength": 7, + "type": "string" + } + }, + "required": [ + "hours", + "ipaddr" + ], + "type": "object" + }, + "AddWorkerTimeout": { + "properties": { + "days": { + "description": "For how many days to put this worker's IP in timeout.", + "example": 7, + "maximum": 30, + "minimum": 1, + "type": "integer" + } + }, + "required": [ + "days" + ], + "type": "object" + }, "AestheticRating": { "properties": { "artifacts": { @@ -4297,9 +4769,9 @@ "DeleteTimeoutIPInput": { "properties": { "ipaddr": { - "description": "The IP address to remove from timeout.", + "description": "The IP address or CIDR to remove from timeout.", "example": "127.0.0.1", - "maxLength": 15, + "maxLength": 40, "minLength": 7, "type": "string" } @@ -4335,6 +4807,20 @@ }, "type": "object" }, + "ExtraSourceImage": { + "properties": { + "image": { + "description": "The Base64-encoded webp to use for further processing.", + "type": "string" + }, + "strength": { + "default": 1, + "description": "Optional field, determining the strength to use for the processing", + "type": "number" + } + }, + "type": "object" + }, "FilterDetails": { "properties": { "description": { @@ -4424,7 +4910,7 @@ }, "state": { "default": "ok", - "description": "The state of this generation.", + "description": "OBSOLETE (Use the gen_metadata field). The state of this generation.", "enum": [ "ok", "censored" @@ -4451,11 +4937,27 @@ }, "GenerationInputKobold": { "properties": { + "allow_downgrade": { + "default": false, + "description": "When true and the request requires upfront kudos and the account does not have enough The request will be downgraded in max context and max tokens so that it does not need upfront kudos.", + "type": "boolean" + }, + "disable_batching": { + "default": false, + "description": "When true, This request will not use batching. This will allow you to retrieve accurate seeds. Feature is restricted to Trusted users and Patreons.", + "type": "boolean" + }, "dry_run": { "default": false, - "description": "When false, the endpoint will simply return the cost of the request in kudos and exit.", + "description": "When true, the endpoint will simply return the cost of the request in kudos and exit.", "type": "boolean" }, + "extra_source_images": { + "items": { + "$ref": "#/components/schemas/ExtraSourceImage" + }, + "type": "array" + }, "models": { "items": { "description": "Specify which models are allowed to be used for this request.", @@ -4470,6 +4972,10 @@ "description": "The prompt which will be sent to KoboldAI to generate text.", "type": "string" }, + "proxied_account": { + "description": "If using a service account as a proxy, provide this value to identify the actual account from which this request is coming from.", + "type": "string" + }, "slow_workers": { "default": true, "description": "When True, allows slower workers to pick up this request. Disabling this incurs an extra kudos cost.", @@ -4485,6 +4991,10 @@ "description": "When true, only trusted workers will serve this request. When False, Evaluating workers will also be used which can increase speed but adds more risk!", "type": "boolean" }, + "webhook": { + "description": "Provide a URL where the AI Horde will send a POST call after each delivered generation. The request will include the details of the job as well as the request ID.", + "type": "string" + }, "worker_blacklist": { "default": false, "description": "If true, the worker list will be treated as a blacklist instead of a whitelist.", @@ -4502,16 +5012,32 @@ }, "GenerationInputStable": { "properties": { + "allow_downgrade": { + "default": false, + "description": "When true and the request requires upfront kudos and the account does not have enough The request will be downgraded in steps and resolution so that it does not need upfront kudos.", + "type": "boolean" + }, "censor_nsfw": { "default": false, "description": "If the request is SFW, and the worker accidentally generates NSFW, it will send back a censored image.", "type": "boolean" }, + "disable_batching": { + "default": false, + "description": "When true, This request will not use batching. This will allow you to retrieve accurate seeds. Feature is restricted to Trusted users and Patreons.", + "type": "boolean" + }, "dry_run": { "default": false, - "description": "When false, the endpoint will simply return the cost of the request in kudos and exit.", + "description": "When true, the endpoint will simply return the cost of the request in kudos and exit.", "type": "boolean" }, + "extra_source_images": { + "items": { + "$ref": "#/components/schemas/ExtraSourceImage" + }, + "type": "array" + }, "models": { "items": { "description": "Specify which models are allowed to be used for this request.", @@ -4532,6 +5058,10 @@ "minLength": 1, "type": "string" }, + "proxied_account": { + "description": "If using a service account as a proxy, provide this value to identify the actual account from which this request is coming from.", + "type": "string" + }, "r2": { "default": true, "description": "If True, the image will be sent via cloudflare r2 download link.", @@ -4566,7 +5096,8 @@ "enum": [ "img2img", "inpainting", - "outpainting" + "outpainting", + "remix" ], "example": "img2img", "type": "string" @@ -4576,6 +5107,13 @@ "description": "When true, only trusted workers will serve this request. When False, Evaluating workers will also be used which can increase speed but adds more risk!", "type": "boolean" }, + "webhook": { + "description": "Provide a URL where the AI Horde will send a POST call after each delivered generation. The request will include the details of the job as well as the request ID.", + "example": "https://haidra.net/00000000-0000-0000-0000-000000000000", + "maxLength": 1024, + "minLength": 10, + "type": "string" + }, "worker_blacklist": { "default": false, "description": "If true, the worker list will be treated as a blacklist instead of a whitelist.", @@ -4601,6 +5139,12 @@ }, { "properties": { + "gen_metadata": { + "items": { + "$ref": "#/components/schemas/GenerationMetadataKobold" + }, + "type": "array" + }, "seed": { "default": 0, "description": "The seed which generated this text.", @@ -4618,35 +5162,112 @@ } ] }, - "GenerationPayload": { + "GenerationMetadataKobold": { "properties": { - "id": { - "description": "The UUID for this text generation.", + "ref": { + "description": "Optionally a reference for the metadata (e.g. a lora ID)", + "maxLength": 255, "type": "string" }, - "model": { - "description": "Which of the available models to use for this request.", + "type": { + "description": "The relevance of the metadata field", + "enum": [ + "censorship" + ], + "example": "censorship", "type": "string" }, - "payload": { - "$ref": "#/components/schemas/ModelPayloadKobold" + "value": { + "description": "The value of the metadata field", + "enum": [ + "csam" + ], + "example": "csam", + "type": "string" + } + }, + "required": [ + "type", + "value" + ], + "type": "object" + }, + "GenerationMetadataStable": { + "properties": { + "ref": { + "description": "Optionally a reference for the metadata (e.g. a lora ID)", + "maxLength": 255, + "type": "string" }, - "skipped": { - "$ref": "#/components/schemas/NoValidRequestFoundKobold" + "type": { + "description": "The relevance of the metadata field", + "enum": [ + "lora", + "ti", + "censorship", + "source_image", + "source_mask", + "extra_source_images", + "batch_index" + ], + "example": "lora", + "type": "string" }, - "softprompt": { - "description": "The soft prompt requested for this generation.", + "value": { + "description": "The value of the metadata field", + "enum": [ + "download_failed", + "parse_failed", + "baseline_mismatch", + "csam", + "nsfw", + "see_ref" + ], + "example": "download_failed", + "type": "string" + } + }, + "required": [ + "type", + "value" + ], + "type": "object" + }, + "GenerationPayload": { + "properties": { + "id": { + "description": "The UUID for this generation.", "type": "string" + }, + "payload": { + "$ref": "#/components/schemas/ModelPayload" + }, + "skipped": { + "$ref": "#/components/schemas/NoValidRequestFound" } }, "type": "object" }, "GenerationPayloadStable": { "properties": { + "extra_source_images": { + "items": { + "$ref": "#/components/schemas/ExtraSourceImage" + }, + "type": "array" + }, "id": { "description": "The UUID for this image generation.", "type": "string" }, + "ids": { + "items": { + "description": "The UUID for this image generation.", + "example": "00000000-0000-0000-0000-000000000000", + "type": "string" + }, + "type": "array" + }, "model": { "description": "Which of the available models to use for this request.", "type": "string" @@ -4658,6 +5279,13 @@ "description": "The r2 upload link to use to upload this image.", "type": "string" }, + "r2_uploads": { + "items": { + "description": "The r2 upload link to use to upload this image.", + "type": "string" + }, + "type": "array" + }, "skipped": { "$ref": "#/components/schemas/NoValidRequestFoundStable" }, @@ -4675,7 +5303,8 @@ "enum": [ "img2img", "inpainting", - "outpainting" + "outpainting", + "remix" ], "example": "img2img", "type": "string" @@ -4694,6 +5323,12 @@ "description": "When true this image has been censored by the worker's safety filter.", "type": "boolean" }, + "gen_metadata": { + "items": { + "$ref": "#/components/schemas/GenerationMetadataStable" + }, + "type": "array" + }, "id": { "description": "The ID for this image.", "title": "Generation ID", @@ -4727,15 +5362,15 @@ "HordeModes": { "properties": { "invite_only_mode": { - "description": "When True, this Horde will not only accept worker explicitly invited to join.", + "description": "When True, this horde will not only accept worker explicitly invited to join.", "type": "boolean" }, "maintenance_mode": { - "description": "When True, this Horde will not accept new requests for image generation, but will finish processing the ones currently in the queue.", + "description": "When True, this horde will not accept new requests for image generation, but will finish processing the ones currently in the queue.", "type": "boolean" }, "raid_mode": { - "description": "When True, this Horde will not always provide full information in order to throw off attackers.", + "description": "When True, this horde will not always provide full information in order to throw off attackers.", "type": "boolean" } }, @@ -4744,58 +5379,79 @@ "HordePerformance": { "properties": { "interrogator_count": { - "description": "How many workers are actively processing image interrogations in this Horde in the past 5 minutes.", + "description": "How many workers are actively processing image interrogations in this {horde_noun} in the past 5 minutes.", "type": "integer" }, "interrogator_thread_count": { - "description": "How many worker threads are actively processing image interrogation in this Horde in the past 5 minutes.", + "description": "How many worker threads are actively processing image interrogation in this {horde_noun} in the past 5 minutes.", "type": "integer" }, "past_minute_megapixelsteps": { - "description": "How many megapixelsteps this Horde generated in the last minute.", + "description": "How many megapixelsteps this horde generated in the last minute.", "type": "number" }, "past_minute_tokens": { - "description": "How many tokens this Horde generated in the last minute.", + "description": "How many tokens this horde generated in the last minute.", "type": "number" }, "queued_forms": { - "description": "The amount of image interrogations waiting and processing currently in this Horde.", + "description": "The amount of image interrogations waiting and processing currently in this horde.", "type": "number" }, "queued_megapixelsteps": { - "description": "The amount of megapixelsteps in waiting and processing requests currently in this Horde.", + "description": "The amount of megapixelsteps in waiting and processing requests currently in this horde.", "type": "number" }, "queued_requests": { - "description": "The amount of waiting and processing image requests currently in this Horde.", + "description": "The amount of waiting and processing image requests currently in this horde.", "type": "integer" }, "queued_text_requests": { - "description": "The amount of waiting and processing text requests currently in this Horde.", + "description": "The amount of waiting and processing text requests currently in this horde.", "type": "integer" }, "queued_tokens": { - "description": "The amount of tokens in waiting and processing requests currently in this Horde.", + "description": "The amount of tokens in waiting and processing requests currently in this horde.", "type": "number" }, "text_thread_count": { - "description": "How many worker threads are actively processing prompt generations in this Horde in the past 5 minutes.", + "description": "How many worker threads are actively processing prompt generations in this {horde_noun} in the past 5 minutes.", "type": "integer" }, "text_worker_count": { - "description": "How many workers are actively processing prompt generations in this Horde in the past 5 minutes.", + "description": "How many workers are actively processing prompt generations in this horde in the past 5 minutes.", "type": "integer" }, "thread_count": { - "description": "How many worker threads are actively processing prompt generations in this Horde in the past 5 minutes.", + "description": "How many worker threads are actively processing prompt generations in this {horde_noun} in the past 5 minutes.", "type": "integer" }, "worker_count": { - "description": "How many workers are actively processing prompt generations in this Horde in the past 5 minutes.", + "description": "How many workers are actively processing prompt generations in this horde in the past 5 minutes.", + "type": "integer" + } + }, + "type": "object" + }, + "IPTimeout": { + "properties": { + "ipaddr": { + "description": "The CIDR which is in timeout.", + "example": "127.0.0.1", + "maxLength": 40, + "minLength": 7, + "type": "string" + }, + "seconds": { + "description": "How many more seconds this IP block is in timeout ", + "example": 1440, "type": "integer" } }, + "required": [ + "ipaddr", + "seconds" + ], "type": "object" }, "ImgModelStats": { @@ -4888,15 +5544,10 @@ "bridge_agent": { "default": "unknown", "description": "The worker name, version and website.", - "example": "AI Horde Worker:11:https://github.com/db0/AI-Horde-Worker", + "example": "AI Horde Worker reGen:4.1.0:https://github.com/Haidra-Org/horde-worker-reGen", "maxLength": 1000, "type": "string" }, - "bridge_version": { - "default": 1, - "description": "The version of the bridge used by this worker.", - "type": "integer" - }, "forms": { "items": { "description": "The type of interrogation this worker can fulfil.", @@ -4981,7 +5632,7 @@ "awarded": { "description": "The amount of Kudos awarded.", "example": 100, - "type": "integer" + "type": "number" } }, "type": "object" @@ -4991,7 +5642,7 @@ "transferred": { "description": "The amount of Kudos tranferred.", "example": 100, - "type": "integer" + "type": "number" } }, "type": "object" @@ -5088,25 +5739,34 @@ "source_image": { "description": "The public URL of the image to interrogate.", "type": "string" + }, + "webhook": { + "description": "Provide a URL where the AI Horde will send a POST call after each delivered generation. The request will include the details of the job as well as the request ID.", + "example": "https://haidra.net/00000000-0000-0000-0000-000000000000", + "maxLength": 1024, + "minLength": 10, + "type": "string" } }, "type": "object" }, - "ModelPayloadKobold": { - "allOf": [ - { - "$ref": "#/components/schemas/ModelPayloadRootKobold" + "ModelPayload": { + "properties": { + "n": { + "description": "The amount of images to generate.", + "example": 1, + "type": "integer" }, - { - "properties": { - "prompt": { - "description": "The prompt which will be sent to KoboldAI to generate the text.", - "type": "string" - } - }, - "type": "object" + "prompt": { + "description": "The prompt which will be sent to the horde against which to run inference.", + "type": "string" + }, + "seed": { + "description": "The seed to use to generete this request.", + "type": "string" } - ] + }, + "type": "object" }, "ModelPayloadLorasStable": { "properties": { @@ -5114,7 +5774,7 @@ "default": 1, "description": "The strength of the LoRa to apply to the clip model.", "maximum": 5, - "minimum": 0, + "minimum": -5, "type": "number" }, "inject_trigger": { @@ -5123,16 +5783,21 @@ "minLength": 1, "type": "string" }, + "is_version": { + "default": false, + "description": "If true, will consider the LoRa ID as a CivitAI version ID and search accordingly. Ensure the name is an integer.", + "type": "boolean" + }, "model": { "default": 1, "description": "The strength of the LoRa to apply to the SD model.", "maximum": 5, - "minimum": 0, + "minimum": -5, "type": "number" }, "name": { - "description": "The exact name of the LoRa.", - "example": "GlowingRunesAIV6", + "description": "The exact name or CivitAI Model Page ID of the LoRa. If is_version is true, this should be the CivitAI version ID.", + "example": "Magnagothica", "maxLength": 255, "minLength": 1, "type": "string" @@ -5145,6 +5810,20 @@ }, "ModelPayloadRootKobold": { "properties": { + "dynatemp_exponent": { + "default": 1, + "description": "Dynamic temperature exponent value.", + "maximum": 5, + "minimum": 0, + "type": "number" + }, + "dynatemp_range": { + "default": 0, + "description": "Dynamic temperature range value.", + "maximum": 5, + "minimum": 0, + "type": "number" + }, "frmtadsnsp": { "description": "Input formatting option. When enabled, adds a leading space to your input if there is no trailing whitespace at the end of the previous action.", "example": false, @@ -5168,16 +5847,24 @@ "max_context_length": { "default": 1024, "description": "Maximum number of tokens to send to the model.", + "maximum": 32000, "minimum": 80, "type": "integer" }, "max_length": { "default": 80, "description": "Number of tokens to generate.", - "maximum": 512, + "maximum": 1024, "minimum": 16, "type": "integer" }, + "min_p": { + "default": 0, + "description": "Min-p sampling value.", + "maximum": 1, + "minimum": 0, + "type": "number" + }, "n": { "example": 1, "maximum": 20, @@ -5214,6 +5901,20 @@ "example": false, "type": "boolean" }, + "smoothing_factor": { + "default": 0, + "description": "Quadratic sampling value.", + "maximum": 10, + "minimum": 0, + "type": "number" + }, + "stop_sequence": { + "items": { + "description": "An array of string sequences whereby the model will stop generating further tokens. The returned text WILL contain the stop sequence.", + "type": "string" + }, + "type": "array" + }, "temperature": { "description": "Temperature value.", "maximum": 5, @@ -5249,6 +5950,11 @@ "maximum": 1, "minimum": 0, "type": "number" + }, + "use_default_badwordsids": { + "description": "When True, uses the default KoboldAI bad word IDs.", + "example": true, + "type": "boolean" } }, "type": "object" @@ -5259,7 +5965,6 @@ "default": 7.5, "maximum": 100, "minimum": 0, - "multipleOf": 0.5, "type": "number" }, "clip_skip": { @@ -5352,21 +6057,22 @@ "sampler_name": { "default": "k_euler_a", "enum": [ - "k_lms", - "k_heun", "k_euler", + "lcm", "k_euler_a", - "k_dpm_2", - "k_dpm_2_a", - "k_dpm_fast", - "k_dpm_adaptive", "k_dpmpp_2s_a", - "k_dpmpp_2m", + "DDIM", "dpmsolver", + "k_lms", + "k_dpm_fast", "k_dpmpp_sde", - "DDIM" + "k_dpm_2", + "k_dpm_2_a", + "k_dpm_adaptive", + "k_heun", + "k_dpmpp_2m" ], - "example": "k_lms", + "example": "k_euler", "type": "string" }, "seed": { @@ -5389,6 +6095,12 @@ "description": "Set to True to create images that stitch together seamlessly.", "type": "boolean" }, + "tis": { + "items": { + "$ref": "#/components/schemas/ModelPayloadTextualInversionsStable" + }, + "type": "array" + }, "width": { "default": 512, "description": "The width of the image to generate.", @@ -5429,6 +6141,37 @@ } ] }, + "ModelPayloadTextualInversionsStable": { + "properties": { + "inject_ti": { + "description": "If set, Will automatically add this TI filename to the prompt or negative prompt accordingly using the provided strength. If this is set to None, then the user will have to manually add the embed to the prompt themselves.", + "enum": [ + "prompt", + "negprompt" + ], + "example": "prompt", + "type": "string" + }, + "name": { + "description": "The exact name or CivitAI ID of the Textual Inversion.", + "example": "7808", + "maxLength": 255, + "minLength": 1, + "type": "string" + }, + "strength": { + "default": 1, + "description": "The strength with which to apply the TI to the prompt. Only used when inject_ti is not None", + "maximum": 5, + "minimum": -5, + "type": "number" + } + }, + "required": [ + "name" + ], + "type": "object" + }, "ModelSpecialPayloadStable": { "properties": { "*": { @@ -5477,6 +6220,13 @@ }, "ModifyUser": { "properties": { + "admin_comment": { + "description": "The new admin comment.", + "example": "User is sus", + "maxLength": 500, + "minLength": 5, + "type": "string" + }, "concurrency": { "description": "The request concurrency this user has after this request.", "example": 30, @@ -5491,6 +6241,10 @@ "description": "The user's new customizer status.", "type": "boolean" }, + "education": { + "description": "The user's new education status.", + "type": "boolean" + }, "flagged": { "description": "The user's new flagged status.", "type": "boolean" @@ -5518,6 +6272,10 @@ "example": false, "type": "boolean" }, + "service": { + "description": "The user's new service status.", + "type": "boolean" + }, "special": { "description": "The user's new special status.", "type": "boolean" @@ -5550,9 +6308,16 @@ }, "ModifyUserInput": { "properties": { + "admin_comment": { + "description": "Add further information about this user for the other admins.", + "example": "User is sus", + "maxLength": 500, + "minLength": 5, + "type": "string" + }, "concurrency": { "description": "The amount of concurrent request this user can have.", - "maximum": 100, + "maximum": 500, "minimum": 0, "type": "integer" }, @@ -5568,6 +6333,16 @@ "example": false, "type": "boolean" }, + "education": { + "description": "When set to true, the user is considered an education account and some options become more restrictive.", + "example": false, + "type": "boolean" + }, + "filtered": { + "description": "When set to true, the replacement filter will always be applied against this user", + "example": false, + "type": "boolean" + }, "flagged": { "description": "When set to true, the user cannot tranfer kudos and all their workers are put into permanent maintenance.", "example": false, @@ -5595,6 +6370,11 @@ "description": "Set the user's suspicion back to 0.", "type": "boolean" }, + "service": { + "description": "When set to true, the user is considered a service account proxying the requests for other users.", + "example": false, + "type": "boolean" + }, "special": { "description": "When set to true, The user can send special payloads.", "example": false, @@ -5660,7 +6440,6 @@ "info": { "description": "You can optionally provide a server note which will be seen in the server details. No profanity allowed!", "maxLength": 1000, - "minLength": 2, "type": "string" }, "maintenance": { @@ -5789,33 +6568,6 @@ }, "type": "object" }, - "NoValidRequestFoundKobold": { - "allOf": [ - { - "$ref": "#/components/schemas/NoValidRequestFound" - }, - { - "properties": { - "matching_softprompt": { - "description": "How many waiting requests were skipped because they demanded an available soft-prompt which this worker does not have.", - "example": 0, - "type": "integer" - }, - "max_context_length": { - "description": "How many waiting requests were skipped because they demanded a higher max_context_length than what this worker provides.", - "example": 0, - "type": "integer" - }, - "max_length": { - "description": "How many waiting requests were skipped because they demanded more generated tokens that what this worker can provide.", - "example": 0, - "type": "integer" - } - }, - "type": "object" - } - ] - }, "NoValidRequestFoundStable": { "allOf": [ { @@ -5884,18 +6636,20 @@ }, "PopInput": { "properties": { + "amount": { + "default": 1, + "description": "How many jobvs to pop at the same time", + "maximum": 20, + "minimum": 1, + "type": "integer" + }, "bridge_agent": { "default": "unknown:0:unknown", "description": "The worker name, version and website.", - "example": "AI Horde Worker:11:https://github.com/db0/AI-Horde-Worker", + "example": "AI Horde Worker reGen:4.1.0:https://github.com/Haidra-Org/horde-worker-reGen", "maxLength": 1000, "type": "string" }, - "bridge_version": { - "default": 1, - "description": "The version of the bridge used by this worker.", - "type": "integer" - }, "models": { "items": { "description": "Which models this worker is serving.", @@ -5930,7 +6684,7 @@ "threads": { "default": 1, "description": "How many threads this worker is running. This is used to accurately the current power available in the horde.", - "maximum": 10, + "maximum": 50, "minimum": 1, "type": "integer" } @@ -6058,11 +6812,17 @@ }, "kudos": { "description": "The expected kudos consumption for this request.", - "type": "integer" + "type": "number" }, "message": { "description": "Any extra information from the horde about this request.", "type": "string" + }, + "warnings": { + "items": { + "$ref": "#/components/schemas/RequestSingleWarning" + }, + "type": "array" } }, "type": "object" @@ -6072,8 +6832,155 @@ "message": { "description": "The error message for this status code.", "type": "string" + }, + "rc": { + "description": "The return code for this error. See: https://github.com/Haidra-Org/AI-Horde/blob/main/README_return_codes.md", + "enum": [ + "MissingPrompt", + "CorruptPrompt", + "KudosValidationError", + "NoValidActions", + "InvalidSize", + "InvalidPromptSize", + "TooManySteps", + "Profanity", + "ProfaneWorkerName", + "ProfaneBridgeAgent", + "ProfaneWorkerInfo", + "ProfaneUserName", + "ProfaneUserContact", + "ProfaneAdminComment", + "ProfaneTeamName", + "ProfaneTeamInfo", + "TooLong", + "TooLongWorkerName", + "TooLongUserName", + "NameAlreadyExists", + "WorkerNameAlreadyExists", + "TeamNameAlreadyExists", + "PolymorphicNameConflict", + "ImageValidationFailed", + "SourceImageResolutionExceeded", + "SourceImageSizeExceeded", + "SourceImageUrlInvalid", + "SourceImageUnreadable", + "InpaintingMissingMask", + "SourceMaskUnnecessary", + "UnsupportedSampler", + "UnsupportedModel", + "ControlNetUnsupported", + "ControlNetSourceMissing", + "ControlNetInvalidPayload", + "SourceImageRequiredForModel", + "UnexpectedModelName", + "TooManyUpscalers", + "ProcGenNotFound", + "InvalidAestheticAttempt", + "AestheticsNotCompleted", + "AestheticsNotPublic", + "AestheticsDuplicate", + "AestheticsMissing", + "AestheticsSolo", + "AestheticsConfused", + "AestheticsAlreadyExist", + "AestheticsServerRejected", + "AestheticsServerError", + "AestheticsServerDown", + "AestheticsServerTimeout", + "InvalidAPIKey", + "WrongCredentials", + "NotAdmin", + "NotModerator", + "NotOwner", + "NotPrivileged", + "AnonForbidden", + "AnonForbiddenWorker", + "AnonForbiddenUserMod", + "NotTrusted", + "UntrustedTeamCreation", + "UntrustedUnsafeIP", + "WorkerMaintenance", + "WorkerFlaggedMaintenance", + "TooManySameIPs", + "WorkerInviteOnly", + "UnsafeIP", + "TimeoutIP", + "TooManyNewIPs", + "KudosUpfront", + "SharedKeyEmpty", + "SharedKeyExpired", + "SharedKeyInsufficientKudos", + "InvalidJobID", + "RequestNotFound", + "WorkerNotFound", + "TeamNotFound", + "FilterNotFound", + "UserNotFound", + "DuplicateGen", + "AbortedGen", + "RequestExpired", + "TooManyPrompts", + "NoValidWorkers", + "MaintenanceMode", + "TargetAccountFlagged", + "SourceAccountFlagged", + "FaultWhenKudosReceiving", + "FaultWhenKudosSending", + "TooFastKudosTransfers", + "KudosTransferToAnon", + "KudosTransferToSelf", + "KudosTransferNotEnough", + "NegativeKudosTransfer", + "KudosTransferFromAnon", + "InvalidAwardUsername", + "KudosAwardToAnon", + "NotAllowedAwards", + "NoWorkerModSelected", + "NoUserModSelected", + "NoHordeModSelected", + "NoTeamModSelected", + "NoFilterModSelected", + "NoSharedKeyModSelected", + "BadRequest", + "Forbidden", + "Locked", + "ControlNetMismatch", + "HiResFixMismatch", + "TooManyLoras", + "BadLoraVersion", + "TooManyTIs", + "BetaAnonForbidden", + "BetaComparisonFault", + "BadCFGDecimals", + "BadCFGNumber", + "BadClientAgent", + "SpecialMissingPayload", + "SpecialForbidden", + "SpecialMissingUsername", + "SpecialModelNeedsSpecialUser", + "SpecialFieldNeedsSpecialUser", + "Img2ImgMismatch", + "TilingMismatch", + "EducationCannotSendKudos", + "InvalidPriorityUsername", + "OnlyServiceAccountProxy", + "RequiresTrust", + "InvalidRemixModel", + "InvalidExtraSourceImages", + "TooManyExtraSourceImages", + "MissingFullSamplerOrder", + "TooManyStopSequences", + "ExcessiveStopSequence", + "TokenOverflow", + "MoreThanMinExtraSourceImage" + ], + "example": "ExampleHordeError", + "type": "string" } }, + "required": [ + "rc" + ], "type": "object" }, "RequestInterrogationResponse": { @@ -6089,6 +6996,32 @@ }, "type": "object" }, + "RequestSingleWarning": { + "properties": { + "code": { + "description": "A unique identifier for this warning.", + "enum": [ + "NoAvailableWorker", + "ClipSkipMismatch", + "StepsTooFew", + "StepsTooMany", + "CfgScaleMismatch", + "CfgScaleTooSmall", + "CfgScaleTooLarge", + "SamplerMismatch", + "SchedulerMismatch" + ], + "example": "NoAvailableWorker", + "type": "string" + }, + "message": { + "description": "Something that you should be aware about this request, in plain text.", + "minLength": 1, + "type": "string" + } + }, + "type": "object" + }, "RequestStatusCheck": { "properties": { "done": { @@ -6176,6 +7109,25 @@ } ] }, + "RequestValidationError": { + "allOf": [ + { + "$ref": "#/components/schemas/RequestError" + }, + { + "properties": { + "errors": { + "additionalProperties": { + "description": "The details of the validation error", + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + } + ] + }, "SharedKeyDetails": { "properties": { "expiry": { @@ -6203,6 +7155,10 @@ "description": "The maximum amount of text tokens this key can generate per job. -1 means unlimited.", "type": "integer" }, + "name": { + "description": "The Shared Key Name.", + "type": "string" + }, "username": { "description": "The owning user's unique Username. It is a combination of their chosen alias plus their ID.", "type": "string" @@ -6288,12 +7244,12 @@ }, "SinglePeriodImgStat": { "properties": { - "requests": { - "description": "The amount of text requests generated during this period.", + "images": { + "description": "The amount of images generated during this period.", "type": "integer" }, - "tokens": { - "description": "The amount of tokens generated during this period.", + "ps": { + "description": "The amount of pixelsteps generated during this period.", "type": "integer" } }, @@ -6382,6 +7338,24 @@ ], "type": "object" }, + "SubmitInputKobold": { + "allOf": [ + { + "$ref": "#/components/schemas/SubmitInput" + }, + { + "properties": { + "gen_metadata": { + "items": { + "$ref": "#/components/schemas/GenerationMetadataKobold" + }, + "type": "array" + } + }, + "type": "object" + } + ] + }, "SubmitInputStable": { "allOf": [ { @@ -6391,9 +7365,15 @@ "properties": { "censored": { "default": false, - "description": "If True, this resulting image has been censored.", + "description": "OBSOLETE (start using meta): If True, this resulting image has been censored.", "type": "boolean" }, + "gen_metadata": { + "items": { + "$ref": "#/components/schemas/GenerationMetadataStable" + }, + "type": "array" + }, "seed": { "description": "The seed for this generation.", "type": "integer" @@ -6524,6 +7504,11 @@ "example": 60, "type": "integer" }, + "admin_comment": { + "description": "(Privileged) Information about this users by the admins", + "example": "User is sus", + "type": "string" + }, "concurrency": { "description": "How many concurrent generations this user may request.", "type": "integer" @@ -6536,12 +7521,17 @@ "contributions": { "$ref": "#/components/schemas/ContributionsDetails" }, + "education": { + "description": "This is an education account used schools and universities.", + "example": false, + "type": "boolean" + }, "evaluating_kudos": { "description": "(Privileged) The amount of Evaluating Kudos this untrusted user has from generations and uptime. When this number reaches a prespecified threshold, they automatically become trusted.", "type": "number" }, "flagged": { - "description": "This user has been flagged for suspicious activity.", + "description": "(Privileged) This user has been flagged for suspicious activity.", "example": false, "type": "boolean" }, @@ -6557,7 +7547,7 @@ "$ref": "#/components/schemas/UserKudosDetails" }, "moderator": { - "description": "This user is a Horde moderator.", + "description": "This user is a AI Horde moderator.", "example": false, "type": "boolean" }, @@ -6572,6 +7562,11 @@ "records": { "$ref": "#/components/schemas/UserRecords" }, + "service": { + "description": "This is a service account used by a horde proxy.", + "example": false, + "type": "boolean" + }, "sharedkey_ids": { "items": { "description": "(Privileged) The list of shared key IDs created by this user.", @@ -6591,7 +7586,7 @@ "type": "integer" }, "trusted": { - "description": "This user is a trusted member of the Horde.", + "description": "This user is a trusted member of the AI Horde.", "example": false, "type": "boolean" }, @@ -6620,7 +7615,7 @@ "type": "array" }, "worker_invited": { - "description": "Whether this user has been invited to join a worker to the horde and how many of them. When 0, this user cannot add (new) workers to the horde.", + "description": "Whether this user has been invited to join a worker to the AI Horde and how many of them. When 0, this user cannot add (new) workers to the horde.", "type": "integer" } }, @@ -6635,7 +7630,7 @@ }, "admin": { "default": 0, - "description": "The amount of Kudos this user has been given by the Horde admins.", + "description": "The amount of Kudos this user has been given by the AI Horde admins.", "type": "number" }, "awarded": { @@ -6643,6 +7638,11 @@ "description": "The amount of Kudos this user has been awarded from things like rating images.", "type": "number" }, + "donated": { + "default": 0, + "description": "The amount of Kudos this user has donated to public goods accounts like education.", + "type": "number" + }, "gifted": { "default": 0, "description": "The amount of Kudos this user has given to other users.", @@ -6708,7 +7708,7 @@ "bridge_agent": { "default": "unknown:0:unknown", "description": "The bridge agent name, version and website.", - "example": "AI Horde Worker:11:https://github.com/db0/AI-Horde-Worker", + "example": "AI Horde Worker reGen:4.1.0:https://github.com/Haidra-Org/horde-worker-reGen", "maxLength": 1000, "type": "string" }, @@ -6734,6 +7734,11 @@ "description": "If True, this worker supports and allows img2img requests.", "type": "boolean" }, + "ipaddr": { + "description": "Privileged. The last known IP this worker has connected from.", + "example": "username#1", + "type": "string" + }, "kudos_details": { "$ref": "#/components/schemas/WorkerKudosDetails" }, @@ -6833,7 +7838,7 @@ "type": "integer" }, "uptime": { - "description": "The amount of seconds this worker has been online for this Horde.", + "description": "The amount of seconds this worker has been online for this AI Horde.", "type": "integer" } }, diff --git a/codegen/ai_horde_codegen_10072023.py b/codegen/ai_horde_codegen_10072023.py deleted file mode 100644 index 68038b0..0000000 --- a/codegen/ai_horde_codegen_10072023.py +++ /dev/null @@ -1,1619 +0,0 @@ -# generated by datamodel-codegen: -# filename: swagger_openapi3.json -# timestamp: 2023-07-10T14:57:52+00:00 - -# Hand edited by tazlin to fix some issues with the generated code -# See README.md for more information - -from __future__ import annotations - -from datetime import datetime -from enum import Enum -from typing import Any - -from pydantic import BaseModel, Field, RootModel - - -class WorkerType(Enum): - image = "image" - text = "text" - - -class ActiveModelLite(BaseModel): - count: int | None = Field(None, description="How many of workers in this horde are running this model.") - name: str | None = Field(None, description="The Name of a model available by workers in this horde.") - - -class AestheticRating(BaseModel): - artifacts: int | None = Field( - None, - description=( - "The artifacts rating for this image.\n" - "0 for flawless generation that perfectly fits to the prompt.\n" - "1 for small, hardly recognizable flaws.\n" - "2 small flaws that can easily be spotted, but don not harm the aesthetic experience.\n" - "3 for flaws that look obviously wrong, but only mildly harm the aesthetic experience.\n" - "4 for flaws that look obviously wrong & significantly harm the aesthetic experience.\n" - "5 for flaws that make the image look like total garbage." - ), - examples=[1], - ge=0, - le=5, - ) - id_: str = Field( - ..., - description="The UUID of image being rated.", - examples=["6038971e-f0b0-4fdd-a3bb-148f561f815e"], - max_length=36, - min_length=36, - ) - rating: int = Field(..., description="The aesthetic rating 1-10 for this image.", ge=1, le=10) - - -class AestheticsPayload(BaseModel): - best: str | None = Field( - None, - description=( - "The UUID of the best image in this generation batch (only used when 2+ images generated). If 2+ aesthetic" - " ratings are also provided, then they take precedence if they're not tied." - ), - examples=["6038971e-f0b0-4fdd-a3bb-148f561f815e"], - max_length=36, - min_length=36, - ) - ratings: list[AestheticRating] | None = None - - -class ContributionsDetails(BaseModel): - fulfillments: int | None = Field(None, description="How many images this user has generated.") - megapixelsteps: float | None = Field(None, description="How many megapixelsteps this user has generated.") - - -class CreateTeamInput(BaseModel): - info: str | None = Field( - None, - description="Extra information or comments about this team.", - examples=["Anarchy is emergent order."], - max_length=1000, - min_length=3, - ) - name: str = Field(..., description="The name of the team. No profanity allowed!", max_length=100, min_length=3) - - -class DeleteTimeoutIPInput(BaseModel): - ipaddr: str = Field( - ..., - description="The IP address to remove from timeout.", - examples=["127.0.0.1"], - max_length=15, - min_length=7, - ) - - -class DeletedTeam(BaseModel): - deleted_id_: str | None = Field(None, description="The ID of the deleted team.") - deleted_name: str | None = Field(None, description="The Name of the deleted team.") - - -class DeletedWorker(BaseModel): - deleted_id_: str | None = Field(None, description="The ID of the deleted worker.") - deleted_name: str | None = Field(None, description="The Name of the deleted worker.") - - -class FilterDetails(BaseModel): - description: str | None = Field(None, description="Description about this regex.") - filter_type_: int = Field(..., description="The integer defining this filter type.", examples=[10], ge=10, le=29) - id_: str = Field(..., description="The UUID of this filter.") - regex: str = Field(..., description="The regex for this filter.", examples=["ac.*"]) - replacement: str | None = Field("", description="The replacement string for this regex.") - user: str = Field(..., description="The moderator which added or last updated this regex.") - - -class FilterPromptSuspicion(BaseModel): - matches: list[str] | None = None - suspicion: str = Field( - ..., - description="Rates how suspicious the provided prompt is. A suspicion over 2 means it would be blocked.", - ) - - -class FilterRegex(BaseModel): - filter_type_: int = Field(..., description="The integer defining this filter type.", examples=[10], ge=10, le=29) - regex: str = Field(..., description="The full regex for this filter type.") - - -class State(Enum): - ok = "ok" - censored = "censored" - - -class Generation(BaseModel): - model: str | None = Field(None, description="The model which generated this image.", title="Generation Model") - state: State = Field(..., description="The state of this generation.", examples=["ok"], title="Generation State") - worker_id_: str | None = Field( - None, - description="The UUID of the worker which generated this image.", - title="Worker ID", - ) - worker_name: str | None = Field( - None, - description="The name of the worker which generated this image.", - title="Worker Name", - ) - - -class SourceProcessing(Enum): - img2img = "img2img" - inpainting = "inpainting" - outpainting = "outpainting" - - -class GenerationKobold(Generation): - seed: int | None = Field(0, description="The seed which generated this text.", title="Generation Seed") - text: str | None = Field(None, description="The generated text.", min_length=0, title="Generated Text") - - -class GenerationStable(Generation): - censored: bool | None = Field( - None, - description="When true this image has been censored by the worker's safety filter.", - ) - id_: str | None = Field(None, description="The ID for this image.", title="Generation ID") - img: str | None = Field( - None, - description="The generated image as a Base64-encoded .webp file.", - title="Generated Image", - ) - seed: str | None = Field(None, description="The seed which generated this image.", title="Generation Seed") - - -class GenerationSubmitted(BaseModel): - reward: float | None = Field( - None, - description="The amount of kudos gained for submitting this request.", - examples=[10], - ) - - -class HordeModes(BaseModel): - invite_only_mode: bool | None = Field( - None, - description="When True, this Horde will not only accept worker explicitly invited to join.", - ) - maintenance_mode: bool | None = Field( - None, - description=( - "When True, this Horde will not accept new requests for image generation, but will finish processing the" - " ones currently in the queue." - ), - ) - raid_mode: bool | None = Field( - None, - description="When True, this Horde will not always provide full information in order to throw off attackers.", - ) - - -class HordePerformance(BaseModel): - interrogator_count: int | None = Field( - None, - description=( - "How many workers are actively processing image interrogations in this Horde in the past 5 minutes." - ), - ) - interrogator_thread_count: int | None = Field( - None, - description=( - "How many worker threads are actively processing image interrogation in this Horde in the past 5 minutes." - ), - ) - past_minute_megapixelsteps: float | None = Field( - None, - description="How many megapixelsteps this Horde generated in the last minute.", - ) - past_minute_tokens: float | None = Field( - None, - description="How many tokens this Horde generated in the last minute.", - ) - queued_forms: float | None = Field( - None, - description="The amount of image interrogations waiting and processing currently in this Horde.", - ) - queued_megapixelsteps: float | None = Field( - None, - description="The amount of megapixelsteps in waiting and processing requests currently in this Horde.", - ) - queued_requests: int | None = Field( - None, - description="The amount of waiting and processing image requests currently in this Horde.", - ) - queued_text_requests: int | None = Field( - None, - description="The amount of waiting and processing text requests currently in this Horde.", - ) - queued_tokens: float | None = Field( - None, - description="The amount of tokens in waiting and processing requests currently in this Horde.", - ) - text_thread_count: int | None = Field( - None, - description=( - "How many worker threads are actively processing prompt generations in this Horde in the past 5 minutes." - ), - ) - text_worker_count: int | None = Field( - None, - description="How many workers are actively processing prompt generations in this Horde in the past 5 minutes.", - ) - thread_count: int | None = Field( - None, - description=( - "How many worker threads are actively processing prompt generations in this Horde in the past 5 minutes." - ), - ) - worker_count: int | None = Field( - None, - description="How many workers are actively processing prompt generations in this Horde in the past 5 minutes.", - ) - - -class InterrogationFormResult(BaseModel): - field_: dict[str, dict[str, Any]] | None = Field(None, alias="*") - - -class InterrogationFormStatus(BaseModel): - form: str | None = Field(None, description="The name of this interrogation form.") - result: InterrogationFormResult | None = None - state: str | None = Field( - None, - description="The overall status of this interrogation.", - title="Interrogation State", - ) - - -class Form(Enum): - caption = "caption" - interrogation = "interrogation" - nsfw_ = "nsfw." - GFPGAN = "GFPGAN" - RealESRGAN_x4plus = "RealESRGAN_x4plus" - RealESRGAN_x2plus = "RealESRGAN_x2plus" - RealESRGAN_x4plus_anime_6B = "RealESRGAN_x4plus_anime_6B" - NMKD_Siax = "NMKD_Siax" - field_4x_AnimeSharp = "4x_AnimeSharp" - CodeFormers = "CodeFormers" - strip_background = "strip_background" - - -class Form1(Enum): - caption = "caption" - interrogation = "interrogation" - nsfw = "nsfw" - GFPGAN = "GFPGAN" - RealESRGAN_x4plus = "RealESRGAN_x4plus" - RealESRGAN_x2plus = "RealESRGAN_x2plus" - RealESRGAN_x4plus_anime_6B = "RealESRGAN_x4plus_anime_6B" - NMKD_Siax = "NMKD_Siax" - field_4x_AnimeSharp = "4x_AnimeSharp" - CodeFormers = "CodeFormers" - strip_background = "strip_background" - - -class InterrogationPopInput(BaseModel): - amount: int | None = Field(1, description="The amount of forms to pop at the same time.") - bridge_agent: str | None = Field( - "unknown", - description="The worker name, version and website.", - examples=["AI Horde Worker:11:https://github.com/db0/AI-Horde-Worker"], - max_length=1000, - ) - bridge_version: int | None = Field(1, description="The version of the bridge used by this worker.") - forms: list[Form1] | None = None - max_tiles: int | None = Field( - 16, - description="The maximum amount of 512x512 tiles this worker can post-process.", - ge=1, - le=256, - ) - name: str | None = Field(None, description="The Name of the Worker.") - priority_usernames: list[str] | None = None - threads: int | None = Field( - 1, - description=( - "How many threads this worker is running. This is used to accurately the current power available in the" - " horde." - ), - ge=1, - le=100, - ) - - -class InterrogationStatus(BaseModel): - forms: list[InterrogationFormStatus] | None = None - state: str | None = Field( - None, - description="The overall status of this interrogation.", - title="Interrogation State", - ) - - -class KudosAwarded(BaseModel): - awarded: int | None = Field(None, description="The amount of Kudos awarded.", examples=[100]) - - -class KudosTransferred(BaseModel): - transferred: int | None = Field(None, description="The amount of Kudos tranferred.", examples=[100]) - - -class ModelInterrogationFormPayloadStable(BaseModel): - field_: dict[str, str] | None = Field(None, alias="*") - - -class Name(Enum): - caption = "caption" - interrogation = "interrogation" - nsfw = "nsfw" - GFPGAN = "GFPGAN" - RealESRGAN_x4plus = "RealESRGAN_x4plus" - RealESRGAN_x2plus = "RealESRGAN_x2plus" - RealESRGAN_x4plus_anime_6B = "RealESRGAN_x4plus_anime_6B" - NMKD_Siax = "NMKD_Siax" - field_4x_AnimeSharp = "4x_AnimeSharp" - CodeFormers = "CodeFormers" - strip_background = "strip_background" - - -class ModelInterrogationFormStable(BaseModel): - name: Name = Field(..., description="The type of interrogation this is.", examples=["caption"]) - payload: ModelInterrogationFormPayloadStable | None = None - - -class ModelInterrogationInputStable(BaseModel): - forms: list[ModelInterrogationFormStable] | None = None - slow_workers: bool | None = Field( - True, - description=( - "When True, allows slower workers to pick up this request. Disabling this incurs an extra kudos cost." - ), - ) - source_image: str | None = Field(None, description="The public URL of the image to interrogate.") - - -class ModelPayloadLorasStable(BaseModel): - clip: float | None = Field(1, description="The strength of the LoRa to apply to the clip model.", ge=0.0, le=5.0) - inject_trigger: str | None = Field( - None, - description=( - "If set, will try to discover a trigger for this LoRa which matches or is similar to this string and" - " inject it into the prompt. If 'any' is specified it will be pick the first trigger." - ), - max_length=30, - min_length=1, - ) - model: float | None = Field(1, description="The strength of the LoRa to apply to the SD model.", ge=0.0, le=5.0) - name: str = Field( - ..., - description="The exact name of the LoRa.", - examples=["GlowingRunesAIV6"], - max_length=255, - min_length=1, - ) - - -class ModelPayloadRootKobold(BaseModel): - frmtadsnsp: bool | None = Field( - None, - description=( - "Input formatting option. When enabled, adds a leading space to your input if there is no trailing" - " whitespace at the end of the previous action." - ), - examples=[False], - ) - frmtrmblln: bool | None = Field( - None, - description=( - "Output formatting option. When enabled, replaces all occurrences of two or more consecutive newlines in" - " the output with one newline." - ), - examples=[False], - ) - frmtrmspch: bool | None = Field( - None, - description="Output formatting option. When enabled, removes #/@%}{+=~|\\^<> from the output.", - examples=[False], - ) - frmttriminc: bool | None = Field( - None, - description=( - "Output formatting option. When enabled, removes some characters from the end of the output such that the" - " output doesn't end in the middle of a sentence. If the output is less than one sentence long, does" - " nothing." - ), - examples=[False], - ) - max_context_length: int | None = Field(1024, description="Maximum number of tokens to send to the model.", ge=80) - max_length: int | None = Field(80, description="Number of tokens to generate.", ge=16, le=512) - n: int | None = Field(None, examples=[1], ge=1, le=20) - rep_pen: float | None = Field(None, description="Base repetition penalty value.", ge=1.0, le=3.0) - rep_pen_range: int | None = Field(None, description="Repetition penalty range.", ge=0, le=4096) - rep_pen_slope: float | None = Field(None, description="Repetition penalty slope.", ge=0.0, le=10.0) - sampler_order: list[int] | None = None - singleline: bool | None = Field( - None, - description=( - "Output formatting option. When enabled, removes everything after the first line of the output, including" - " the newline." - ), - examples=[False], - ) - temperature: float | None = Field(None, description="Temperature value.", ge=0.0, le=5.0) - tfs: float | None = Field(None, description="Tail free sampling value.", ge=0.0, le=1.0) - top_a: float | None = Field(None, description="Top-a sampling value.", ge=0.0, le=1.0) - top_k: int | None = Field(None, description="Top-k sampling value.", ge=0, le=100) - top_p: float | None = Field(None, description="Top-p sampling value.", ge=0.001, le=1.0) - typical: float | None = Field(None, description="Typical sampling value.", ge=0.0, le=1.0) - - -class ControlType(Enum): - canny = "canny" - hed = "hed" - depth = "depth" - normal = "normal" - openpose = "openpose" - seg = "seg" - scribble = "scribble" - fakescribbles = "fakescribbles" - hough = "hough" - - -class PostProcessingEnum(Enum): - GFPGAN = "GFPGAN" - RealESRGAN_x4plus = "RealESRGAN_x4plus" - RealESRGAN_x2plus = "RealESRGAN_x2plus" - RealESRGAN_x4plus_anime_6B = "RealESRGAN_x4plus_anime_6B" - NMKD_Siax = "NMKD_Siax" - field_4x_AnimeSharp = "4x_AnimeSharp" - CodeFormers = "CodeFormers" - strip_background = "strip_background" - - -class SamplerName(Enum): - k_lms = "k_lms" - k_heun = "k_heun" - k_euler = "k_euler" - k_euler_a = "k_euler_a" - k_dpm_2 = "k_dpm_2" - k_dpm_2_a = "k_dpm_2_a" - k_dpm_fast = "k_dpm_fast" - k_dpm_adaptive = "k_dpm_adaptive" - k_dpmpp_2s_a = "k_dpmpp_2s_a" - k_dpmpp_2m = "k_dpmpp_2m" - dpmsolver = "dpmsolver" - k_dpmpp_sde = "k_dpmpp_sde" - DDIM = "DDIM" - - -class ModelSpecialPayloadStable(BaseModel): - field_: dict[str, dict[str, Any]] | None = Field(None, alias="*") - - -class ModifyTeam(BaseModel): - info: str | None = Field(None, description="The Info of the team.") - id_: str | None = Field(None, description="The ID of the team.") - name: str | None = Field(None, description="The Name of the team.") - - -class ModifyTeamInput(BaseModel): - info: str | None = Field( - None, - description="Extra information or comments about this team.", - examples=["Anarchy is emergent order."], - max_length=1000, - min_length=3, - ) - name: str | None = Field( - None, - description="The name of the team. No profanity allowed!", - max_length=100, - min_length=3, - ) - - -class ModifyUser(BaseModel): - concurrency: int | None = Field( - None, - description="The request concurrency this user has after this request.", - examples=[30], - ) - contact: str | None = Field(None, description="The new contact details.", examples=["email@examples.com"]) - customizer: bool | None = Field(None, description="The user's new customizer status.") - flagged: bool | None = Field(None, description="The user's new flagged status.") - moderator: bool | None = Field(None, description="The user's new moderator status.", examples=[False]) - monthly_kudos: int | None = Field(None, description="The user's new monthly kudos total.", examples=[0]) - new_kudos: float | None = Field(None, description="The new total Kudos this user has after this request.") - new_suspicion: int | None = Field(None, description="The user's new suspiciousness rating.") - public_workers: bool | None = Field(None, description="The user's new public_workers status.", examples=[False]) - special: bool | None = Field(None, description="The user's new special status.") - trusted: bool | None = Field(None, description="The user's new trusted status.") - usage_multiplier: float | None = Field( - None, - description="Multiplies the amount of kudos lost when generating images.", - examples=[1], - ) - username: str | None = Field(None, description="The user's new username.", examples=["username#1"]) - vpn: bool | None = Field(None, description="The user's new vpn status.") - worker_invited: int | None = Field( - None, - description=( - "Whether this user has been invited to join a worker to the horde and how many of them. When 0, this user" - " cannot add (new) workers to the horde." - ), - examples=[1], - ) - - -class ModifyUserInput(BaseModel): - concurrency: int | None = Field( - None, - description="The amount of concurrent request this user can have.", - ge=0, - le=100, - ) - contact: str | None = Field( - None, - description=( - "Contact details for the horde admins to reach the user in case of emergency. This is only visible to" - " horde moderators." - ), - examples=["email@examples.com"], - max_length=500, - min_length=5, - ) - customizer: bool | None = Field( - None, - description=( - "When set to true, the user will be able to serve custom Stable Diffusion models which do not exist in the" - " Official AI Horde Model Reference." - ), - examples=[False], - ) - flagged: bool | None = Field( - None, - description=( - "When set to true, the user cannot tranfer kudos and all their workers are put into permanent maintenance." - ), - examples=[False], - ) - kudos: float | None = Field(None, description="The amount of kudos to modify (can be negative).") - moderator: bool | None = Field( - None, - description="Set to true to make this user a horde moderator.", - examples=[False], - ) - monthly_kudos: int | None = Field( - None, - description="When specified, will start assigning the user monthly kudos, starting now!", - ) - public_workers: bool | None = Field( - None, - description="Set to true to make this user display their worker IDs.", - examples=[False], - ) - reset_suspicion: bool | None = Field(None, description="Set the user's suspicion back to 0.") - special: bool | None = Field( - None, - description="When set to true, The user can send special payloads.", - examples=[False], - ) - trusted: bool | None = Field( - None, - description="When set to true,the user and their servers will not be affected by suspicion.", - examples=[False], - ) - usage_multiplier: float | None = Field( - None, - description="The amount by which to multiply the users kudos consumption.", - ge=0.1, - le=10.0, - ) - username: str | None = Field( - None, - description="When specified, will change the username. No profanity allowed!", - max_length=100, - min_length=3, - ) - vpn: bool | None = Field( - None, - description=( - "When set to true, the user will be able to onboard workers behind a VPN. This should be used as a" - " temporary solution until the user is trusted." - ), - examples=[False], - ) - worker_invited: int | None = Field( - None, - description=( - "Set to the amount of workers this user is allowed to join to the horde when in worker invite-only mode." - ), - ) - - -class ModifyWorker(BaseModel): - info: str | None = Field(None, description="The new state of the 'info' var for this worker.") - maintenance: bool | None = Field( - None, - description=( - "The new state of the 'maintenance' var for this worker. When True, this worker will not pick up any new" - " requests." - ), - ) - name: str | None = Field(None, description="The new name for this this worker.") - paused: bool | None = Field( - None, - description=( - "The new state of the 'paused' var for this worker. When True, this worker will not be given any new" - " requests." - ), - ) - team: str | None = Field(None, description="The new team of this worker.", examples=["Direct Action"]) - - -class ModifyWorkerInput(BaseModel): - info: str | None = Field( - None, - description=( - "You can optionally provide a server note which will be seen in the server details. No profanity allowed!" - ), - max_length=1000, - min_length=2, - ) - maintenance: bool | None = Field(None, description="Set to true to put this worker into maintenance.") - maintenance_msg: str | None = Field( - None, - description=( - "if maintenance is True, you can optionally provide a message to be used instead of the default" - " maintenance message, so that the owner is informed." - ), - ) - name: str | None = Field( - None, - description="When this is set, it will change the worker's name. No profanity allowed!", - max_length=100, - min_length=5, - ) - paused: bool | None = Field(None, description="(Mods only) Set to true to pause this worker.") - team: str | None = Field( - None, - description=( - "The team towards which this worker contributes kudos. It an empty string ('') is passed, it will leave" - " the worker without a team. No profanity allowed!" - ), - examples=["0bed257b-e57c-4327-ac64-40cdfb1ac5e6"], - max_length=36, - ) - - -class MonthlyKudos(BaseModel): - amount: int | None = Field(None, description="How much recurring Kudos this user receives monthly.") - last_received: datetime | None = Field(None, description="Last date this user received monthly Kudos.") - - -class Newspiece(BaseModel): - date_published: str | None = Field(None, description="The date this newspiece was published.") - importance: str | None = Field(None, description="How critical this piece of news is.", examples=["Information"]) - newspiece: str | None = Field(None, description="The actual piece of news.") - - -class NoValidInterrogationsFound(BaseModel): - bridge_version: int | None = Field( - None, - description=( - "How many waiting requests were skipped because they require a higher version of the bridge than this" - " worker is running (upgrade if you see this in your skipped list)." - ), - examples=[0], - ge=0, - ) - untrusted: int | None = Field( - None, - description=( - "How many waiting requests were skipped because they demanded a trusted worker which this worker is not." - ), - ge=0, - ) - worker_id_: int | None = Field( - None, - description="How many waiting requests were skipped because they demanded a specific worker.", - ge=0, - ) - - -class NoValidRequestFound(BaseModel): - blacklist: int | None = Field( - None, - description=( - "How many waiting requests were skipped because they demanded a generation with a word that this worker" - " does not accept." - ), - ge=0, - ) - bridge_version: int | None = Field( - None, - description=( - "How many waiting requests were skipped because they require a higher version of the bridge than this" - " worker is running (upgrade if you see this in your skipped list)." - ), - examples=[0], - ge=0, - ) - kudos: int | None = Field( - None, - description=( - "How many waiting requests were skipped because the user didn't have enough kudos when this worker" - " requires upfront kudos." - ), - ) - models: int | None = Field( - None, - description=( - "How many waiting requests were skipped because they demanded a different model than what this worker" - " provides." - ), - examples=[0], - ge=0, - ) - nsfw: int | None = Field( - None, - description=( - "How many waiting requests were skipped because they demanded a nsfw generation which this worker does not" - " provide." - ), - ge=0, - ) - performance: int | None = Field( - None, - description="How many waiting requests were skipped because they required higher performance.", - ge=0, - ) - untrusted: int | None = Field( - None, - description=( - "How many waiting requests were skipped because they demanded a trusted worker which this worker is not." - ), - ge=0, - ) - worker_id_: int | None = Field( - None, - description="How many waiting requests were skipped because they demanded a specific worker.", - ge=0, - ) - - -class NoValidRequestFoundKobold(NoValidRequestFound): - matching_softprompt: int | None = Field( - None, - description=( - "How many waiting requests were skipped because they demanded an available soft-prompt which this worker" - " does not have." - ), - examples=[0], - ) - max_context_length: int | None = Field( - None, - description=( - "How many waiting requests were skipped because they demanded a higher max_context_length than what this" - " worker provides." - ), - examples=[0], - ) - max_length: int | None = Field( - None, - description=( - "How many waiting requests were skipped because they demanded more generated tokens that what this worker" - " can provide." - ), - examples=[0], - ) - - -class NoValidRequestFoundStable(NoValidRequestFound): - controlnet: int | None = Field( - None, - description="How many waiting requests were skipped because they requested a controlnet.", - ) - img2img: int | None = Field( - None, - description="How many waiting requests were skipped because they requested img2img.", - ) - lora: int | None = Field(None, description="How many waiting requests were skipped because they requested loras.") - max_pixels: int | None = Field( - None, - description=( - "How many waiting requests were skipped because they demanded a higher size than this worker provides." - ), - ) - painting: int | None = Field( - None, - description="How many waiting requests were skipped because they requested inpainting/outpainting.", - ) - post_processing: int | None = Field( - None, - alias="post-processing", - description="How many waiting requests were skipped because they requested post-processing.", - ) - unsafe_ip: int | None = Field( - None, - description="How many waiting requests were skipped because they came from an unsafe IP.", - ) - - -class PatchExistingFilter(BaseModel): - description: str | None = Field(None, description="Description about this regex.") - filter_type_: int | None = Field( - None, - description="The integer defining this filter type.", - examples=[10], - ge=10, - le=29, - ) - regex: str | None = Field(None, description="The regex for this filter.", examples=["ac.*"]) - replacement: str | None = Field("", description="The replacement string for this regex.") - - -class Model(RootModel): - root: str = Field(..., description="Which models this worker is serving.", max_length=255, min_length=3) - - -class PopInput(BaseModel): - bridge_agent: str | None = Field( - "unknown:0:unknown", - description="The worker name, version and website.", - examples=["AI Horde Worker:11:https://github.com/db0/AI-Horde-Worker"], - max_length=1000, - ) - bridge_version: int | None = Field(1, description="The version of the bridge used by this worker.") - models: list[Model] | None = None - name: str | None = Field(None, description="The Name of the Worker.") - nsfw: bool | None = Field(False, description="Whether this worker can generate NSFW requests or not.") - priority_usernames: list[str] | None = None - require_upfront_kudos: bool | None = Field( - False, - description=( - "If True, this worker will only pick up requests where the owner has the required kudos to consume already" - " available." - ), - examples=[False], - ) - threads: int | None = Field( - 1, - description=( - "How many threads this worker is running. This is used to accurately the current power available in the" - " horde." - ), - ge=1, - le=10, - ) - - -class PopInputKobold(PopInput): - max_context_length: int | None = Field( - 2048, - description="The max amount of context to submit to this AI for sampling.", - ) - max_length: int | None = Field(512, description="The maximum amount of tokens this worker can generate.") - softprompts: list[str] | None = None - - -class PopInputStable(PopInput): - allow_controlnet: bool | None = Field( - True, - description="If True, this worker will pick up requests requesting ControlNet.", - ) - allow_img2img: bool | None = Field(True, description="If True, this worker will pick up img2img requests.") - allow_lora: bool | None = Field(True, description="If True, this worker will pick up requests requesting LoRas.") - allow_painting: bool | None = Field( - True, - description="If True, this worker will pick up inpainting/outpainting requests.", - ) - allow_post_processing: bool | None = Field( - True, - description="If True, this worker will pick up requests requesting post-processing.", - ) - allow_unsafe_ipaddr: bool | None = Field( - True, - description="If True, this worker will pick up img2img requests coming from clients with an unsafe IP.", - ) - blacklist: list[str] | None = None - max_pixels: int | None = Field(262144, description="The maximum amount of pixels this worker can generate.") - - -class PutNewFilter(BaseModel): - description: str | None = Field(None, description="Description about this regex.") - filter_type_: int = Field(..., description="The integer defining this filter type.", examples=[10], ge=10, le=29) - regex: str = Field(..., description="The regex for this filter.", examples=["ac.*"]) - replacement: str | None = Field("", description="The replacement string for this regex.") - - -class RequestAsync(BaseModel): - id_: str | None = Field( - None, - description="The UUID of the request. Use this to retrieve the request status in the future.", - ) - kudos: int | None = Field(None, description="The expected kudos consumption for this request.") - message: str | None = Field(None, description="Any extra information from the horde about this request.") - - -class RequestError(BaseModel): - message: str | None = Field(None, description="The error message for this status code.") - - -class RequestInterrogationResponse(BaseModel): - id_: str | None = Field( - None, - description="The UUID of the request. Use this to retrieve the request status in the future.", - ) - message: str | None = Field(None, description="Any extra information from the horde about this request.") - - -class RequestStatusCheck(BaseModel): - done: bool | None = Field(None, description="True when all jobs in this request are done. Else False.") - faulted: bool | None = Field( - False, - description="True when this request caused an internal server error and could not be completed.", - ) - finished: int | None = Field(None, description="The amount of finished jobs in this request.") - is_possible: bool | None = Field( - True, - description=( - "If False, this request will not be able to be completed with the pool of workers currently available." - ), - ) - kudos: float | None = Field(None, description="The amount of total Kudos this request has consumed until now.") - processing: int | None = Field(None, description="The amount of still processing jobs in this request.") - queue_position: int | None = Field( - None, - description="The position in the requests queue. This position is determined by relative Kudos amounts.", - ) - restarted: int | None = Field( - None, - description=( - "The amount of jobs that timed out and had to be restarted or were reported as failed by a worker." - ), - ) - wait_time: int | None = Field( - None, - description="The expected amount to wait (in seconds) to generate all jobs in this request.", - ) - waiting: int | None = Field(None, description="The amount of jobs waiting to be picked up by a worker.") - - -class RequestStatusKobold(RequestStatusCheck): - generations: list[GenerationKobold] | None = None - - -class RequestStatusStable(RequestStatusCheck): - generations: list[GenerationStable] | None = None - shared: bool | None = Field(None, description="If True, These images have been shared with LAION.") - - -class SharedKeyDetails(BaseModel): - expiry: datetime | None = Field(None, description="The date at which this API key will expire.") - id_: str | None = Field(None, description="The SharedKey ID.") - kudos: int | None = Field(None, description="The Kudos limit assigned to this key.") - max_image_pixels: int | None = Field( - None, - description="The maximum amount of image pixels this key can generate per job. -1 means unlimited.", - ) - max_image_steps: int | None = Field( - None, - description="The maximum amount of image steps this key can use per job. -1 means unlimited.", - ) - max_text_tokens: int | None = Field( - None, - description="The maximum amount of text tokens this key can generate per job. -1 means unlimited.", - ) - username: str | None = Field( - None, - description="The owning user's unique Username. It is a combination of their chosen alias plus their ID.", - ) - utilized: int | None = Field(None, description="How much kudos has been utilized via this shared key until now.") - - -class SharedKeyInput(BaseModel): - expiry: int | None = Field( - -1, - description="The amount of days after which this key will expire. If -1, this key will not expire.", - examples=[30], - ge=-1, - ) - kudos: int | None = Field( - 5000, - description=( - "The Kudos limit assigned to this key. If -1, then anyone with this key can use an unlimited amount of" - " kudos from this account." - ), - ge=-1, - le=50000000, - ) - max_image_pixels: int | None = Field( - -1, - description="The maximum amount of image pixels this key can generate per job. -1 means unlimited.", - ge=-1, - le=4194304, - ) - max_image_steps: int | None = Field( - -1, - description="The maximum amount of image steps this key can use per job. -1 means unlimited.", - ge=-1, - le=500, - ) - max_text_tokens: int | None = Field( - -1, - description="The maximum amount of text tokens this key can generate per job. -1 means unlimited.", - ge=-1, - le=500, - ) - name: str | None = Field( - None, - description="A descriptive name for this key.", - examples=["Mutual Aid"], - max_length=255, - min_length=3, - ) - - -class SimpleResponse(BaseModel): - message: str = Field(..., description="The result of this operation.") - - -class SinglePeriodImgModelStats(BaseModel): - field_: dict[str, int] | None = Field(None, alias="*") - - -class SinglePeriodImgStat(BaseModel): - requests: int | None = Field(None, description="The amount of text requests generated during this period.") - tokens: int | None = Field(None, description="The amount of tokens generated during this period.") - - -class SinglePeriodTxtModelStats(BaseModel): - field_: dict[str, int] | None = Field(None, alias="*") - - -class StatsImgTotals(BaseModel): - day: SinglePeriodImgStat | None = None - hour: SinglePeriodImgStat | None = None - minute: SinglePeriodImgStat | None = None - month: SinglePeriodImgStat | None = None - total: SinglePeriodImgStat | None = None - - -class StatsTxtTotals(BaseModel): - day: SinglePeriodImgStat | None = None - hour: SinglePeriodImgStat | None = None - minute: SinglePeriodImgStat | None = None - month: SinglePeriodImgStat | None = None - total: SinglePeriodImgStat | None = None - - -class JobState(Enum): - ok = "ok" - censored = "censored" - faulted = "faulted" - csam = "csam" - - -class SubmitInput(BaseModel): - generation: str | None = Field( - None, - description="R2 result was uploaded to R2, else the string of the result.", - examples=["R2"], - ) - id_: str = Field( - ..., - description="The UUID of this generation.", - examples=["00000000-0000-0000-0000-000000000000"], - ) - state: JobState | None = Field( - JobState.ok, - description="The state of this generation.", - examples=[JobState.ok], - title="Generation State", - ) - - -class SubmitInputStable(SubmitInput): - censored: bool | None = Field(False, description="If True, this resulting image has been censored.") - seed: int = Field(..., description="The seed for this generation.") - - -class TeamDetailsLite(BaseModel): - id_: str | None = Field(None, description="The UUID of this team.") - name: str | None = Field(None, description="The Name given to this team.") - - -class TxtModelStats(BaseModel): - day: SinglePeriodTxtModelStats | None = None - month: SinglePeriodTxtModelStats | None = None - total: SinglePeriodTxtModelStats | None = None - - -class UsageDetails(BaseModel): - megapixelsteps: float | None = Field(None, description="How many megapixelsteps this user has requested.") - requests: int | None = Field(None, description="How many images this user has requested.") - - -class UserAmountRecords(BaseModel): - image: int | None = Field(0, description="How many images this user has generated or requested.") - interrogation: int | None = Field(0, description="How many texts this user has generated or requested.") - text: int | None = Field(0, description="How many texts this user has generated or requested.") - - -class UserKudosDetails(BaseModel): - accumulated: float | None = Field(0, description="The ammount of Kudos accumulated or used for generating images.") - admin: float | None = Field(0, description="The amount of Kudos this user has been given by the Horde admins.") - awarded: float | None = Field( - 0, - description="The amount of Kudos this user has been awarded from things like rating images.", - ) - gifted: float | None = Field(0, description="The amount of Kudos this user has given to other users.") - received: float | None = Field(0, description="The amount of Kudos this user has been given by other users.") - donated: float | None = Field( - 0, description="The amount of Kudos this user has donated to support education accounts." - ) - recurring: float | None = Field( - 0, - description="The amount of Kudos this user has received from recurring rewards.", - ) - - -class UserThingRecords(BaseModel): - megapixelsteps: float | None = Field( - 0, - description="How many megapixelsteps this user has generated or requested.", - ) - tokens: int | None = Field(0, description="How many token this user has generated or requested.") - - -class Worker_Type(Enum): - image = "image" - text = "text" - interrogation = "interrogation" - - -class WorkerDetailsLite(BaseModel): - id_: str | None = Field(None, description="The UUID of this worker.") - name: str | None = Field(None, description="The Name given to this worker.") - online: bool | None = Field(None, description="True if the worker has checked-in the past 5 minutes.") - type_: Worker_Type | None = Field(None, description="The Type of worker this is.", examples=["image"]) - - -class WorkerKudosDetails(BaseModel): - generated: float | None = Field(None, description="How much Kudos this worker has received for generating images.") - uptime: int | None = Field(None, description="How much Kudos this worker has received for staying online longer.") - - -class ActiveModel(ActiveModelLite): - eta: int | None = Field(None, description="Estimated time in seconds for this model's queue to be cleared.") - jobs: float | None = Field(None, description="The job count waiting to be generated by this model.") - performance: float | None = Field(None, description="The average speed of generation for this model.") - queued: float | None = Field(None, description="The amount waiting to be generated by this model.") - type_: WorkerType | None = Field(None, description="The model type (text or image).", examples=["image"]) - - -class ImgModelStats(BaseModel): - day: SinglePeriodImgModelStats | None = None - month: SinglePeriodImgModelStats | None = None - total: SinglePeriodImgModelStats | None = None - - -class InterrogationPopFormPayload(BaseModel): - form: Form | None = Field(None, description="The name of this interrogation form", examples=["caption"]) - id_: str | None = Field( - None, - description="The UUID of the interrogation form. Use this to post the results in the future.", - ) - payload: ModelInterrogationFormPayloadStable | None = None - r2_upload: str | None = Field(None, description="The URL in which the post-processed image can be uploaded.") - source_image: str | None = Field(None, description="The URL From which the source image can be downloaded.") - - -class InterrogationPopPayload(BaseModel): - forms: list[InterrogationPopFormPayload] | None = None - skipped: NoValidInterrogationsFound | None = None - - -class ModelGenerationInputKobold(ModelPayloadRootKobold): - pass - - -class ModelPayloadKobold(ModelPayloadRootKobold): - prompt: str | None = Field(None, description="The prompt which will be sent to KoboldAI to generate the text.") - - -class ModelPayloadRootStable(BaseModel): - cfg_scale: float | None = Field(7.5, ge=0.0, le=100.0, multiple_of=0.5) - clip_skip: int | None = Field( - None, - description="The number of CLIP language processor layers to skip.", - examples=[1], - ge=1, - le=12, - ) - control_type_: ControlType | None = Field(None, examples=["canny"]) - denoising_strength: float | None = Field(None, examples=[0.75], ge=0.01, le=1.0) - facefixer_strength: float | None = Field(None, examples=[0.75], ge=0.0, le=1.0) - height: int | None = Field( - 512, - description="The height of the image to generate.", - ge=64, - le=3072, - multiple_of=64.0, - ) - hires_fix: bool | None = Field( - False, - description="Set to True to process the image at base resolution before upscaling and re-processing.", - ) - image_is_control: bool | None = Field( - False, - description="Set to True if the image submitted is a pre-generated control map for ControlNet use.", - ) - karras: bool | None = Field(False, description="Set to True to enable karras noise scheduling tweaks.") - loras: list[ModelPayloadLorasStable] | None = None - post_processing: set[PostProcessingEnum] | None = None - return_control_map: bool | None = Field( - False, - description="Set to True if you want the ControlNet map returned instead of a generated image.", - ) - sampler_name: SamplerName | None = Field(SamplerName.k_lms, examples=[SamplerName.k_lms]) - seed: str | None = Field( - None, - description="The seed to use to generate this request. You can pass text as well as numbers.", - examples=["The little seed that could"], - ) - seed_variation: int | None = Field( - None, - description="If passed with multiple n, the provided seed will be incremented every time by this value.", - examples=[1], - ge=1, - le=1000, - ) - special: ModelSpecialPayloadStable | None = None - tiling: bool | None = Field(False, description="Set to True to create images that stitch together seamlessly.") - width: int | None = Field(512, description="The width of the image to generate.", ge=64, le=3072, multiple_of=64.0) - - -class ModelPayloadStable(ModelPayloadRootStable): - ddim_steps: int | None = 30 - n_iter: int | None = Field(1, description="The amount of images to generate.") - prompt: str | None = Field( - None, - description="The prompt which will be sent to Stable Diffusion to generate an image.", - ) - use_nsfw_censor: bool | None = Field( - None, - description="When true will apply NSFW censoring model on the generation.", - ) - - -class TeamDetails(TeamDetailsLite): - info: str | None = Field( - None, - description="Extra information or comments about this team provided by its owner.", - examples=["Anarchy is emergent order."], - ) - creator: str | None = Field(None, description="The alias of the user which created this team.", examples=["db0#1"]) - kudos: float | None = Field( - None, - description="How many Kudos the workers in this team have been rewarded while part of this team.", - ) - models: list[ActiveModelLite] | None = None - requests_fulfilled: int | None = Field(None, description="How many images this team's workers have generated.") - uptime: int | None = Field( - None, - description="The total amount of time workers have stayed online while on this team.", - ) - worker_count: int | None = Field( - None, - description="How many workers have been dedicated to this team.", - examples=[10], - ) - workers: list[WorkerDetailsLite] | None = None - - -class UserRecords(BaseModel): - contribution: UserThingRecords | None = None - fulfillment: UserAmountRecords | None = None - request: UserAmountRecords | None = None - usage: UserThingRecords | None = None - - -class WorkerDetails(WorkerDetailsLite): - info: str | None = Field( - None, - description="Extra information or comments about this worker provided by its owner.", - examples=["https://dbzer0.com"], - ) - bridge_agent: str = Field( - ..., - description="The bridge agent name, version and website.", - examples=["AI Horde Worker:11:https://github.com/db0/AI-Horde-Worker"], - max_length=1000, - ) - contact: str | None = Field( - None, - description=( - "(Privileged) Contact details for the horde admins to reach the owner of this worker in emergencies." - ), - examples=["email@examples.com"], - max_length=500, - min_length=5, - ) - flagged: bool | None = Field( - None, - description=( - "The worker's owner has been flagged for suspicious activity. This worker will not be given any jobs to" - " process." - ), - ) - forms: list[str] | None = None - img2img: bool | None = Field(None, description="If True, this worker supports and allows img2img requests.") - kudos_details: WorkerKudosDetails | None = None - kudos_rewards: float | None = Field(None, description="How many Kudos this worker has been rewarded in total.") - lora: bool | None = Field(None, description="If True, this worker supports and allows lora requests.") - maintenance_mode: bool | None = Field( - None, - description="When True, this worker will not pick up any new requests.", - examples=[False], - ) - max_context_length: int | None = Field(None, description="The maximum tokens this worker can read.", examples=[80]) - max_length: int | None = Field(None, description="The maximum tokens this worker can generate.", examples=[80]) - max_pixels: int | None = Field( - None, - description="The maximum pixels in resolution this worker can generate.", - examples=[262144], - ) - megapixelsteps_generated: float | None = Field( - None, - description="How many megapixelsteps this worker has generated until now.", - ) - models: list[str] | None = None - nsfw: bool | None = Field(False, description="Whether this worker can generate NSFW requests or not.") - owner: str | None = Field( - None, - description="Privileged or public if the owner has allowed it. The alias of the owner of this worker.", - examples=["username#1"], - ) - painting: bool | None = Field(None, description="If True, this worker supports and allows inpainting requests.") - paused: bool | None = Field( - None, - description="(Privileged) When True, this worker not be given any new requests.", - examples=[False], - ) - performance: str | None = Field(None, description="The average performance of this worker in human readable form.") - post_processing: bool | None = Field( - None, - alias="post-processing", - description="If True, this worker supports and allows post-processing requests.", - ) - requests_fulfilled: int | None = Field(None, description="How many images this worker has generated.") - suspicious: int | None = Field( - None, - description="(Privileged) How much suspicion this worker has accumulated.", - examples=[0], - ) - team: TeamDetailsLite | None = None - threads: int | None = Field(None, description="How many threads this worker is running.") - tokens_generated: float | None = Field(None, description="How many tokens this worker has generated until now.") - trusted: bool | None = Field(None, description="The worker is trusted to return valid generations.") - uncompleted_jobs: int | None = Field( - None, - description="How many jobs this worker has left uncompleted after it started them.", - examples=[0], - ) - uptime: int | None = Field(None, description="The amount of seconds this worker has been online for this Horde.") - - -class GenerationInputKobold(BaseModel): - dry_run: bool | None = Field( - False, - description="When false, the endpoint will simply return the cost of the request in kudos and exit.", - ) - models: list[str] | None = None - params: ModelGenerationInputKobold | None = None - prompt: str | None = Field(None, description="The prompt which will be sent to KoboldAI to generate text.") - slow_workers: bool | None = Field( - True, - description=( - "When True, allows slower workers to pick up this request. Disabling this incurs an extra kudos cost." - ), - ) - softprompt: str | None = Field( - None, - description="Specify which softpompt needs to be used to service this request.", - min_length=1, - ) - trusted_workers: bool | None = Field( - False, - description=( - "When true, only trusted workers will serve this request. When False, Evaluating workers will also be used" - " which can increase speed but adds more risk!" - ), - ) - worker_blacklist: bool | None = Field( - False, - description="If true, the worker list will be treated as a blacklist instead of a whitelist.", - ) - workers: list[str] | None = None - - -class GenerationPayload(BaseModel): - id_: str | None = Field(None, description="The UUID for this text generation.") - model: str | None = Field(None, description="Which of the available models to use for this request.") - payload: ModelPayloadKobold | None = None - skipped: NoValidRequestFoundKobold | None = None - softprompt: str | None = Field(None, description="The soft prompt requested for this generation.") - - -class GenerationPayloadStable(BaseModel): - id_: str | None = Field(None, description="The UUID for this image generation.") - model: str | None = Field(None, description="Which of the available models to use for this request.") - payload: ModelPayloadStable | None = None - r2_upload: str | None = Field(None, description="The r2 upload link to use to upload this image.") - skipped: NoValidRequestFoundStable | None = None - source_image: str | None = Field(None, description="The Base64-encoded webp to use for img2img.") - source_mask: str | None = Field( - None, - description=( - "If img_processing is set to 'inpainting' or 'outpainting', this parameter can be optionally provided as" - " the mask of the areas to inpaint. If this arg is not passed, the inpainting/outpainting mask has to be" - " embedded as alpha channel." - ), - ) - source_processing: SourceProcessing | None = Field( - SourceProcessing.img2img, - description="If source_image is provided, specifies how to process it.", - examples=[SourceProcessing.img2img], - ) - - -class ModelGenerationInputStable(ModelPayloadRootStable): - n: int | None = Field(1, description="The amount of images to generate.", ge=1, le=20) - steps: int | None = Field(30, ge=1, le=500) - - -class UserDetails(BaseModel): - account_age: int | None = Field( - None, - description="How many seconds since this account was created.", - examples=[60], - ) - concurrency: int | None = Field(None, description="How many concurrent generations this user may request.") - contact: str | None = Field( - None, - description="(Privileged) Contact details for the horde admins to reach the user in case of emergency.", - examples=["email@examples.com"], - ) - contributions: ContributionsDetails | None = None - evaluating_kudos: float | None = Field( - None, - description=( - "(Privileged) The amount of Evaluating Kudos this untrusted user has from generations and uptime. When" - " this number reaches a prespecified threshold, they automatically become trusted." - ), - ) - flagged: bool | None = Field( - None, - description="This user has been flagged for suspicious activity.", - examples=[False], - ) - id_: int | None = Field(None, description="The user unique ID. It is always an integer.") - kudos: float | None = Field( - None, - description=( - "The amount of Kudos this user has. The amount of Kudos determines the priority when requesting image" - " generations." - ), - ) - kudos_details: UserKudosDetails | None = None - moderator: bool | None = Field(None, description="This user is a Horde moderator.", examples=[False]) - monthly_kudos: MonthlyKudos | None = None - pseudonymous: bool | None = Field( - None, - description="If true, this user has not registered using an oauth service.", - examples=[False], - ) - records: UserRecords | None = None - sharedkey_ids: list[str] | None = None - special: bool | None = Field( - None, - description="(Privileged) This user has been given the Special role.", - examples=[False], - ) - suspicious: int | None = Field( - None, - description="(Privileged) How much suspicion this user has accumulated.", - examples=[0], - ) - trusted: bool | None = Field(None, description="This user is a trusted member of the Horde.", examples=[False]) - usage: UsageDetails | None = None - username: str | None = Field( - None, - description="The user's unique Username. It is a combination of their chosen alias plus their ID.", - ) - vpn: bool | None = Field(None, description="(Privileged) This user has been given the VPN role.", examples=[False]) - worker_count: int | None = Field(None, description="How many workers this user has created (active or inactive).") - worker_ids: list[str] | None = None - worker_invited: int | None = Field( - None, - description=( - "Whether this user has been invited to join a worker to the horde and how many of them. When 0, this user" - " cannot add (new) workers to the horde." - ), - ) - - -class GenerationInputStable(BaseModel): - censor_nsfw: bool | None = Field( - False, - description=( - "If the request is SFW, and the worker accidentally generates NSFW, it will send back a censored image." - ), - ) - dry_run: bool | None = Field( - False, - description="When false, the endpoint will simply return the cost of the request in kudos and exit.", - ) - models: list[str] | None = None - nsfw: bool | None = Field( - False, - description="Set to true if this request is NSFW. This will skip workers which censor images.", - ) - params: ModelGenerationInputStable | None = None - prompt: str = Field( - ..., - description="The prompt which will be sent to Stable Diffusion to generate an image.", - min_length=1, - ) - r2: bool | None = Field(True, description="If True, the image will be sent via cloudflare r2 download link.") - replacement_filter: bool | None = Field( - True, - description="If enabled, suspicious prompts are sanitized through a string replacement filter instead.", - ) - shared: bool | None = Field( - False, - description=( - "If True, The image will be shared with LAION for improving their dataset. This will also reduce your" - " kudos consumption by 2. For anonymous users, this is always True." - ), - ) - slow_workers: bool | None = Field( - True, - description=( - "When True, allows slower workers to pick up this request. Disabling this incurs an extra kudos cost." - ), - ) - source_image: str | None = Field(None, description="The Base64-encoded webp to use for img2img.") - source_mask: str | None = Field( - None, - description=( - "If source_processing is set to 'inpainting' or 'outpainting', this parameter can be optionally provided" - " as the Base64-encoded webp mask of the areas to inpaint. If this arg is not passed, the" - " inpainting/outpainting mask has to be embedded as alpha channel." - ), - ) - source_processing: SourceProcessing | None = Field( - SourceProcessing.img2img, - description="If source_image is provided, specifies how to process it.", - examples=[SourceProcessing.img2img], - ) - trusted_workers: bool | None = Field( - False, - description=( - "When true, only trusted workers will serve this request. When False, Evaluating workers will also be used" - " which can increase speed but adds more risk!" - ), - ) - worker_blacklist: bool | None = Field( - False, - description="If true, the worker list will be treated as a blacklist instead of a whitelist.", - ) - workers: list[str] | None = None diff --git a/codegen/swagger.json b/codegen/swagger.json deleted file mode 100644 index 48d3732..0000000 --- a/codegen/swagger.json +++ /dev/null @@ -1,5864 +0,0 @@ -{ - "swagger": "2.0", - "basePath": "/api", - "paths": { - "/v2/filters": { - "put": { - "responses": { - "403": { - "description": "Access Denied", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "400": { - "description": "Validation Error", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "201": { - "description": "New Filter details", - "schema": { - "$ref": "#/definitions/FilterDetails" - } - } - }, - "summary": "Moderator Only: Add a new regex filter", - "operationId": "put_filters", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "required": true, - "description": "A mod API key." - }, - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version.", - "default": "unknown:0:unknown" - }, - { - "name": "payload", - "required": true, - "in": "body", - "schema": { - "$ref": "#/definitions/PutNewFilter" - } - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - }, - "get": { - "responses": { - "403": { - "description": "Access Denied", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "400": { - "description": "Validation Error", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Filters List", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/FilterDetails" - } - } - } - }, - "summary": "Moderator Only: A List all filters, or filtered by the query", - "operationId": "get_filters", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "required": true, - "description": "A mod API key." - }, - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version.", - "default": "unknown:0:unknown" - }, - { - "name": "filter_type", - "in": "query", - "type": "integer", - "description": "The filter type." - }, - { - "name": "contains", - "in": "query", - "type": "string", - "description": "Only return filter containing this word." - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - }, - "post": { - "responses": { - "403": { - "description": "Access Denied", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "400": { - "description": "Validation Error", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Returns the suspicion of the provided prompt. A suspicion of 2 or more means it would be blocked.", - "schema": { - "$ref": "#/definitions/FilterPromptSuspicion" - } - } - }, - "summary": "Moderator Only: Check The suspicion of the provided prompt", - "operationId": "post_filters", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "required": true, - "description": "A mod API key." - }, - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version.", - "default": "unknown:0:unknown" - }, - { - "name": "payload", - "required": true, - "in": "body", - "schema": { - "type": "object", - "properties": { - "prompt": { - "type": "string" - }, - "filter_type": { - "type": "integer" - } - } - } - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/filters/regex": { - "get": { - "responses": { - "403": { - "description": "Access Denied", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "400": { - "description": "Validation Error", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Filters Regex", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/FilterRegex" - } - } - } - }, - "summary": "Moderator Only: A List all filters, or filtered by the query", - "operationId": "get_filter_regex", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "required": true, - "description": "A mod API key." - }, - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version.", - "default": "unknown:0:unknown" - }, - { - "name": "filter_type", - "in": "query", - "type": "integer", - "description": "The filter type." - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/filters/{filter_id}": { - "parameters": [ - { - "name": "filter_id", - "in": "path", - "required": true, - "type": "string" - } - ], - "get": { - "responses": { - "403": { - "description": "Access Denied", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "400": { - "description": "Validation Error", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Filters List", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/FilterDetails" - } - } - } - }, - "summary": "Moderator Only: Display a single filter", - "operationId": "get_filter_single", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "required": true, - "description": "A mod API key." - }, - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version.", - "default": "unknown:0:unknown" - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - }, - "delete": { - "responses": { - "403": { - "description": "Access Denied", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "400": { - "description": "Validation Error", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Filter Deleted", - "schema": { - "$ref": "#/definitions/SimpleResponse" - } - } - }, - "summary": "Moderator Only: Delete a regex filter", - "operationId": "delete_filter_single", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "required": true, - "description": "A mod API key." - }, - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version.", - "default": "unknown:0:unknown" - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - }, - "patch": { - "responses": { - "403": { - "description": "Access Denied", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "400": { - "description": "Validation Error", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Patched Filter details", - "schema": { - "$ref": "#/definitions/FilterDetails" - } - } - }, - "summary": "Moderator Only: Modify an existing regex filter", - "operationId": "patch_filter_single", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "required": true, - "description": "A mod API key." - }, - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version.", - "default": "unknown:0:unknown" - }, - { - "name": "payload", - "required": true, - "in": "body", - "schema": { - "$ref": "#/definitions/PatchExistingFilter" - } - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/find_user": { - "get": { - "responses": { - "404": { - "description": "User Not Found", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "400": { - "description": "Validation Error", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Worker Details", - "schema": { - "$ref": "#/definitions/UserDetails" - } - } - }, - "summary": "Lookup user details based on their API key", - "description": "This can be used to verify a user exists", - "operationId": "get_find_user", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "description": "User API key we're looking for." - }, - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version.", - "default": "unknown:0:unknown" - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/generate/async": { - "post": { - "responses": { - "429": { - "description": "Too Many Prompts", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "503": { - "description": "Maintenance Mode", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "400": { - "description": "Validation Error", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "202": { - "description": "Generation Queued", - "schema": { - "$ref": "#/definitions/RequestAsync" - } - } - }, - "summary": "Initiate an Asynchronous request to generate images", - "description": "This endpoint will immediately return with the UUID of the request for generation.\nThis endpoint will always be accepted, even if there are no workers available currently to fulfill this request. \nPerhaps some will appear in the next 10 minutes.\nAsynchronous requests live for 10 minutes before being considered stale and being deleted.", - "operationId": "post_image_async_generate", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "required": true, - "description": "The API Key corresponding to a registered user." - }, - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version", - "default": "unknown:0:unknown" - }, - { - "name": "payload", - "required": true, - "in": "body", - "schema": { - "$ref": "#/definitions/GenerationInputStable" - } - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/generate/check/{id}": { - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "type": "string" - } - ], - "get": { - "responses": { - "404": { - "description": "Request Not found", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Async Request Status Check", - "schema": { - "$ref": "#/definitions/RequestStatusCheck" - } - } - }, - "summary": "Retrieve the status of an Asynchronous generation request without images", - "description": "Use this request to check the status of a currently running asynchronous request without consuming bandwidth.", - "operationId": "get_image_async_check", - "parameters": [ - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version", - "default": "unknown:0:unknown" - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/generate/pop": { - "post": { - "responses": { - "403": { - "description": "Access Denied", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "400": { - "description": "Validation Error", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Generation Popped", - "schema": { - "$ref": "#/definitions/GenerationPayloadStable" - } - } - }, - "summary": "Check if there are generation requests queued for fulfillment", - "description": "This endpoint is used by registered workers only", - "operationId": "post_image_job_pop", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "required": true, - "description": "The API Key corresponding to a registered user." - }, - { - "name": "payload", - "required": true, - "in": "body", - "schema": { - "$ref": "#/definitions/PopInputStable" - } - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/generate/rate/{id}": { - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "type": "string" - } - ], - "post": { - "responses": { - "404": { - "description": "Generation Request Not Found", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "400": { - "description": "Aesthetics Already Submitted", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Aesthetics Submitted", - "schema": { - "$ref": "#/definitions/GenerationSubmitted" - } - } - }, - "summary": "Submit aesthetic ratings for generated images to be used by LAION", - "description": "The request has to have been sent as shared: true.\nYou can select the best image in the set, and/or provide a rating for each or some images in the set.\nIf you select best-of image, you will gain 4 kudos. Each rating is 5 kudos. Best-of will be ignored when ratings conflict with it.\nYou can never gain more kudos than you spent for this generation. Your reward at max will be your kudos consumption - 1.", - "operationId": "post_aesthetics", - "parameters": [ - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version", - "default": "unknown:0:unknown" - }, - { - "name": "payload", - "required": true, - "in": "body", - "schema": { - "$ref": "#/definitions/AestheticsPayload" - } - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/generate/status/{id}": { - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "type": "string" - } - ], - "get": { - "responses": { - "404": { - "description": "Request Not found", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Async Request Full Status", - "schema": { - "$ref": "#/definitions/RequestStatusStable" - } - } - }, - "summary": "Retrieve the full status of an Asynchronous generation request", - "description": "This request will include all already generated images in download URL or base64 encoded .webp files.\nAs such, you are requested to not retrieve this endpoint often. Instead use the /check/ endpoint first\nThis endpoint is limited to 10 request per minute", - "operationId": "get_image_async_status", - "parameters": [ - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version", - "default": "unknown:0:unknown" - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - }, - "delete": { - "responses": { - "404": { - "description": "Request Not found", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Async Request Full Status", - "schema": { - "$ref": "#/definitions/RequestStatusStable" - } - } - }, - "summary": "Cancel an unfinished request", - "description": "This request will include all already generated images in base64 encoded .webp files.", - "operationId": "delete_image_async_status", - "parameters": [ - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version", - "default": "unknown:0:unknown" - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/generate/submit": { - "post": { - "responses": { - "404": { - "description": "Request Not Found", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "403": { - "description": "Access Denied", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "400": { - "description": "Generation Already Submitted", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Generation Submitted", - "schema": { - "$ref": "#/definitions/GenerationSubmitted" - } - } - }, - "summary": "Submit a generated image", - "description": "This endpoint is used by registered workers only", - "operationId": "post_image_job_submit", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "required": true, - "description": "The worker's owner API key." - }, - { - "name": "payload", - "required": true, - "in": "body", - "schema": { - "$ref": "#/definitions/SubmitInputStable" - } - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/generate/text/async": { - "post": { - "responses": { - "429": { - "description": "Too Many Prompts", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "503": { - "description": "Maintenance Mode", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "400": { - "description": "Validation Error", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "202": { - "description": "Generation Queued", - "schema": { - "$ref": "#/definitions/RequestAsync" - } - } - }, - "summary": "Initiate an Asynchronous request to generate text", - "description": "This endpoint will immediately return with the UUID of the request for generation.\nThis endpoint will always be accepted, even if there are no workers available currently to fulfill this request. \nPerhaps some will appear in the next 20 minutes.\nAsynchronous requests live for 20 minutes before being considered stale and being deleted.", - "operationId": "post_text_async_generate", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "required": true, - "description": "The API Key corresponding to a registered user." - }, - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version", - "default": "unknown:0:unknown" - }, - { - "name": "payload", - "required": true, - "in": "body", - "schema": { - "$ref": "#/definitions/GenerationInputKobold" - } - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/generate/text/pop": { - "post": { - "responses": { - "403": { - "description": "Access Denied", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "400": { - "description": "Validation Error", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Generation Popped", - "schema": { - "$ref": "#/definitions/GenerationPayload" - } - } - }, - "summary": "Check if there are generation requests queued for fulfillment", - "description": "This endpoint is used by registered workers only", - "operationId": "post_text_job_pop", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "required": true, - "description": "The API Key corresponding to a registered user." - }, - { - "name": "payload", - "required": true, - "in": "body", - "schema": { - "$ref": "#/definitions/PopInputKobold" - } - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/generate/text/status/{id}": { - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "type": "string" - } - ], - "get": { - "responses": { - "404": { - "description": "Request Not found", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Async Request Full Status", - "schema": { - "$ref": "#/definitions/RequestStatusKobold" - } - } - }, - "summary": "Retrieve the full status of an Asynchronous generation request", - "description": "This request will include all already generated texts.", - "operationId": "get_text_async_status", - "parameters": [ - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version", - "default": "unknown:0:unknown" - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - }, - "delete": { - "responses": { - "404": { - "description": "Request Not found", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Async Request Full Status", - "schema": { - "$ref": "#/definitions/RequestStatusKobold" - } - } - }, - "summary": "Cancel an unfinished request", - "description": "This request will include all already generated texts.", - "operationId": "delete_text_async_status", - "parameters": [ - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version", - "default": "unknown:0:unknown" - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/generate/text/submit": { - "post": { - "responses": { - "404": { - "description": "Request Not Found", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "403": { - "description": "Access Denied", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "400": { - "description": "Generation Already Submitted", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Generation Submitted", - "schema": { - "$ref": "#/definitions/GenerationSubmitted" - } - } - }, - "summary": "Submit generated text", - "description": "This endpoint is used by registered workers only", - "operationId": "post_text_job_submit", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "required": true, - "description": "The worker's owner API key." - }, - { - "name": "payload", - "required": true, - "in": "body", - "schema": { - "$ref": "#/definitions/SubmitInput" - } - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/interrogate/async": { - "post": { - "responses": { - "429": { - "description": "Too Many Prompts", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "503": { - "description": "Maintenance Mode", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "400": { - "description": "Validation Error", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "202": { - "description": "Interrogation Queued", - "schema": { - "$ref": "#/definitions/RequestInterrogationResponse" - } - } - }, - "summary": "Initiate an Asynchronous request to interrogate an image", - "description": "This endpoint will immediately return with the UUID of the request for interrogation.\nThis endpoint will always be accepted, even if there are no workers available currently to fulfill this request. \nPerhaps some will appear in the next 20 minutes.\nAsynchronous requests live for 20 minutes before being considered stale and being deleted.", - "operationId": "post_interrogate", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "required": true, - "description": "A User API key" - }, - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version", - "default": "unknown:0:unknown" - }, - { - "name": "payload", - "required": true, - "in": "body", - "schema": { - "$ref": "#/definitions/ModelInterrogationInputStable" - } - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/interrogate/pop": { - "post": { - "responses": { - "403": { - "description": "Access Denied", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "400": { - "description": "Validation Error", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Interrogation Popped", - "schema": { - "$ref": "#/definitions/InterrogationPopPayload" - } - } - }, - "summary": "Check if there are interrogation requests queued for fulfillment", - "description": "This endpoint is used by registered workers only", - "operationId": "post_interrogate_pop", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "required": true, - "description": "The API Key corresponding to a registered user" - }, - { - "name": "payload", - "required": true, - "in": "body", - "schema": { - "$ref": "#/definitions/InterrogationPopInput" - } - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/interrogate/status/{id}": { - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "type": "string" - } - ], - "get": { - "responses": { - "404": { - "description": "Request Not found", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Interrogation Request Status", - "schema": { - "$ref": "#/definitions/InterrogationStatus" - } - } - }, - "summary": "Retrieve the full status of an interrogation request", - "description": "This request will include all already generated images.\nAs such, you are requested to not retrieve this endpoint often. Instead use the /check/ endpoint first", - "operationId": "get_interrogation_status", - "parameters": [ - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version", - "default": "unknown:0:unknown" - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - }, - "delete": { - "responses": { - "404": { - "description": "Request Not found", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Interrogation Request Status", - "schema": { - "$ref": "#/definitions/InterrogationStatus" - } - } - }, - "summary": "Cancel an unfinished interrogation request", - "description": "This request will return all already interrogated image results.", - "operationId": "delete_interrogation_status", - "parameters": [ - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version", - "default": "unknown:0:unknown" - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/interrogate/submit": { - "post": { - "responses": { - "404": { - "description": "Request Not Found", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "403": { - "description": "Access Denied", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "400": { - "description": "Generation Already Submitted", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Interrogation Submitted", - "schema": { - "$ref": "#/definitions/GenerationSubmitted" - } - } - }, - "summary": "Submit the results of an interrogated image", - "description": "This endpoint is used by registered workers only", - "operationId": "post_interrogate_submit", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "required": true, - "description": "The worker's owner API key" - }, - { - "name": "payload", - "required": true, - "in": "body", - "schema": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "result": { - "type": "string" - }, - "state": { - "type": "string" - } - } - } - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/kudos/award": { - "post": { - "responses": { - "403": { - "description": "Access Denied", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "400": { - "description": "Validation Error", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Kudos Awarded", - "schema": { - "$ref": "#/definitions/KudosAwarded" - } - } - }, - "summary": "Awards Kudos to registed user", - "description": "This API can only be used through privileged access.", - "operationId": "post_award_kudos", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "required": true, - "description": "The sending user's API key." - }, - { - "name": "payload", - "required": true, - "in": "body", - "schema": { - "type": "object", - "properties": { - "username": { - "type": "string" - }, - "amount": { - "type": "integer" - } - } - } - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/kudos/kai/{user_id}": { - "parameters": [ - { - "name": "user_id", - "in": "path", - "required": true, - "type": "string" - } - ], - "post": { - "responses": { - "200": { - "description": "Success" - } - }, - "summary": "Receives kudos from the KoboldAI Horde", - "operationId": "post_kobold_kudos_transfer", - "parameters": [ - { - "name": "payload", - "required": true, - "in": "body", - "schema": { - "type": "object", - "properties": { - "kai_id": { - "type": "integer" - }, - "kudos_amount": { - "type": "integer" - }, - "trusted": { - "type": "boolean" - } - } - } - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/kudos/transfer": { - "post": { - "responses": { - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "400": { - "description": "Validation Error", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Kudos Transferred", - "schema": { - "$ref": "#/definitions/KudosTransferred" - } - } - }, - "summary": "Transfer Kudos to another registed user", - "operationId": "post_transfer_kudos", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "required": true, - "description": "The sending user's API key." - }, - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version.", - "default": "unknown:0:unknown" - }, - { - "name": "payload", - "required": true, - "in": "body", - "schema": { - "type": "object", - "properties": { - "username": { - "type": "string" - }, - "amount": { - "type": "integer" - } - } - } - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/operations/ipaddr": { - "delete": { - "responses": { - "403": { - "description": "Access Denied", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "400": { - "description": "Validation Error", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Operation Completed", - "schema": { - "$ref": "#/definitions/SimpleResponse" - } - } - }, - "summary": "Remove an IP from timeout", - "description": "Only usable by horde moderators", - "operationId": "delete_operations_ip", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "required": true, - "description": "A mod API key." - }, - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version.", - "default": "unknown:0:unknown" - }, - { - "name": "payload", - "required": true, - "in": "body", - "schema": { - "$ref": "#/definitions/DeleteTimeoutIPInput" - } - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/sharedkeys": { - "put": { - "responses": { - "404": { - "description": "Shared Key Not Found", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "403": { - "description": "Access Denied", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "400": { - "description": "Validation Error", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "SharedKey Details", - "schema": { - "$ref": "#/definitions/SharedKeyDetails" - } - } - }, - "summary": "Create a new SharedKey for this user", - "operationId": "put_shared_key", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "required": true, - "description": "User API key." - }, - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version.", - "default": "unknown:0:unknown" - }, - { - "name": "payload", - "required": true, - "in": "body", - "schema": { - "$ref": "#/definitions/SharedKeyInput" - } - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/sharedkeys/{sharedkey_id}": { - "parameters": [ - { - "name": "sharedkey_id", - "in": "path", - "required": true, - "type": "string" - } - ], - "get": { - "responses": { - "404": { - "description": "Shared Key Not Found", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Shared Key Details", - "schema": { - "$ref": "#/definitions/SharedKeyDetails" - } - } - }, - "summary": "Get details about an existing Shared Key for this user", - "operationId": "get_shared_key_single", - "parameters": [ - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version.", - "default": "unknown:0:unknown" - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - }, - "delete": { - "responses": { - "404": { - "description": "Shared Key Not Found", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Shared Key Deleted", - "schema": { - "$ref": "#/definitions/SimpleResponse" - } - } - }, - "summary": "Delete an existing SharedKey for this user", - "operationId": "delete_shared_key_single", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "required": true, - "description": "User API key." - }, - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version.", - "default": "unknown:0:unknown" - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - }, - "patch": { - "responses": { - "404": { - "description": "Shared Key Not Found", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "403": { - "description": "Access Denied", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "400": { - "description": "Validation Error", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Shared Key Details", - "schema": { - "$ref": "#/definitions/SharedKeyDetails" - } - } - }, - "summary": "Modify an existing Shared Key", - "operationId": "patch_shared_key_single", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "required": true, - "description": "User API key." - }, - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version", - "default": "unknown:0:unknown" - }, - { - "name": "payload", - "required": true, - "in": "body", - "schema": { - "$ref": "#/definitions/SharedKeyInput" - } - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/stats/img/models": { - "get": { - "responses": { - "200": { - "description": "Horde generated images statistics per model", - "schema": { - "$ref": "#/definitions/ImgModelStats" - } - } - }, - "summary": "Details how many images were generated per model for the past day, month and total", - "operationId": "get_image_horde_stats_models", - "parameters": [ - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version", - "default": "unknown:0:unknown" - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/stats/img/totals": { - "get": { - "responses": { - "200": { - "description": "Horde generated images statistics", - "schema": { - "$ref": "#/definitions/StatsImgTotals" - } - } - }, - "summary": "Details how many images have been generated in the past minux,hour,day,month and total", - "description": "Also shows the amount of pixelsteps for the same timeframe.", - "operationId": "get_image_horde_stats_totals", - "parameters": [ - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version", - "default": "unknown:0:unknown" - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/stats/text/models": { - "get": { - "responses": { - "200": { - "description": "Horde generated text statistics per model", - "schema": { - "$ref": "#/definitions/TxtModelStats" - } - } - }, - "summary": "Details how many texts were generated per model for the past day, month and total", - "operationId": "get_text_horde_stats_models", - "parameters": [ - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version", - "default": "unknown:0:unknown" - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/stats/text/totals": { - "get": { - "responses": { - "200": { - "description": "Horde generated text statistics", - "schema": { - "$ref": "#/definitions/StatsTxtTotals" - } - } - }, - "summary": "Details how many texts have been generated in the past minux,hour,day,month and total", - "description": "Also shows the amount of pixelsteps for the same timeframe.", - "operationId": "get_text_horde_stats_totals", - "parameters": [ - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version", - "default": "unknown:0:unknown" - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/status/heartbeat": { - "get": { - "responses": { - "200": { - "description": "Success" - } - }, - "summary": "If this loads, this node is available", - "operationId": "get_heartbeat", - "parameters": [ - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version.", - "default": "unknown:0:unknown" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/status/models": { - "get": { - "responses": { - "200": { - "description": "List All Active Models", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/ActiveModel" - } - } - } - }, - "summary": "Returns a list of models active currently in this horde", - "operationId": "get_models", - "parameters": [ - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version.", - "default": "unknown:0:unknown" - }, - { - "name": "type", - "in": "query", - "type": "string", - "description": "Filter the models by type (image or text).", - "default": "image" - }, - { - "name": "min_count", - "in": "query", - "type": "integer", - "description": "Filter only models that have at least this amount of threads serving." - }, - { - "name": "max_count", - "in": "query", - "type": "integer", - "description": "Filter the models that have at most this amount of threads serving." - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/status/models/{model_name}": { - "parameters": [ - { - "name": "model_name", - "in": "path", - "required": true, - "type": "string" - } - ], - "get": { - "responses": { - "200": { - "description": "Lists specific model stats", - "schema": { - "$ref": "#/definitions/ActiveModel" - } - } - }, - "summary": "Returns all the statistics of a specific model in this horde", - "operationId": "get_model_single", - "parameters": [ - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version.", - "default": "unknown:0:unknown" - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/status/modes": { - "put": { - "responses": { - "403": { - "description": "Access Denied", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Maintenance Mode Set", - "schema": { - "$ref": "#/definitions/HordeModes" - } - } - }, - "summary": "Change Horde Modes", - "description": "Endpoint for admins to (un)set the horde into maintenance, invite_only or raid modes.", - "operationId": "put_horde_modes", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "required": true, - "description": "The Admin API key." - }, - { - "name": "payload", - "required": true, - "in": "body", - "schema": { - "type": "object", - "properties": { - "maintenance": { - "type": "boolean" - }, - "invite_only": { - "type": "boolean" - }, - "raid": { - "type": "boolean" - } - } - } - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - }, - "get": { - "responses": { - "200": { - "description": "Horde Maintenance", - "schema": { - "$ref": "#/definitions/HordeModes" - } - } - }, - "summary": "Horde Maintenance Mode Status", - "description": "Use this endpoint to quicky determine if this horde is in maintenance, invite_only or raid mode.", - "operationId": "get_horde_modes", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "description": "The Admin or Owner API key." - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/status/news": { - "get": { - "responses": { - "200": { - "description": "Horde News", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/Newspiece" - } - } - } - }, - "summary": "Read the latest happenings on the horde", - "operationId": "get_horde_news", - "parameters": [ - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version.", - "default": "unknown:0:unknown" - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/status/performance": { - "get": { - "responses": { - "200": { - "description": "Horde Performance", - "schema": { - "$ref": "#/definitions/HordePerformance" - } - } - }, - "summary": "Details about the current performance of this Horde", - "operationId": "get_horde_load", - "parameters": [ - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version.", - "default": "unknown:0:unknown" - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/teams": { - "post": { - "responses": { - "403": { - "description": "Access Denied", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "400": { - "description": "Validation Error", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Create Team", - "schema": { - "$ref": "#/definitions/ModifyTeam" - } - } - }, - "summary": "Create a new team", - "description": "Only trusted users can create new teams.", - "operationId": "post_teams", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "required": true, - "description": "A User API key." - }, - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version.", - "default": "unknown:0:unknown" - }, - { - "name": "payload", - "required": true, - "in": "body", - "schema": { - "$ref": "#/definitions/CreateTeamInput" - } - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - }, - "get": { - "responses": { - "200": { - "description": "Teams List", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/TeamDetails" - } - } - } - }, - "summary": "A List with the details of all teams", - "operationId": "get_teams", - "parameters": [ - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version.", - "default": "unknown:0:unknown" - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/teams/{team_id}": { - "parameters": [ - { - "name": "team_id", - "in": "path", - "required": true, - "type": "string" - } - ], - "get": { - "responses": { - "404": { - "description": "Team Not Found", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "403": { - "description": "Access Denied", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Team Details", - "schema": { - "$ref": "#/definitions/TeamDetails" - } - } - }, - "summary": "Details of a worker Team", - "operationId": "get_team_single", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "description": "The Moderator or Owner API key." - }, - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version.", - "default": "unknown:0:unknown" - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - }, - "delete": { - "responses": { - "404": { - "description": "Team Not Found", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "403": { - "description": "Access Denied", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Delete Team", - "schema": { - "$ref": "#/definitions/DeletedTeam" - } - } - }, - "summary": "Delete the team entry", - "description": "Only the team's creator or a horde moderator can use this endpoint.\nThis action is unrecoverable!", - "operationId": "delete_team_single", - "parameters": [ - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version.", - "default": "unknown:0:unknown" - }, - { - "name": "apikey", - "in": "header", - "type": "string", - "description": "The Moderator or Owner API key." - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - }, - "patch": { - "responses": { - "404": { - "description": "Team Not Found", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "403": { - "description": "Access Denied", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "400": { - "description": "Validation Error", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Modify Team", - "schema": { - "$ref": "#/definitions/ModifyTeam" - } - } - }, - "summary": "Update a Team's information", - "operationId": "patch_team_single", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "description": "The Moderator or Creator API key." - }, - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version.", - "default": "unknown:0:unknown" - }, - { - "name": "payload", - "required": true, - "in": "body", - "schema": { - "$ref": "#/definitions/ModifyTeamInput" - } - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/users": { - "get": { - "responses": { - "200": { - "description": "Users List", - "schema": { - "$ref": "#/definitions/UserDetails" - } - } - }, - "summary": "A List with the details and statistic of all registered users", - "operationId": "get_users", - "parameters": [ - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version.", - "default": "unknown:0:unknown" - }, - { - "name": "page", - "in": "query", - "type": "integer", - "description": "Which page of results to return. Each page has 25 users.", - "default": 1 - }, - { - "name": "sort", - "in": "query", - "type": "string", - "description": "How to sort the returned list.", - "default": "kudos" - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/users/{user_id}": { - "parameters": [ - { - "name": "user_id", - "in": "path", - "required": true, - "type": "string" - } - ], - "put": { - "responses": { - "404": { - "description": "Worker Not Found", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "403": { - "description": "Access Denied", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "400": { - "description": "Validation Error", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Modify User", - "schema": { - "$ref": "#/definitions/ModifyUser" - } - } - }, - "summary": "Endpoint for horde admins to perform operations on users", - "operationId": "put_user_single", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "required": true, - "description": "The Admin API ." - }, - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version.", - "default": "unknown:0:unknown" - }, - { - "name": "payload", - "required": true, - "in": "body", - "schema": { - "$ref": "#/definitions/ModifyUserInput" - } - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - }, - "get": { - "responses": { - "404": { - "description": "User Not Found", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "User Details", - "schema": { - "$ref": "#/definitions/UserDetails" - } - } - }, - "summary": "Details and statistics about a specific user", - "operationId": "get_user_single", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "description": "The Admin, Mod or Owner API key." - }, - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version.", - "default": "unknown:0:unknown" - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/workers": { - "get": { - "responses": { - "200": { - "description": "Workers List", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/WorkerDetails" - } - } - } - }, - "summary": "A List with the details of all registered and active workers", - "operationId": "get_workers", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "description": "A Moderator API key." - }, - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version.", - "default": "unknown:0:unknown" - }, - { - "name": "type", - "in": "query", - "type": "string", - "description": "Filter the workers by type (image, text or interrogation)." - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - }, - "/v2/workers/{worker_id}": { - "parameters": [ - { - "name": "worker_id", - "in": "path", - "required": true, - "type": "string" - } - ], - "put": { - "responses": { - "404": { - "description": "Worker Not Found", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "403": { - "description": "Access Denied", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "400": { - "description": "Validation Error", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Modify Worker", - "schema": { - "$ref": "#/definitions/ModifyWorker" - } - } - }, - "summary": "Put the worker into maintenance or pause mode", - "description": "Maintenance can be set by the owner of the serve or an admin. \nWhen in maintenance, the worker will receive a 503 request when trying to retrieve new requests. Use this to avoid disconnecting your worker in the middle of a generation\nPaused can be set only by the admins of this Horde.\nWhen in paused mode, the worker will not be given any requests to generate.", - "operationId": "put_worker_single", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "required": true, - "description": "The Moderator or Owner API key." - }, - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version.", - "default": "unknown:0:unknown" - }, - { - "name": "payload", - "required": true, - "in": "body", - "schema": { - "$ref": "#/definitions/ModifyWorkerInput" - } - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - }, - "get": { - "responses": { - "404": { - "description": "Worker Not Found", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "403": { - "description": "Access Denied", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Worker Details", - "schema": { - "$ref": "#/definitions/WorkerDetails" - } - } - }, - "summary": "Details of a registered worker", - "description": "Can retrieve the details of a worker even if inactive\n(A worker is considered inactive if it has not checked in for 5 minutes)", - "operationId": "get_worker_single", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "description": "The Moderator or Owner API key." - }, - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version.", - "default": "unknown:0:unknown" - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - }, - "delete": { - "responses": { - "404": { - "description": "Worker Not Found", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "403": { - "description": "Access Denied", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "401": { - "description": "Invalid API Key", - "schema": { - "$ref": "#/definitions/RequestError" - } - }, - "200": { - "description": "Delete Worker", - "schema": { - "$ref": "#/definitions/DeletedWorker" - } - } - }, - "summary": "Delete the worker entry", - "description": "This will delete the worker and their statistics. Will not affect the kudos generated by that worker for their owner.\nOnly the worker's owner and an admin can use this endpoint.\nThis action is unrecoverable!", - "operationId": "delete_worker_single", - "parameters": [ - { - "name": "apikey", - "in": "header", - "type": "string", - "description": "The Moderator or Owner API key." - }, - { - "name": "Client-Agent", - "in": "header", - "type": "string", - "description": "The client name and version.", - "default": "unknown:0:unknown" - }, - { - "name": "X-Fields", - "in": "header", - "type": "string", - "format": "mask", - "description": "An optional fields mask" - } - ], - "tags": [ - "v2" - ] - } - } - }, - "info": { - "title": "AI Horde", - "version": "2.0", - "description": "The API documentation for the AI Horde" - }, - "produces": [ - "application/json" - ], - "consumes": [ - "application/json" - ], - "tags": [ - { - "name": "v2", - "description": "API Version 2" - } - ], - "definitions": { - "GenerationInputStable": { - "required": [ - "prompt" - ], - "properties": { - "prompt": { - "type": "string", - "description": "The prompt which will be sent to Stable Diffusion to generate an image.", - "minLength": 1 - }, - "params": { - "$ref": "#/definitions/ModelGenerationInputStable" - }, - "nsfw": { - "type": "boolean", - "description": "Set to true if this request is NSFW. This will skip workers which censor images.", - "default": false - }, - "trusted_workers": { - "type": "boolean", - "description": "When true, only trusted workers will serve this request. When False, Evaluating workers will also be used which can increase speed but adds more risk!", - "default": false - }, - "slow_workers": { - "type": "boolean", - "description": "When True, allows slower workers to pick up this request. Disabling this incurs an extra kudos cost.", - "default": true - }, - "censor_nsfw": { - "type": "boolean", - "description": "If the request is SFW, and the worker accidentally generates NSFW, it will send back a censored image.", - "default": false - }, - "workers": { - "type": "array", - "items": { - "type": "string", - "description": "Specify up to 5 workers which are allowed to service this request." - } - }, - "worker_blacklist": { - "type": "boolean", - "description": "If true, the worker list will be treated as a blacklist instead of a whitelist.", - "default": false - }, - "models": { - "type": "array", - "items": { - "type": "string", - "description": "Specify which models are allowed to be used for this request." - } - }, - "source_image": { - "type": "string", - "description": "The Base64-encoded webp to use for img2img." - }, - "source_processing": { - "type": "string", - "description": "If source_image is provided, specifies how to process it.", - "default": "img2img", - "example": "img2img", - "enum": [ - "img2img", - "inpainting", - "outpainting" - ] - }, - "source_mask": { - "type": "string", - "description": "If source_processing is set to 'inpainting' or 'outpainting', this parameter can be optionally provided as the Base64-encoded webp mask of the areas to inpaint. If this arg is not passed, the inpainting/outpainting mask has to be embedded as alpha channel." - }, - "r2": { - "type": "boolean", - "description": "If True, the image will be sent via cloudflare r2 download link.", - "default": true - }, - "shared": { - "type": "boolean", - "description": "If True, The image will be shared with LAION for improving their dataset. This will also reduce your kudos consumption by 2. For anonymous users, this is always True.", - "default": false - }, - "replacement_filter": { - "type": "boolean", - "description": "If enabled, suspicious prompts are sanitized through a string replacement filter instead.", - "default": true - }, - "dry_run": { - "type": "boolean", - "description": "When false, the endpoint will simply return the cost of the request in kudos and exit.", - "default": false - } - }, - "type": "object" - }, - "ModelGenerationInputStable": { - "allOf": [ - { - "$ref": "#/definitions/ModelPayloadRootStable" - }, - { - "properties": { - "steps": { - "type": "integer", - "default": 30, - "minimum": 1, - "maximum": 500 - }, - "n": { - "type": "integer", - "description": "The amount of images to generate.", - "default": 1, - "minimum": 1, - "maximum": 20 - } - }, - "type": "object" - } - ] - }, - "ModelPayloadRootStable": { - "properties": { - "sampler_name": { - "type": "string", - "default": "k_euler_a", - "example": "k_lms", - "enum": [ - "k_lms", - "k_heun", - "k_euler", - "k_euler_a", - "k_dpm_2", - "k_dpm_2_a", - "k_dpm_fast", - "k_dpm_adaptive", - "k_dpmpp_2s_a", - "k_dpmpp_2m", - "dpmsolver", - "k_dpmpp_sde", - "DDIM" - ] - }, - "cfg_scale": { - "type": "number", - "default": 7.5, - "minimum": 0, - "maximum": 100, - "multipleOf": 0.5 - }, - "denoising_strength": { - "type": "number", - "example": 0.75, - "minimum": 0.01, - "maximum": 1.0 - }, - "seed": { - "type": "string", - "description": "The seed to use to generate this request. You can pass text as well as numbers.", - "example": "The little seed that could" - }, - "height": { - "type": "integer", - "description": "The height of the image to generate.", - "default": 512, - "minimum": 64, - "maximum": 3072, - "multipleOf": 64 - }, - "width": { - "type": "integer", - "description": "The width of the image to generate.", - "default": 512, - "minimum": 64, - "maximum": 3072, - "multipleOf": 64 - }, - "seed_variation": { - "type": "integer", - "description": "If passed with multiple n, the provided seed will be incremented every time by this value.", - "example": 1, - "minimum": 1, - "maximum": 1000 - }, - "post_processing": { - "type": "array", - "uniqueItems": true, - "items": { - "type": "string", - "description": "The list of post-processors to apply to the image, in the order to be applied.", - "example": "GFPGAN", - "enum": [ - "GFPGAN", - "RealESRGAN_x4plus", - "RealESRGAN_x2plus", - "RealESRGAN_x4plus_anime_6B", - "NMKD_Siax", - "4x_AnimeSharp", - "CodeFormers", - "strip_background" - ] - } - }, - "karras": { - "type": "boolean", - "description": "Set to True to enable karras noise scheduling tweaks.", - "default": false - }, - "tiling": { - "type": "boolean", - "description": "Set to True to create images that stitch together seamlessly.", - "default": false - }, - "hires_fix": { - "type": "boolean", - "description": "Set to True to process the image at base resolution before upscaling and re-processing.", - "default": false - }, - "clip_skip": { - "type": "integer", - "description": "The number of CLIP language processor layers to skip.", - "example": 1, - "minimum": 1, - "maximum": 12 - }, - "control_type": { - "type": "string", - "example": "canny", - "enum": [ - "canny", - "hed", - "depth", - "normal", - "openpose", - "seg", - "scribble", - "fakescribbles", - "hough" - ] - }, - "image_is_control": { - "type": "boolean", - "description": "Set to True if the image submitted is a pre-generated control map for ControlNet use.", - "default": false - }, - "return_control_map": { - "type": "boolean", - "description": "Set to True if you want the ControlNet map returned instead of a generated image.", - "default": false - }, - "facefixer_strength": { - "type": "number", - "example": 0.75, - "minimum": 0, - "maximum": 1.0 - }, - "loras": { - "type": "array", - "items": { - "$ref": "#/definitions/ModelPayloadLorasStable" - } - }, - "special": { - "$ref": "#/definitions/ModelSpecialPayloadStable" - } - }, - "type": "object" - }, - "ModelPayloadLorasStable": { - "required": [ - "name" - ], - "properties": { - "name": { - "type": "string", - "description": "The exact name of the LoRa.", - "example": "GlowingRunesAIV6", - "minLength": 1, - "maxLength": 255 - }, - "model": { - "type": "number", - "description": "The strength of the LoRa to apply to the SD model.", - "default": 1.0, - "minimum": 0.0, - "maximum": 5.0 - }, - "clip": { - "type": "number", - "description": "The strength of the LoRa to apply to the clip model.", - "default": 1.0, - "minimum": 0.0, - "maximum": 5.0 - }, - "inject_trigger": { - "type": "string", - "description": "If set, will try to discover a trigger for this LoRa which matches or is similar to this string and inject it into the prompt. If 'any' is specified it will be pick the first trigger.", - "minLength": 1, - "maxLength": 30 - } - }, - "type": "object" - }, - "ModelSpecialPayloadStable": { - "properties": { - "*": { - "type": "object", - "additionalProperties": { - "type": "object" - } - } - }, - "type": "object" - }, - "RequestError": { - "properties": { - "message": { - "type": "string", - "description": "The error message for this status code." - } - }, - "type": "object" - }, - "RequestAsync": { - "properties": { - "id": { - "type": "string", - "description": "The UUID of the request. Use this to retrieve the request status in the future." - }, - "kudos": { - "type": "integer", - "description": "The expected kudos consumption for this request." - }, - "message": { - "type": "string", - "description": "Any extra information from the horde about this request." - } - }, - "type": "object" - }, - "RequestStatusStable": { - "allOf": [ - { - "$ref": "#/definitions/RequestStatusCheck" - }, - { - "properties": { - "generations": { - "type": "array", - "items": { - "$ref": "#/definitions/GenerationStable" - } - }, - "shared": { - "type": "boolean", - "description": "If True, These images have been shared with LAION." - } - }, - "type": "object" - } - ] - }, - "RequestStatusCheck": { - "properties": { - "finished": { - "type": "integer", - "description": "The amount of finished jobs in this request." - }, - "processing": { - "type": "integer", - "description": "The amount of still processing jobs in this request." - }, - "restarted": { - "type": "integer", - "description": "The amount of jobs that timed out and had to be restarted or were reported as failed by a worker." - }, - "waiting": { - "type": "integer", - "description": "The amount of jobs waiting to be picked up by a worker." - }, - "done": { - "type": "boolean", - "description": "True when all jobs in this request are done. Else False." - }, - "faulted": { - "type": "boolean", - "description": "True when this request caused an internal server error and could not be completed.", - "default": false - }, - "wait_time": { - "type": "integer", - "description": "The expected amount to wait (in seconds) to generate all jobs in this request." - }, - "queue_position": { - "type": "integer", - "description": "The position in the requests queue. This position is determined by relative Kudos amounts." - }, - "kudos": { - "type": "number", - "description": "The amount of total Kudos this request has consumed until now." - }, - "is_possible": { - "type": "boolean", - "description": "If False, this request will not be able to be completed with the pool of workers currently available.", - "default": true - } - }, - "type": "object" - }, - "GenerationStable": { - "allOf": [ - { - "$ref": "#/definitions/Generation" - }, - { - "properties": { - "img": { - "type": "string", - "title": "Generated Image", - "description": "The generated image as a Base64-encoded .webp file." - }, - "seed": { - "type": "string", - "title": "Generation Seed", - "description": "The seed which generated this image." - }, - "id": { - "type": "string", - "title": "Generation ID", - "description": "The ID for this image." - }, - "censored": { - "type": "boolean", - "description": "When true this image has been censored by the worker's safety filter." - } - }, - "type": "object" - } - ] - }, - "Generation": { - "required": [ - "state" - ], - "properties": { - "worker_id": { - "type": "string", - "title": "Worker ID", - "description": "The UUID of the worker which generated this image." - }, - "worker_name": { - "type": "string", - "title": "Worker Name", - "description": "The name of the worker which generated this image." - }, - "model": { - "type": "string", - "title": "Generation Model", - "description": "The model which generated this image." - }, - "state": { - "type": "string", - "title": "Generation State", - "description": "The state of this generation.", - "default": "ok", - "example": "ok", - "enum": [ - "ok", - "censored" - ] - } - }, - "type": "object" - }, - "AestheticsPayload": { - "properties": { - "best": { - "type": "string", - "description": "The UUID of the best image in this generation batch (only used when 2+ images generated). If 2+ aesthetic ratings are also provided, then they take precedence if they're not tied.", - "example": "6038971e-f0b0-4fdd-a3bb-148f561f815e", - "minLength": 36, - "maxLength": 36 - }, - "ratings": { - "type": "array", - "items": { - "$ref": "#/definitions/AestheticRating" - } - } - }, - "type": "object" - }, - "AestheticRating": { - "required": [ - "id", - "rating" - ], - "properties": { - "id": { - "type": "string", - "description": "The UUID of image being rated.", - "example": "6038971e-f0b0-4fdd-a3bb-148f561f815e", - "minLength": 36, - "maxLength": 36 - }, - "rating": { - "type": "integer", - "description": "The aesthetic rating 1-10 for this image.", - "minimum": 1, - "maximum": 10 - }, - "artifacts": { - "type": "integer", - "description": "The artifacts rating for this image.\n0 for flawless generation that perfectly fits to the prompt.\n1 for small, hardly recognizable flaws.\n2 small flaws that can easily be spotted, but don not harm the aesthetic experience.\n3 for flaws that look obviously wrong, but only mildly harm the aesthetic experience.\n4 for flaws that look obviously wrong & significantly harm the aesthetic experience.\n5 for flaws that make the image look like total garbage.", - "example": 1, - "minimum": 0, - "maximum": 5 - } - }, - "type": "object" - }, - "GenerationSubmitted": { - "properties": { - "reward": { - "type": "number", - "description": "The amount of kudos gained for submitting this request.", - "example": 10.0 - } - }, - "type": "object" - }, - "PopInputStable": { - "allOf": [ - { - "$ref": "#/definitions/PopInput" - }, - { - "properties": { - "max_pixels": { - "type": "integer", - "description": "The maximum amount of pixels this worker can generate.", - "default": 262144 - }, - "blacklist": { - "type": "array", - "items": { - "type": "string", - "description": "Words which, when detected will refuste to pick up any jobs." - } - }, - "allow_img2img": { - "type": "boolean", - "description": "If True, this worker will pick up img2img requests.", - "default": true - }, - "allow_painting": { - "type": "boolean", - "description": "If True, this worker will pick up inpainting/outpainting requests.", - "default": true - }, - "allow_unsafe_ipaddr": { - "type": "boolean", - "description": "If True, this worker will pick up img2img requests coming from clients with an unsafe IP.", - "default": true - }, - "allow_post_processing": { - "type": "boolean", - "description": "If True, this worker will pick up requests requesting post-processing.", - "default": true - }, - "allow_controlnet": { - "type": "boolean", - "description": "If True, this worker will pick up requests requesting ControlNet.", - "default": true - }, - "allow_lora": { - "type": "boolean", - "description": "If True, this worker will pick up requests requesting LoRas.", - "default": true - } - }, - "type": "object" - } - ] - }, - "PopInput": { - "properties": { - "name": { - "type": "string", - "description": "The Name of the Worker." - }, - "priority_usernames": { - "type": "array", - "items": { - "type": "string", - "description": "Users with priority to use this worker." - } - }, - "nsfw": { - "type": "boolean", - "description": "Whether this worker can generate NSFW requests or not.", - "default": false - }, - "models": { - "type": "array", - "items": { - "type": "string", - "description": "Which models this worker is serving.", - "minLength": 3, - "maxLength": 255 - } - }, - "bridge_version": { - "type": "integer", - "description": "The version of the bridge used by this worker.", - "default": 1 - }, - "bridge_agent": { - "type": "string", - "description": "The worker name, version and website.", - "default": "unknown:0:unknown", - "example": "AI Horde Worker:11:https://github.com/db0/AI-Horde-Worker", - "maxLength": 1000 - }, - "threads": { - "type": "integer", - "description": "How many threads this worker is running. This is used to accurately the current power available in the horde.", - "default": 1, - "minimum": 1, - "maximum": 10 - }, - "require_upfront_kudos": { - "type": "boolean", - "description": "If True, this worker will only pick up requests where the owner has the required kudos to consume already available.", - "default": false, - "example": false - } - }, - "type": "object" - }, - "GenerationPayloadStable": { - "properties": { - "payload": { - "$ref": "#/definitions/ModelPayloadStable" - }, - "id": { - "type": "string", - "description": "The UUID for this image generation." - }, - "skipped": { - "$ref": "#/definitions/NoValidRequestFoundStable" - }, - "model": { - "type": "string", - "description": "Which of the available models to use for this request." - }, - "source_image": { - "type": "string", - "description": "The Base64-encoded webp to use for img2img." - }, - "source_processing": { - "type": "string", - "description": "If source_image is provided, specifies how to process it.", - "default": "img2img", - "example": "img2img", - "enum": [ - "img2img", - "inpainting", - "outpainting" - ] - }, - "source_mask": { - "type": "string", - "description": "If img_processing is set to 'inpainting' or 'outpainting', this parameter can be optionally provided as the mask of the areas to inpaint. If this arg is not passed, the inpainting/outpainting mask has to be embedded as alpha channel." - }, - "r2_upload": { - "type": "string", - "description": "The r2 upload link to use to upload this image." - } - }, - "type": "object" - }, - "ModelPayloadStable": { - "allOf": [ - { - "$ref": "#/definitions/ModelPayloadRootStable" - }, - { - "properties": { - "prompt": { - "type": "string", - "description": "The prompt which will be sent to Stable Diffusion to generate an image." - }, - "ddim_steps": { - "type": "integer", - "default": 30 - }, - "n_iter": { - "type": "integer", - "description": "The amount of images to generate.", - "default": 1 - }, - "use_nsfw_censor": { - "type": "boolean", - "description": "When true will apply NSFW censoring model on the generation." - } - }, - "type": "object" - } - ] - }, - "NoValidRequestFoundStable": { - "allOf": [ - { - "$ref": "#/definitions/NoValidRequestFound" - }, - { - "properties": { - "max_pixels": { - "type": "integer", - "description": "How many waiting requests were skipped because they demanded a higher size than this worker provides." - }, - "unsafe_ip": { - "type": "integer", - "description": "How many waiting requests were skipped because they came from an unsafe IP." - }, - "img2img": { - "type": "integer", - "description": "How many waiting requests were skipped because they requested img2img." - }, - "painting": { - "type": "integer", - "description": "How many waiting requests were skipped because they requested inpainting/outpainting." - }, - "post-processing": { - "type": "integer", - "description": "How many waiting requests were skipped because they requested post-processing." - }, - "lora": { - "type": "integer", - "description": "How many waiting requests were skipped because they requested loras." - }, - "controlnet": { - "type": "integer", - "description": "How many waiting requests were skipped because they requested a controlnet." - } - }, - "type": "object" - } - ] - }, - "NoValidRequestFound": { - "properties": { - "worker_id": { - "type": "integer", - "description": "How many waiting requests were skipped because they demanded a specific worker.", - "minimum": 0 - }, - "performance": { - "type": "integer", - "description": "How many waiting requests were skipped because they required higher performance.", - "minimum": 0 - }, - "nsfw": { - "type": "integer", - "description": "How many waiting requests were skipped because they demanded a nsfw generation which this worker does not provide.", - "minimum": 0 - }, - "blacklist": { - "type": "integer", - "description": "How many waiting requests were skipped because they demanded a generation with a word that this worker does not accept.", - "minimum": 0 - }, - "untrusted": { - "type": "integer", - "description": "How many waiting requests were skipped because they demanded a trusted worker which this worker is not.", - "minimum": 0 - }, - "models": { - "type": "integer", - "description": "How many waiting requests were skipped because they demanded a different model than what this worker provides.", - "example": 0, - "minimum": 0 - }, - "bridge_version": { - "type": "integer", - "description": "How many waiting requests were skipped because they require a higher version of the bridge than this worker is running (upgrade if you see this in your skipped list).", - "example": 0, - "minimum": 0 - }, - "kudos": { - "type": "integer", - "description": "How many waiting requests were skipped because the user didn't have enough kudos when this worker requires upfront kudos." - } - }, - "type": "object" - }, - "SubmitInputStable": { - "allOf": [ - { - "$ref": "#/definitions/SubmitInput" - }, - { - "required": [ - "seed" - ], - "properties": { - "seed": { - "type": "integer", - "description": "The seed for this generation." - }, - "censored": { - "type": "boolean", - "description": "If True, this resulting image has been censored.", - "default": false - } - }, - "type": "object" - } - ] - }, - "SubmitInput": { - "required": [ - "id" - ], - "properties": { - "id": { - "type": "string", - "description": "The UUID of this generation.", - "example": "00000000-0000-0000-0000-000000000000" - }, - "generation": { - "type": "string", - "description": "R2 result was uploaded to R2, else the string of the result.", - "example": "R2" - }, - "state": { - "type": "string", - "title": "Generation State", - "description": "The state of this generation.", - "default": "ok", - "example": "ok", - "enum": [ - "ok", - "censored", - "faulted", - "csam" - ] - } - }, - "type": "object" - }, - "GenerationInputKobold": { - "properties": { - "prompt": { - "type": "string", - "description": "The prompt which will be sent to KoboldAI to generate text." - }, - "params": { - "$ref": "#/definitions/ModelGenerationInputKobold" - }, - "softprompt": { - "type": "string", - "description": "Specify which softpompt needs to be used to service this request.", - "minLength": 1 - }, - "trusted_workers": { - "type": "boolean", - "description": "When true, only trusted workers will serve this request. When False, Evaluating workers will also be used which can increase speed but adds more risk!", - "default": false - }, - "slow_workers": { - "type": "boolean", - "description": "When True, allows slower workers to pick up this request. Disabling this incurs an extra kudos cost.", - "default": true - }, - "workers": { - "type": "array", - "items": { - "type": "string", - "description": "Specify up to 5 workers which are allowed to service this request." - } - }, - "worker_blacklist": { - "type": "boolean", - "description": "If true, the worker list will be treated as a blacklist instead of a whitelist.", - "default": false - }, - "models": { - "type": "array", - "items": { - "type": "string", - "description": "Specify which models are allowed to be used for this request." - } - }, - "dry_run": { - "type": "boolean", - "description": "When false, the endpoint will simply return the cost of the request in kudos and exit.", - "default": false - } - }, - "type": "object" - }, - "ModelGenerationInputKobold": { - "allOf": [ - { - "$ref": "#/definitions/ModelPayloadRootKobold" - }, - { - "properties": {}, - "type": "object" - } - ] - }, - "ModelPayloadRootKobold": { - "properties": { - "n": { - "type": "integer", - "example": 1, - "minimum": 1, - "maximum": 20 - }, - "frmtadsnsp": { - "type": "boolean", - "description": "Input formatting option. When enabled, adds a leading space to your input if there is no trailing whitespace at the end of the previous action.", - "example": false - }, - "frmtrmblln": { - "type": "boolean", - "description": "Output formatting option. When enabled, replaces all occurrences of two or more consecutive newlines in the output with one newline.", - "example": false - }, - "frmtrmspch": { - "type": "boolean", - "description": "Output formatting option. When enabled, removes #/@%}{+=~|\\^<> from the output.", - "example": false - }, - "frmttriminc": { - "type": "boolean", - "description": "Output formatting option. When enabled, removes some characters from the end of the output such that the output doesn't end in the middle of a sentence. If the output is less than one sentence long, does nothing.", - "example": false - }, - "max_context_length": { - "type": "integer", - "description": "Maximum number of tokens to send to the model.", - "default": 1024, - "minimum": 80 - }, - "max_length": { - "type": "integer", - "description": "Number of tokens to generate.", - "default": 80, - "minimum": 16, - "maximum": 512 - }, - "rep_pen": { - "type": "number", - "description": "Base repetition penalty value.", - "minimum": 1, - "maximum": 3 - }, - "rep_pen_range": { - "type": "integer", - "description": "Repetition penalty range.", - "minimum": 0, - "maximum": 4096 - }, - "rep_pen_slope": { - "type": "number", - "description": "Repetition penalty slope.", - "minimum": 0, - "maximum": 10 - }, - "singleline": { - "type": "boolean", - "description": "Output formatting option. When enabled, removes everything after the first line of the output, including the newline.", - "example": false - }, - "temperature": { - "type": "number", - "description": "Temperature value.", - "minimum": 0, - "maximum": 5.0 - }, - "tfs": { - "type": "number", - "description": "Tail free sampling value.", - "minimum": 0.0, - "maximum": 1.0 - }, - "top_a": { - "type": "number", - "description": "Top-a sampling value.", - "minimum": 0.0, - "maximum": 1.0 - }, - "top_k": { - "type": "integer", - "description": "Top-k sampling value.", - "minimum": 0, - "maximum": 100 - }, - "top_p": { - "type": "number", - "description": "Top-p sampling value.", - "minimum": 0.001, - "maximum": 1.0 - }, - "typical": { - "type": "number", - "description": "Typical sampling value.", - "minimum": 0.0, - "maximum": 1.0 - }, - "sampler_order": { - "type": "array", - "items": { - "type": "integer", - "description": "Array of integers representing the sampler order to be used." - } - } - }, - "type": "object" - }, - "RequestStatusKobold": { - "allOf": [ - { - "$ref": "#/definitions/RequestStatusCheck" - }, - { - "properties": { - "generations": { - "type": "array", - "items": { - "$ref": "#/definitions/GenerationKobold" - } - } - }, - "type": "object" - } - ] - }, - "GenerationKobold": { - "allOf": [ - { - "$ref": "#/definitions/Generation" - }, - { - "properties": { - "text": { - "type": "string", - "title": "Generated Text", - "description": "The generated text.", - "minLength": 0 - }, - "seed": { - "type": "integer", - "title": "Generation Seed", - "description": "The seed which generated this text.", - "default": 0 - } - }, - "type": "object" - } - ] - }, - "PopInputKobold": { - "allOf": [ - { - "$ref": "#/definitions/PopInput" - }, - { - "properties": { - "max_length": { - "type": "integer", - "description": "The maximum amount of tokens this worker can generate.", - "default": 512 - }, - "max_context_length": { - "type": "integer", - "description": "The max amount of context to submit to this AI for sampling.", - "default": 2048 - }, - "softprompts": { - "type": "array", - "items": { - "type": "string", - "description": "The available softprompt files on this worker for the currently running model." - } - } - }, - "type": "object" - } - ] - }, - "GenerationPayload": { - "properties": { - "payload": { - "$ref": "#/definitions/ModelPayloadKobold" - }, - "id": { - "type": "string", - "description": "The UUID for this text generation." - }, - "skipped": { - "$ref": "#/definitions/NoValidRequestFoundKobold" - }, - "softprompt": { - "type": "string", - "description": "The soft prompt requested for this generation." - }, - "model": { - "type": "string", - "description": "Which of the available models to use for this request." - } - }, - "type": "object" - }, - "ModelPayloadKobold": { - "allOf": [ - { - "$ref": "#/definitions/ModelPayloadRootKobold" - }, - { - "properties": { - "prompt": { - "type": "string", - "description": "The prompt which will be sent to KoboldAI to generate the text." - } - }, - "type": "object" - } - ] - }, - "NoValidRequestFoundKobold": { - "allOf": [ - { - "$ref": "#/definitions/NoValidRequestFound" - }, - { - "properties": { - "max_context_length": { - "type": "integer", - "description": "How many waiting requests were skipped because they demanded a higher max_context_length than what this worker provides.", - "example": 0 - }, - "max_length": { - "type": "integer", - "description": "How many waiting requests were skipped because they demanded more generated tokens that what this worker can provide.", - "example": 0 - }, - "matching_softprompt": { - "type": "integer", - "description": "How many waiting requests were skipped because they demanded an available soft-prompt which this worker does not have.", - "example": 0 - } - }, - "type": "object" - } - ] - }, - "UserDetails": { - "properties": { - "username": { - "type": "string", - "description": "The user's unique Username. It is a combination of their chosen alias plus their ID." - }, - "id": { - "type": "integer", - "description": "The user unique ID. It is always an integer." - }, - "kudos": { - "type": "number", - "description": "The amount of Kudos this user has. The amount of Kudos determines the priority when requesting image generations." - }, - "evaluating_kudos": { - "type": "number", - "description": "(Privileged) The amount of Evaluating Kudos this untrusted user has from generations and uptime. When this number reaches a prespecified threshold, they automatically become trusted." - }, - "concurrency": { - "type": "integer", - "description": "How many concurrent generations this user may request." - }, - "worker_invited": { - "type": "integer", - "description": "Whether this user has been invited to join a worker to the horde and how many of them. When 0, this user cannot add (new) workers to the horde." - }, - "moderator": { - "type": "boolean", - "description": "This user is a Horde moderator.", - "example": false - }, - "kudos_details": { - "$ref": "#/definitions/UserKudosDetails" - }, - "worker_count": { - "type": "integer", - "description": "How many workers this user has created (active or inactive)." - }, - "worker_ids": { - "type": "array", - "items": { - "type": "string", - "description": "Privileged or public when the user has explicitly allows it to be public.", - "example": "00000000-0000-0000-0000-000000000000" - } - }, - "sharedkey_ids": { - "type": "array", - "items": { - "type": "string", - "description": "(Privileged) The list of shared key IDs created by this user.", - "example": "00000000-0000-0000-0000-000000000000" - } - }, - "monthly_kudos": { - "$ref": "#/definitions/MonthlyKudos" - }, - "trusted": { - "type": "boolean", - "description": "This user is a trusted member of the Horde.", - "example": false - }, - "flagged": { - "type": "boolean", - "description": "This user has been flagged for suspicious activity.", - "example": false - }, - "vpn": { - "type": "boolean", - "description": "(Privileged) This user has been given the VPN role.", - "example": false - }, - "special": { - "type": "boolean", - "description": "(Privileged) This user has been given the Special role.", - "example": false - }, - "suspicious": { - "type": "integer", - "description": "(Privileged) How much suspicion this user has accumulated.", - "example": 0 - }, - "pseudonymous": { - "type": "boolean", - "description": "If true, this user has not registered using an oauth service.", - "example": false - }, - "contact": { - "type": "string", - "description": "(Privileged) Contact details for the horde admins to reach the user in case of emergency.", - "example": "email@example.com" - }, - "account_age": { - "type": "integer", - "description": "How many seconds since this account was created.", - "example": 60 - }, - "usage": { - "$ref": "#/definitions/UsageDetails" - }, - "contributions": { - "$ref": "#/definitions/ContributionsDetails" - }, - "records": { - "$ref": "#/definitions/UserRecords" - } - }, - "type": "object" - }, - "UserKudosDetails": { - "properties": { - "accumulated": { - "type": "number", - "description": "The ammount of Kudos accumulated or used for generating images.", - "default": 0 - }, - "gifted": { - "type": "number", - "description": "The amount of Kudos this user has given to other users.", - "default": 0 - }, - "admin": { - "type": "number", - "description": "The amount of Kudos this user has been given by the Horde admins.", - "default": 0 - }, - "received": { - "type": "number", - "description": "The amount of Kudos this user has been given by other users.", - "default": 0 - }, - "recurring": { - "type": "number", - "description": "The amount of Kudos this user has received from recurring rewards.", - "default": 0 - }, - "awarded": { - "type": "number", - "description": "The amount of Kudos this user has been awarded from things like rating images.", - "default": 0 - } - }, - "type": "object" - }, - "MonthlyKudos": { - "properties": { - "amount": { - "type": "integer", - "description": "How much recurring Kudos this user receives monthly." - }, - "last_received": { - "type": "string", - "format": "date-time", - "description": "Last date this user received monthly Kudos." - } - }, - "type": "object" - }, - "UsageDetails": { - "properties": { - "megapixelsteps": { - "type": "number", - "description": "How many megapixelsteps this user has requested." - }, - "requests": { - "type": "integer", - "description": "How many images this user has requested." - } - }, - "type": "object" - }, - "ContributionsDetails": { - "properties": { - "megapixelsteps": { - "type": "number", - "description": "How many megapixelsteps this user has generated." - }, - "fulfillments": { - "type": "integer", - "description": "How many images this user has generated." - } - }, - "type": "object" - }, - "UserRecords": { - "properties": { - "usage": { - "$ref": "#/definitions/UserThingRecords" - }, - "contribution": { - "$ref": "#/definitions/UserThingRecords" - }, - "fulfillment": { - "$ref": "#/definitions/UserAmountRecords" - }, - "request": { - "$ref": "#/definitions/UserAmountRecords" - } - }, - "type": "object" - }, - "UserThingRecords": { - "properties": { - "megapixelsteps": { - "type": "number", - "description": "How many megapixelsteps this user has generated or requested.", - "default": 0 - }, - "tokens": { - "type": "integer", - "description": "How many token this user has generated or requested.", - "default": 0 - } - }, - "type": "object" - }, - "UserAmountRecords": { - "properties": { - "image": { - "type": "integer", - "description": "How many images this user has generated or requested.", - "default": 0 - }, - "text": { - "type": "integer", - "description": "How many texts this user has generated or requested.", - "default": 0 - }, - "interrogation": { - "type": "integer", - "description": "How many texts this user has generated or requested.", - "default": 0 - } - }, - "type": "object" - }, - "ModifyUserInput": { - "properties": { - "kudos": { - "type": "number", - "description": "The amount of kudos to modify (can be negative)." - }, - "concurrency": { - "type": "integer", - "description": "The amount of concurrent request this user can have.", - "minimum": 0, - "maximum": 100 - }, - "usage_multiplier": { - "type": "number", - "description": "The amount by which to multiply the users kudos consumption.", - "minimum": 0.1, - "maximum": 10 - }, - "worker_invited": { - "type": "integer", - "description": "Set to the amount of workers this user is allowed to join to the horde when in worker invite-only mode." - }, - "moderator": { - "type": "boolean", - "description": "Set to true to make this user a horde moderator.", - "example": false - }, - "public_workers": { - "type": "boolean", - "description": "Set to true to make this user display their worker IDs.", - "example": false - }, - "monthly_kudos": { - "type": "integer", - "description": "When specified, will start assigning the user monthly kudos, starting now!" - }, - "username": { - "type": "string", - "description": "When specified, will change the username. No profanity allowed!", - "minLength": 3, - "maxLength": 100 - }, - "trusted": { - "type": "boolean", - "description": "When set to true,the user and their servers will not be affected by suspicion.", - "example": false - }, - "flagged": { - "type": "boolean", - "description": "When set to true, the user cannot tranfer kudos and all their workers are put into permanent maintenance.", - "example": false - }, - "customizer": { - "type": "boolean", - "description": "When set to true, the user will be able to serve custom Stable Diffusion models which do not exist in the Official AI Horde Model Reference.", - "example": false - }, - "vpn": { - "type": "boolean", - "description": "When set to true, the user will be able to onboard workers behind a VPN. This should be used as a temporary solution until the user is trusted.", - "example": false - }, - "special": { - "type": "boolean", - "description": "When set to true, The user can send special payloads.", - "example": false - }, - "reset_suspicion": { - "type": "boolean", - "description": "Set the user's suspicion back to 0." - }, - "contact": { - "type": "string", - "description": "Contact details for the horde admins to reach the user in case of emergency. This is only visible to horde moderators.", - "example": "email@example.com", - "minLength": 5, - "maxLength": 500 - } - }, - "type": "object" - }, - "ModifyUser": { - "properties": { - "new_kudos": { - "type": "number", - "description": "The new total Kudos this user has after this request." - }, - "concurrency": { - "type": "integer", - "description": "The request concurrency this user has after this request.", - "example": 30 - }, - "usage_multiplier": { - "type": "number", - "description": "Multiplies the amount of kudos lost when generating images.", - "example": 1.0 - }, - "worker_invited": { - "type": "integer", - "description": "Whether this user has been invited to join a worker to the horde and how many of them. When 0, this user cannot add (new) workers to the horde.", - "example": 1 - }, - "moderator": { - "type": "boolean", - "description": "The user's new moderator status.", - "example": false - }, - "public_workers": { - "type": "boolean", - "description": "The user's new public_workers status.", - "example": false - }, - "username": { - "type": "string", - "description": "The user's new username.", - "example": "username#1" - }, - "monthly_kudos": { - "type": "integer", - "description": "The user's new monthly kudos total.", - "example": 0 - }, - "trusted": { - "type": "boolean", - "description": "The user's new trusted status." - }, - "flagged": { - "type": "boolean", - "description": "The user's new flagged status." - }, - "customizer": { - "type": "boolean", - "description": "The user's new customizer status." - }, - "vpn": { - "type": "boolean", - "description": "The user's new vpn status." - }, - "special": { - "type": "boolean", - "description": "The user's new special status." - }, - "new_suspicion": { - "type": "integer", - "description": "The user's new suspiciousness rating." - }, - "contact": { - "type": "string", - "description": "The new contact details.", - "example": "email@example.com" - } - }, - "type": "object" - }, - "SharedKeyInput": { - "properties": { - "kudos": { - "type": "integer", - "description": "The Kudos limit assigned to this key. If -1, then anyone with this key can use an unlimited amount of kudos from this account.", - "default": 5000, - "minimum": -1, - "maximum": 50000000 - }, - "expiry": { - "type": "integer", - "description": "The amount of days after which this key will expire. If -1, this key will not expire.", - "default": -1, - "example": 30, - "minimum": -1 - }, - "name": { - "type": "string", - "description": "A descriptive name for this key.", - "example": "Mutual Aid", - "minLength": 3, - "maxLength": 255 - }, - "max_image_pixels": { - "type": "integer", - "description": "The maximum amount of image pixels this key can generate per job. -1 means unlimited.", - "default": -1, - "minimum": -1, - "maximum": 4194304 - }, - "max_image_steps": { - "type": "integer", - "description": "The maximum amount of image steps this key can use per job. -1 means unlimited.", - "default": -1, - "minimum": -1, - "maximum": 500 - }, - "max_text_tokens": { - "type": "integer", - "description": "The maximum amount of text tokens this key can generate per job. -1 means unlimited.", - "default": -1, - "minimum": -1, - "maximum": 500 - } - }, - "type": "object" - }, - "SharedKeyDetails": { - "properties": { - "id": { - "type": "string", - "description": "The SharedKey ID." - }, - "username": { - "type": "string", - "description": "The owning user's unique Username. It is a combination of their chosen alias plus their ID." - }, - "kudos": { - "type": "integer", - "description": "The Kudos limit assigned to this key." - }, - "expiry": { - "type": "string", - "format": "date-time", - "description": "The date at which this API key will expire." - }, - "utilized": { - "type": "integer", - "description": "How much kudos has been utilized via this shared key until now." - }, - "max_image_pixels": { - "type": "integer", - "description": "The maximum amount of image pixels this key can generate per job. -1 means unlimited." - }, - "max_image_steps": { - "type": "integer", - "description": "The maximum amount of image steps this key can use per job. -1 means unlimited." - }, - "max_text_tokens": { - "type": "integer", - "description": "The maximum amount of text tokens this key can generate per job. -1 means unlimited." - } - }, - "type": "object" - }, - "SimpleResponse": { - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string", - "description": "The result of this operation.", - "default": "OK" - } - }, - "type": "object" - }, - "WorkerDetails": { - "allOf": [ - { - "$ref": "#/definitions/WorkerDetailsLite" - }, - { - "required": [ - "bridge_agent" - ], - "properties": { - "requests_fulfilled": { - "type": "integer", - "description": "How many images this worker has generated." - }, - "kudos_rewards": { - "type": "number", - "description": "How many Kudos this worker has been rewarded in total." - }, - "kudos_details": { - "$ref": "#/definitions/WorkerKudosDetails" - }, - "performance": { - "type": "string", - "description": "The average performance of this worker in human readable form." - }, - "threads": { - "type": "integer", - "description": "How many threads this worker is running." - }, - "uptime": { - "type": "integer", - "description": "The amount of seconds this worker has been online for this Horde." - }, - "maintenance_mode": { - "type": "boolean", - "description": "When True, this worker will not pick up any new requests.", - "example": false - }, - "paused": { - "type": "boolean", - "description": "(Privileged) When True, this worker not be given any new requests.", - "example": false - }, - "info": { - "type": "string", - "description": "Extra information or comments about this worker provided by its owner.", - "example": "https://dbzer0.com" - }, - "nsfw": { - "type": "boolean", - "description": "Whether this worker can generate NSFW requests or not.", - "default": false - }, - "owner": { - "type": "string", - "description": "Privileged or public if the owner has allowed it. The alias of the owner of this worker.", - "example": "username#1" - }, - "trusted": { - "type": "boolean", - "description": "The worker is trusted to return valid generations." - }, - "flagged": { - "type": "boolean", - "description": "The worker's owner has been flagged for suspicious activity. This worker will not be given any jobs to process." - }, - "suspicious": { - "type": "integer", - "description": "(Privileged) How much suspicion this worker has accumulated.", - "example": 0 - }, - "uncompleted_jobs": { - "type": "integer", - "description": "How many jobs this worker has left uncompleted after it started them.", - "example": 0 - }, - "models": { - "type": "array", - "items": { - "type": "string", - "description": "Which models this worker if offering." - } - }, - "forms": { - "type": "array", - "items": { - "type": "string", - "description": "Which forms this worker if offering." - } - }, - "team": { - "$ref": "#/definitions/TeamDetailsLite" - }, - "contact": { - "type": "string", - "description": "(Privileged) Contact details for the horde admins to reach the owner of this worker in emergencies.", - "example": "email@example.com", - "minLength": 5, - "maxLength": 500 - }, - "bridge_agent": { - "type": "string", - "description": "The bridge agent name, version and website.", - "default": "unknown:0:unknown", - "example": "AI Horde Worker:11:https://github.com/db0/AI-Horde-Worker", - "maxLength": 1000 - }, - "max_pixels": { - "type": "integer", - "description": "The maximum pixels in resolution this worker can generate.", - "example": 262144 - }, - "megapixelsteps_generated": { - "type": "number", - "description": "How many megapixelsteps this worker has generated until now." - }, - "img2img": { - "type": "boolean", - "description": "If True, this worker supports and allows img2img requests." - }, - "painting": { - "type": "boolean", - "description": "If True, this worker supports and allows inpainting requests." - }, - "post-processing": { - "type": "boolean", - "description": "If True, this worker supports and allows post-processing requests." - }, - "lora": { - "type": "boolean", - "description": "If True, this worker supports and allows lora requests." - }, - "max_length": { - "type": "integer", - "description": "The maximum tokens this worker can generate.", - "example": 80 - }, - "max_context_length": { - "type": "integer", - "description": "The maximum tokens this worker can read.", - "example": 80 - }, - "tokens_generated": { - "type": "number", - "description": "How many tokens this worker has generated until now." - } - }, - "type": "object" - } - ] - }, - "WorkerDetailsLite": { - "properties": { - "type": { - "type": "string", - "description": "The Type of worker this is.", - "example": "image", - "enum": [ - "image", - "text", - "interrogation" - ] - }, - "name": { - "type": "string", - "description": "The Name given to this worker." - }, - "id": { - "type": "string", - "description": "The UUID of this worker." - }, - "online": { - "type": "boolean", - "description": "True if the worker has checked-in the past 5 minutes." - } - }, - "type": "object" - }, - "WorkerKudosDetails": { - "properties": { - "generated": { - "type": "number", - "description": "How much Kudos this worker has received for generating images." - }, - "uptime": { - "type": "integer", - "description": "How much Kudos this worker has received for staying online longer." - } - }, - "type": "object" - }, - "TeamDetailsLite": { - "properties": { - "name": { - "type": "string", - "description": "The Name given to this team." - }, - "id": { - "type": "string", - "description": "The UUID of this team." - } - }, - "type": "object" - }, - "ModifyWorkerInput": { - "properties": { - "maintenance": { - "type": "boolean", - "description": "Set to true to put this worker into maintenance." - }, - "maintenance_msg": { - "type": "string", - "description": "if maintenance is True, you can optionally provide a message to be used instead of the default maintenance message, so that the owner is informed." - }, - "paused": { - "type": "boolean", - "description": "(Mods only) Set to true to pause this worker." - }, - "info": { - "type": "string", - "description": "You can optionally provide a server note which will be seen in the server details. No profanity allowed!", - "minLength": 2, - "maxLength": 1000 - }, - "name": { - "type": "string", - "description": "When this is set, it will change the worker's name. No profanity allowed!", - "minLength": 5, - "maxLength": 100 - }, - "team": { - "type": "string", - "description": "The team towards which this worker contributes kudos. It an empty string ('') is passed, it will leave the worker without a team. No profanity allowed!", - "example": "0bed257b-e57c-4327-ac64-40cdfb1ac5e6", - "maxLength": 36 - } - }, - "type": "object" - }, - "ModifyWorker": { - "properties": { - "maintenance": { - "type": "boolean", - "description": "The new state of the 'maintenance' var for this worker. When True, this worker will not pick up any new requests." - }, - "paused": { - "type": "boolean", - "description": "The new state of the 'paused' var for this worker. When True, this worker will not be given any new requests." - }, - "info": { - "type": "string", - "description": "The new state of the 'info' var for this worker." - }, - "name": { - "type": "string", - "description": "The new name for this this worker." - }, - "team": { - "type": "string", - "description": "The new team of this worker.", - "example": "Direct Action" - } - }, - "type": "object" - }, - "DeletedWorker": { - "properties": { - "deleted_id": { - "type": "string", - "description": "The ID of the deleted worker." - }, - "deleted_name": { - "type": "string", - "description": "The Name of the deleted worker." - } - }, - "type": "object" - }, - "KudosTransferred": { - "properties": { - "transferred": { - "type": "integer", - "description": "The amount of Kudos tranferred.", - "example": 100 - } - }, - "type": "object" - }, - "KudosAwarded": { - "properties": { - "awarded": { - "type": "integer", - "description": "The amount of Kudos awarded.", - "example": 100 - } - }, - "type": "object" - }, - "HordeModes": { - "properties": { - "maintenance_mode": { - "type": "boolean", - "description": "When True, this Horde will not accept new requests for image generation, but will finish processing the ones currently in the queue." - }, - "invite_only_mode": { - "type": "boolean", - "description": "When True, this Horde will not only accept worker explicitly invited to join." - }, - "raid_mode": { - "type": "boolean", - "description": "When True, this Horde will not always provide full information in order to throw off attackers." - } - }, - "type": "object" - }, - "HordePerformance": { - "properties": { - "queued_requests": { - "type": "integer", - "description": "The amount of waiting and processing image requests currently in this Horde." - }, - "queued_text_requests": { - "type": "integer", - "description": "The amount of waiting and processing text requests currently in this Horde." - }, - "worker_count": { - "type": "integer", - "description": "How many workers are actively processing prompt generations in this Horde in the past 5 minutes." - }, - "text_worker_count": { - "type": "integer", - "description": "How many workers are actively processing prompt generations in this Horde in the past 5 minutes." - }, - "thread_count": { - "type": "integer", - "description": "How many worker threads are actively processing prompt generations in this Horde in the past 5 minutes." - }, - "text_thread_count": { - "type": "integer", - "description": "How many worker threads are actively processing prompt generations in this Horde in the past 5 minutes." - }, - "queued_megapixelsteps": { - "type": "number", - "description": "The amount of megapixelsteps in waiting and processing requests currently in this Horde." - }, - "past_minute_megapixelsteps": { - "type": "number", - "description": "How many megapixelsteps this Horde generated in the last minute." - }, - "queued_forms": { - "type": "number", - "description": "The amount of image interrogations waiting and processing currently in this Horde." - }, - "interrogator_count": { - "type": "integer", - "description": "How many workers are actively processing image interrogations in this Horde in the past 5 minutes." - }, - "interrogator_thread_count": { - "type": "integer", - "description": "How many worker threads are actively processing image interrogation in this Horde in the past 5 minutes." - }, - "queued_tokens": { - "type": "number", - "description": "The amount of tokens in waiting and processing requests currently in this Horde." - }, - "past_minute_tokens": { - "type": "number", - "description": "How many tokens this Horde generated in the last minute." - } - }, - "type": "object" - }, - "ActiveModel": { - "allOf": [ - { - "$ref": "#/definitions/ActiveModelLite" - }, - { - "properties": { - "performance": { - "type": "number", - "description": "The average speed of generation for this model." - }, - "queued": { - "type": "number", - "description": "The amount waiting to be generated by this model." - }, - "jobs": { - "type": "number", - "description": "The job count waiting to be generated by this model." - }, - "eta": { - "type": "integer", - "description": "Estimated time in seconds for this model's queue to be cleared." - }, - "type": { - "type": "string", - "description": "The model type (text or image).", - "example": "image", - "enum": [ - "image", - "tex.t" - ] - } - }, - "type": "object" - } - ] - }, - "ActiveModelLite": { - "properties": { - "name": { - "type": "string", - "description": "The Name of a model available by workers in this horde." - }, - "count": { - "type": "integer", - "description": "How many of workers in this horde are running this model." - } - }, - "type": "object" - }, - "Newspiece": { - "properties": { - "date_published": { - "type": "string", - "description": "The date this newspiece was published." - }, - "newspiece": { - "type": "string", - "description": "The actual piece of news." - }, - "importance": { - "type": "string", - "description": "How critical this piece of news is.", - "example": "Information" - } - }, - "type": "object" - }, - "CreateTeamInput": { - "required": [ - "name" - ], - "properties": { - "name": { - "type": "string", - "description": "The name of the team. No profanity allowed!", - "minLength": 3, - "maxLength": 100 - }, - "info": { - "type": "string", - "description": "Extra information or comments about this team.", - "example": "Anarchy is emergent order.", - "minLength": 3, - "maxLength": 1000 - } - }, - "type": "object" - }, - "ModifyTeam": { - "properties": { - "id": { - "type": "string", - "description": "The ID of the team." - }, - "name": { - "type": "string", - "description": "The Name of the team." - }, - "info": { - "type": "string", - "description": "The Info of the team." - } - }, - "type": "object" - }, - "TeamDetails": { - "allOf": [ - { - "$ref": "#/definitions/TeamDetailsLite" - }, - { - "properties": { - "info": { - "type": "string", - "description": "Extra information or comments about this team provided by its owner.", - "example": "Anarchy is emergent order." - }, - "requests_fulfilled": { - "type": "integer", - "description": "How many images this team's workers have generated." - }, - "kudos": { - "type": "number", - "description": "How many Kudos the workers in this team have been rewarded while part of this team." - }, - "uptime": { - "type": "integer", - "description": "The total amount of time workers have stayed online while on this team." - }, - "creator": { - "type": "string", - "description": "The alias of the user which created this team.", - "example": "db0#1" - }, - "worker_count": { - "type": "integer", - "description": "How many workers have been dedicated to this team.", - "example": 10 - }, - "workers": { - "type": "array", - "items": { - "$ref": "#/definitions/WorkerDetailsLite" - } - }, - "models": { - "type": "array", - "items": { - "$ref": "#/definitions/ActiveModelLite" - } - } - }, - "type": "object" - } - ] - }, - "ModifyTeamInput": { - "properties": { - "name": { - "type": "string", - "description": "The name of the team. No profanity allowed!", - "minLength": 3, - "maxLength": 100 - }, - "info": { - "type": "string", - "description": "Extra information or comments about this team.", - "example": "Anarchy is emergent order.", - "minLength": 3, - "maxLength": 1000 - } - }, - "type": "object" - }, - "DeletedTeam": { - "properties": { - "deleted_id": { - "type": "string", - "description": "The ID of the deleted team." - }, - "deleted_name": { - "type": "string", - "description": "The Name of the deleted team." - } - }, - "type": "object" - }, - "DeleteTimeoutIPInput": { - "required": [ - "ipaddr" - ], - "properties": { - "ipaddr": { - "type": "string", - "description": "The IP address to remove from timeout.", - "example": "127.0.0.1", - "minLength": 7, - "maxLength": 15 - } - }, - "type": "object" - }, - "ModelInterrogationInputStable": { - "properties": { - "forms": { - "type": "array", - "items": { - "$ref": "#/definitions/ModelInterrogationFormStable" - } - }, - "source_image": { - "type": "string", - "description": "The public URL of the image to interrogate." - }, - "slow_workers": { - "type": "boolean", - "description": "When True, allows slower workers to pick up this request. Disabling this incurs an extra kudos cost.", - "default": true - } - }, - "type": "object" - }, - "ModelInterrogationFormStable": { - "required": [ - "name" - ], - "properties": { - "name": { - "type": "string", - "description": "The type of interrogation this is.", - "example": "caption", - "enum": [ - "caption", - "interrogation", - "nsfw", - "GFPGAN", - "RealESRGAN_x4plus", - "RealESRGAN_x2plus", - "RealESRGAN_x4plus_anime_6B", - "NMKD_Siax", - "4x_AnimeSharp", - "CodeFormers", - "strip_background" - ] - }, - "payload": { - "$ref": "#/definitions/ModelInterrogationFormPayloadStable" - } - }, - "type": "object" - }, - "ModelInterrogationFormPayloadStable": { - "properties": { - "*": { - "type": "object", - "additionalProperties": { - "type": "string" - } - } - }, - "type": "object" - }, - "RequestInterrogationResponse": { - "properties": { - "id": { - "type": "string", - "description": "The UUID of the request. Use this to retrieve the request status in the future." - }, - "message": { - "type": "string", - "description": "Any extra information from the horde about this request." - } - }, - "type": "object" - }, - "InterrogationStatus": { - "properties": { - "state": { - "type": "string", - "title": "Interrogation State", - "description": "The overall status of this interrogation." - }, - "forms": { - "type": "array", - "items": { - "$ref": "#/definitions/InterrogationFormStatus" - } - } - }, - "type": "object" - }, - "InterrogationFormStatus": { - "properties": { - "form": { - "type": "string", - "description": "The name of this interrogation form." - }, - "state": { - "type": "string", - "title": "Interrogation State", - "description": "The overall status of this interrogation." - }, - "result": { - "$ref": "#/definitions/InterrogationFormResult" - } - }, - "type": "object" - }, - "InterrogationFormResult": { - "properties": { - "*": { - "type": "object", - "additionalProperties": { - "type": "object" - } - } - }, - "type": "object" - }, - "InterrogationPopInput": { - "properties": { - "name": { - "type": "string", - "description": "The Name of the Worker." - }, - "priority_usernames": { - "type": "array", - "items": { - "type": "string", - "description": "Users with priority to use this worker." - } - }, - "forms": { - "type": "array", - "items": { - "type": "string", - "description": "The type of interrogation this worker can fulfil.", - "example": "caption", - "enum": [ - "caption", - "interrogation", - "nsfw", - "GFPGAN", - "RealESRGAN_x4plus", - "RealESRGAN_x2plus", - "RealESRGAN_x4plus_anime_6B", - "NMKD_Siax", - "4x_AnimeSharp", - "CodeFormers", - "strip_background" - ] - } - }, - "amount": { - "type": "integer", - "description": "The amount of forms to pop at the same time.", - "default": 1 - }, - "bridge_version": { - "type": "integer", - "description": "The version of the bridge used by this worker.", - "default": 1 - }, - "bridge_agent": { - "type": "string", - "description": "The worker name, version and website.", - "default": "unknown", - "example": "AI Horde Worker:11:https://github.com/db0/AI-Horde-Worker", - "maxLength": 1000 - }, - "threads": { - "type": "integer", - "description": "How many threads this worker is running. This is used to accurately the current power available in the horde.", - "default": 1, - "minimum": 1, - "maximum": 100 - }, - "max_tiles": { - "type": "integer", - "description": "The maximum amount of 512x512 tiles this worker can post-process.", - "default": 16, - "minimum": 1, - "maximum": 256 - } - }, - "type": "object" - }, - "InterrogationPopPayload": { - "properties": { - "forms": { - "type": "array", - "items": { - "$ref": "#/definitions/InterrogationPopFormPayload" - } - }, - "skipped": { - "$ref": "#/definitions/NoValidInterrogationsFound" - } - }, - "type": "object" - }, - "InterrogationPopFormPayload": { - "properties": { - "id": { - "type": "string", - "description": "The UUID of the interrogation form. Use this to post the results in the future." - }, - "form": { - "type": "string", - "description": "The name of this interrogation form", - "example": "caption", - "enum": [ - "caption", - "interrogation", - "nsfw.", - "GFPGAN", - "RealESRGAN_x4plus", - "RealESRGAN_x2plus", - "RealESRGAN_x4plus_anime_6B", - "NMKD_Siax", - "4x_AnimeSharp", - "CodeFormers", - "strip_background" - ] - }, - "payload": { - "$ref": "#/definitions/ModelInterrogationFormPayloadStable" - }, - "source_image": { - "type": "string", - "description": "The URL From which the source image can be downloaded." - }, - "r2_upload": { - "type": "string", - "description": "The URL in which the post-processed image can be uploaded." - } - }, - "type": "object" - }, - "NoValidInterrogationsFound": { - "properties": { - "worker_id": { - "type": "integer", - "description": "How many waiting requests were skipped because they demanded a specific worker.", - "minimum": 0 - }, - "untrusted": { - "type": "integer", - "description": "How many waiting requests were skipped because they demanded a trusted worker which this worker is not.", - "minimum": 0 - }, - "bridge_version": { - "type": "integer", - "description": "How many waiting requests were skipped because they require a higher version of the bridge than this worker is running (upgrade if you see this in your skipped list).", - "example": 0, - "minimum": 0 - } - }, - "type": "object" - }, - "PutNewFilter": { - "required": [ - "filter_type", - "regex" - ], - "properties": { - "regex": { - "type": "string", - "description": "The regex for this filter.", - "example": "ac.*" - }, - "filter_type": { - "type": "integer", - "description": "The integer defining this filter type.", - "example": 10, - "minimum": 10, - "maximum": 29 - }, - "description": { - "type": "string", - "description": "Description about this regex." - }, - "replacement": { - "type": "string", - "description": "The replacement string for this regex.", - "default": "" - } - }, - "type": "object" - }, - "FilterDetails": { - "required": [ - "filter_type", - "id", - "regex", - "user" - ], - "properties": { - "id": { - "type": "string", - "description": "The UUID of this filter." - }, - "regex": { - "type": "string", - "description": "The regex for this filter.", - "example": "ac.*" - }, - "filter_type": { - "type": "integer", - "description": "The integer defining this filter type.", - "example": 10, - "minimum": 10, - "maximum": 29 - }, - "description": { - "type": "string", - "description": "Description about this regex." - }, - "replacement": { - "type": "string", - "description": "The replacement string for this regex.", - "default": "" - }, - "user": { - "type": "string", - "description": "The moderator which added or last updated this regex." - } - }, - "type": "object" - }, - "FilterPromptSuspicion": { - "required": [ - "suspicion" - ], - "properties": { - "suspicion": { - "type": "string", - "description": "Rates how suspicious the provided prompt is. A suspicion over 2 means it would be blocked.", - "default": 0 - }, - "matches": { - "type": "array", - "items": { - "type": "string", - "description": "Which words in the prompt matched the filters." - } - } - }, - "type": "object" - }, - "FilterRegex": { - "required": [ - "filter_type", - "regex" - ], - "properties": { - "filter_type": { - "type": "integer", - "description": "The integer defining this filter type.", - "example": 10, - "minimum": 10, - "maximum": 29 - }, - "regex": { - "type": "string", - "description": "The full regex for this filter type." - } - }, - "type": "object" - }, - "PatchExistingFilter": { - "properties": { - "regex": { - "type": "string", - "description": "The regex for this filter.", - "example": "ac.*" - }, - "filter_type": { - "type": "integer", - "description": "The integer defining this filter type.", - "example": 10, - "minimum": 10, - "maximum": 29 - }, - "description": { - "type": "string", - "description": "Description about this regex." - }, - "replacement": { - "type": "string", - "description": "The replacement string for this regex.", - "default": "" - } - }, - "type": "object" - }, - "StatsImgTotals": { - "properties": { - "minute": { - "$ref": "#/definitions/SinglePeriodImgStat" - }, - "hour": { - "$ref": "#/definitions/SinglePeriodImgStat" - }, - "day": { - "$ref": "#/definitions/SinglePeriodImgStat" - }, - "month": { - "$ref": "#/definitions/SinglePeriodImgStat" - }, - "total": { - "$ref": "#/definitions/SinglePeriodImgStat" - } - }, - "type": "object" - }, - "SinglePeriodImgStat": { - "properties": { - "requests": { - "type": "integer", - "description": "The amount of text requests generated during this period." - }, - "tokens": { - "type": "integer", - "description": "The amount of tokens generated during this period." - } - }, - "type": "object" - }, - "ImgModelStats": { - "properties": { - "day": { - "$ref": "#/definitions/SinglePeriodImgModelStats" - }, - "month": { - "$ref": "#/definitions/SinglePeriodImgModelStats" - }, - "total": { - "$ref": "#/definitions/SinglePeriodImgModelStats" - } - }, - "type": "object" - }, - "SinglePeriodImgModelStats": { - "properties": { - "*": { - "type": "object", - "additionalProperties": { - "type": "integer", - "description": "The amount of requests fulfilled for this model." - } - } - }, - "type": "object" - }, - "StatsTxtTotals": { - "properties": { - "minute": { - "$ref": "#/definitions/SinglePeriodImgStat" - }, - "hour": { - "$ref": "#/definitions/SinglePeriodImgStat" - }, - "day": { - "$ref": "#/definitions/SinglePeriodImgStat" - }, - "month": { - "$ref": "#/definitions/SinglePeriodImgStat" - }, - "total": { - "$ref": "#/definitions/SinglePeriodImgStat" - } - }, - "type": "object" - }, - "TxtModelStats": { - "properties": { - "day": { - "$ref": "#/definitions/SinglePeriodTxtModelStats" - }, - "month": { - "$ref": "#/definitions/SinglePeriodTxtModelStats" - }, - "total": { - "$ref": "#/definitions/SinglePeriodTxtModelStats" - } - }, - "type": "object" - }, - "SinglePeriodTxtModelStats": { - "properties": { - "*": { - "type": "object", - "additionalProperties": { - "type": "integer", - "description": "The amount of requests fulfilled for this model." - } - } - }, - "type": "object" - } - }, - "responses": { - "ParseError": { - "description": "When a mask can't be parsed" - }, - "MaskError": { - "description": "When any error occurs on mask" - } - } -} diff --git a/docs/api_to_sdk_map.md b/docs/api_to_sdk_map.md index 19b60a0..e2348c8 100644 --- a/docs/api_to_sdk_map.md +++ b/docs/api_to_sdk_map.md @@ -11,12 +11,27 @@ This is a mapping of the AI-Horde API models (defined at [https://stablehorde.ne | /v2/generate/status/{id} | DELETE | [DeleteImageGenerateRequest][horde_sdk.ai_horde_api.apimodels.generate._status.DeleteImageGenerateRequest] | | /v2/generate/status/{id} | GET | [ImageGenerateStatusRequest][horde_sdk.ai_horde_api.apimodels.generate._status.ImageGenerateStatusRequest] | | /v2/generate/submit | POST | [ImageGenerationJobSubmitRequest][horde_sdk.ai_horde_api.apimodels.generate._submit.ImageGenerationJobSubmitRequest] | +| /v2/generate/text/async | POST | [TextGenerateAsyncRequest][horde_sdk.ai_horde_api.apimodels.generate.text._async.TextGenerateAsyncRequest] | +| /v2/generate/text/pop | POST | [TextGenerateJobPopRequest][horde_sdk.ai_horde_api.apimodels.generate.text._pop.TextGenerateJobPopRequest] | +| /v2/generate/text/status/{id} | DELETE | [DeleteTextGenerateRequest][horde_sdk.ai_horde_api.apimodels.generate.text._status.DeleteTextGenerateRequest] | +| /v2/generate/text/status/{id} | GET | [TextGenerateStatusRequest][horde_sdk.ai_horde_api.apimodels.generate.text._status.TextGenerateStatusRequest] | +| /v2/generate/text/submit | POST | [TextGenerationJobSubmitRequest][horde_sdk.ai_horde_api.apimodels.generate.text._submit.TextGenerationJobSubmitRequest] | | /v2/interrogate/async | POST | [AlchemyAsyncRequest][horde_sdk.ai_horde_api.apimodels.alchemy._async.AlchemyAsyncRequest] | | /v2/interrogate/pop | POST | [AlchemyPopRequest][horde_sdk.ai_horde_api.apimodels.alchemy._pop.AlchemyPopRequest] | | /v2/interrogate/status/{id} | DELETE | [AlchemyDeleteRequest][horde_sdk.ai_horde_api.apimodels.alchemy._status.AlchemyDeleteRequest] | | /v2/interrogate/status/{id} | GET | [AlchemyStatusRequest][horde_sdk.ai_horde_api.apimodels.alchemy._status.AlchemyStatusRequest] | -| /v2/stats/img/models | GET | [StatsImageModelsRequest][horde_sdk.ai_horde_api.apimodels._stats.StatsImageModelsRequest] | -| /v2/workers | GET | [AllWorkersDetailsRequest][horde_sdk.ai_horde_api.apimodels.workers._workers_all.AllWorkersDetailsRequest] | +| /v2/interrogate/submit | POST | [AlchemyJobSubmitRequest][horde_sdk.ai_horde_api.apimodels.alchemy._submit.AlchemyJobSubmitRequest] | +| /v2/stats/img/models | GET | [ImageStatsModelsRequest][horde_sdk.ai_horde_api.apimodels._stats.ImageStatsModelsRequest] | +| /v2/stats/img/totals | GET | [ImageStatsModelsTotalRequest][horde_sdk.ai_horde_api.apimodels._stats.ImageStatsModelsTotalRequest] | +| /v2/stats/text/models | GET | [TextStatsModelsRequest][horde_sdk.ai_horde_api.apimodels._stats.TextStatsModelsRequest] | +| /v2/stats/text/totals | GET | [TextStatsModelsTotalRequest][horde_sdk.ai_horde_api.apimodels._stats.TextStatsModelsTotalRequest] | +| /v2/status/heartbeat | GET | [AIHordeHeartbeatRequest][horde_sdk.ai_horde_api.apimodels._status.AIHordeHeartbeatRequest] | +| /v2/status/models | GET | [HordeStatusModelsAllRequest][horde_sdk.ai_horde_api.apimodels._status.HordeStatusModelsAllRequest] | +| /v2/status/models/{model_name} | GET | [HordeStatusModelsSingleRequest][horde_sdk.ai_horde_api.apimodels._status.HordeStatusModelsSingleRequest] | +| /v2/status/news | GET | [NewsRequest][horde_sdk.ai_horde_api.apimodels._status.NewsRequest] | +| /v2/status/performance | GET | [HordePerformanceRequest][horde_sdk.ai_horde_api.apimodels._status.HordePerformanceRequest] | +| /v2/workers | GET | [AllWorkersDetailsRequest][horde_sdk.ai_horde_api.apimodels.workers._workers.AllWorkersDetailsRequest] | +| /v2/workers/{worker_id} | GET | [SingleWorkerDetailsRequest][horde_sdk.ai_horde_api.apimodels.workers._workers.SingleWorkerDetailsRequest] | ## Responses @@ -29,8 +44,23 @@ This is a mapping of the AI-Horde API models (defined at [https://stablehorde.ne | /v2/generate/pop | 200 | [ImageGenerateJobPopResponse][horde_sdk.ai_horde_api.apimodels.generate._pop.ImageGenerateJobPopResponse] | | /v2/generate/status/{id} | 200 | [ImageGenerateStatusResponse][horde_sdk.ai_horde_api.apimodels.generate._status.ImageGenerateStatusResponse] | | /v2/generate/submit | 200 | [JobSubmitResponse][horde_sdk.ai_horde_api.apimodels.base.JobSubmitResponse] | +| /v2/generate/text/async | 200 | [TextGenerateAsyncDryRunResponse][horde_sdk.ai_horde_api.apimodels.generate.text._async.TextGenerateAsyncDryRunResponse] | +| /v2/generate/text/async | 202 | [TextGenerateAsyncResponse][horde_sdk.ai_horde_api.apimodels.generate.text._async.TextGenerateAsyncResponse] | +| /v2/generate/text/pop | 200 | [TextGenerateJobPopResponse][horde_sdk.ai_horde_api.apimodels.generate.text._pop.TextGenerateJobPopResponse] | +| /v2/generate/text/status/{id} | 200 | [TextGenerateStatusResponse][horde_sdk.ai_horde_api.apimodels.generate.text._status.TextGenerateStatusResponse] | +| /v2/generate/text/submit | 200 | [JobSubmitResponse][horde_sdk.ai_horde_api.apimodels.base.JobSubmitResponse] | | /v2/interrogate/async | 202 | [AlchemyAsyncResponse][horde_sdk.ai_horde_api.apimodels.alchemy._async.AlchemyAsyncResponse] | | /v2/interrogate/pop | 200 | [AlchemyPopResponse][horde_sdk.ai_horde_api.apimodels.alchemy._pop.AlchemyPopResponse] | | /v2/interrogate/status/{id} | 200 | [AlchemyStatusResponse][horde_sdk.ai_horde_api.apimodels.alchemy._status.AlchemyStatusResponse] | -| /v2/stats/img/models | 200 | [StatsModelsResponse][horde_sdk.ai_horde_api.apimodels._stats.StatsModelsResponse] | -| /v2/workers | 200 | [AllWorkersDetailsResponse][horde_sdk.ai_horde_api.apimodels.workers._workers_all.AllWorkersDetailsResponse] | +| /v2/interrogate/submit | 200 | [AlchemyJobSubmitResponse][horde_sdk.ai_horde_api.apimodels.alchemy._submit.AlchemyJobSubmitResponse] | +| /v2/stats/img/models | 200 | [ImageModelStatsResponse][horde_sdk.ai_horde_api.apimodels._stats.ImageModelStatsResponse] | +| /v2/stats/img/totals | 200 | [ImageStatsModelsTotalResponse][horde_sdk.ai_horde_api.apimodels._stats.ImageStatsModelsTotalResponse] | +| /v2/stats/text/models | 200 | [TextModelStatsResponse][horde_sdk.ai_horde_api.apimodels._stats.TextModelStatsResponse] | +| /v2/stats/text/totals | 200 | [TextStatsModelsTotalResponse][horde_sdk.ai_horde_api.apimodels._stats.TextStatsModelsTotalResponse] | +| /v2/status/heartbeat | 200 | [AIHordeHeartbeatResponse][horde_sdk.ai_horde_api.apimodels._status.AIHordeHeartbeatResponse] | +| /v2/status/models | 200 | [HordeStatusModelsAllResponse][horde_sdk.ai_horde_api.apimodels._status.HordeStatusModelsAllResponse] | +| /v2/status/models/{model_name} | 200 | [HordeStatusModelsSingleResponse][horde_sdk.ai_horde_api.apimodels._status.HordeStatusModelsSingleResponse] | +| /v2/status/news | 200 | [NewsResponse][horde_sdk.ai_horde_api.apimodels._status.NewsResponse] | +| /v2/status/performance | 200 | [HordePerformanceResponse][horde_sdk.ai_horde_api.apimodels._status.HordePerformanceResponse] | +| /v2/workers | 200 | [AllWorkersDetailsResponse][horde_sdk.ai_horde_api.apimodels.workers._workers.AllWorkersDetailsResponse] | +| /v2/workers/{worker_id} | 200 | [SingleWorkerDetailsResponse][horde_sdk.ai_horde_api.apimodels.workers._workers.SingleWorkerDetailsResponse] | diff --git a/docs/api_to_sdk_payload_map.json b/docs/api_to_sdk_payload_map.json index 21206c4..5c46cec 100644 --- a/docs/api_to_sdk_payload_map.json +++ b/docs/api_to_sdk_payload_map.json @@ -1,4 +1,7 @@ { + "/v2/status/heartbeat": { + "GET": "horde_sdk.ai_horde_api.apimodels._status.AIHordeHeartbeatRequest" + }, "/v2/interrogate/async": { "POST": "horde_sdk.ai_horde_api.apimodels.alchemy._async.AlchemyAsyncRequest" }, @@ -6,19 +9,35 @@ "DELETE": "horde_sdk.ai_horde_api.apimodels.alchemy._status.AlchemyDeleteRequest", "GET": "horde_sdk.ai_horde_api.apimodels.alchemy._status.AlchemyStatusRequest" }, + "/v2/interrogate/submit": { + "POST": "horde_sdk.ai_horde_api.apimodels.alchemy._submit.AlchemyJobSubmitRequest" + }, "/v2/interrogate/pop": { "POST": "horde_sdk.ai_horde_api.apimodels.alchemy._pop.AlchemyPopRequest" }, "/v2/workers": { - "GET": "horde_sdk.ai_horde_api.apimodels.workers._workers_all.AllWorkersDetailsRequest" + "GET": "horde_sdk.ai_horde_api.apimodels.workers._workers.AllWorkersDetailsRequest" }, "/v2/generate/status/{id}": { "DELETE": "horde_sdk.ai_horde_api.apimodels.generate._status.DeleteImageGenerateRequest", "GET": "horde_sdk.ai_horde_api.apimodels.generate._status.ImageGenerateStatusRequest" }, + "/v2/generate/text/status/{id}": { + "DELETE": "horde_sdk.ai_horde_api.apimodels.generate.text._status.DeleteTextGenerateRequest", + "GET": "horde_sdk.ai_horde_api.apimodels.generate.text._status.TextGenerateStatusRequest" + }, "/v2/find_user": { "GET": "horde_sdk.ai_horde_api.apimodels._find_user.FindUserRequest" }, + "/v2/status/performance": { + "GET": "horde_sdk.ai_horde_api.apimodels._status.HordePerformanceRequest" + }, + "/v2/status/models": { + "GET": "horde_sdk.ai_horde_api.apimodels._status.HordeStatusModelsAllRequest" + }, + "/v2/status/models/{model_name}": { + "GET": "horde_sdk.ai_horde_api.apimodels._status.HordeStatusModelsSingleRequest" + }, "/v2/generate/async": { "POST": "horde_sdk.ai_horde_api.apimodels.generate._async.ImageGenerateAsyncRequest" }, @@ -32,6 +51,30 @@ "POST": "horde_sdk.ai_horde_api.apimodels.generate._submit.ImageGenerationJobSubmitRequest" }, "/v2/stats/img/models": { - "GET": "horde_sdk.ai_horde_api.apimodels._stats.StatsImageModelsRequest" + "GET": "horde_sdk.ai_horde_api.apimodels._stats.ImageStatsModelsRequest" + }, + "/v2/stats/img/totals": { + "GET": "horde_sdk.ai_horde_api.apimodels._stats.ImageStatsModelsTotalRequest" + }, + "/v2/status/news": { + "GET": "horde_sdk.ai_horde_api.apimodels._status.NewsRequest" + }, + "/v2/workers/{worker_id}": { + "GET": "horde_sdk.ai_horde_api.apimodels.workers._workers.SingleWorkerDetailsRequest" + }, + "/v2/generate/text/async": { + "POST": "horde_sdk.ai_horde_api.apimodels.generate.text._async.TextGenerateAsyncRequest" + }, + "/v2/generate/text/pop": { + "POST": "horde_sdk.ai_horde_api.apimodels.generate.text._pop.TextGenerateJobPopRequest" + }, + "/v2/generate/text/submit": { + "POST": "horde_sdk.ai_horde_api.apimodels.generate.text._submit.TextGenerationJobSubmitRequest" + }, + "/v2/stats/text/models": { + "GET": "horde_sdk.ai_horde_api.apimodels._stats.TextStatsModelsRequest" + }, + "/v2/stats/text/totals": { + "GET": "horde_sdk.ai_horde_api.apimodels._stats.TextStatsModelsTotalRequest" } } diff --git a/docs/api_to_sdk_response_map.json b/docs/api_to_sdk_response_map.json index 8be1605..b97b198 100644 --- a/docs/api_to_sdk_response_map.json +++ b/docs/api_to_sdk_response_map.json @@ -1,22 +1,40 @@ { + "/v2/status/heartbeat": { + "200": "horde_sdk.ai_horde_api.apimodels._status.AIHordeHeartbeatResponse" + }, "/v2/interrogate/async": { "202": "horde_sdk.ai_horde_api.apimodels.alchemy._async.AlchemyAsyncResponse" }, "/v2/interrogate/status/{id}": { "200": "horde_sdk.ai_horde_api.apimodels.alchemy._status.AlchemyStatusResponse" }, + "/v2/interrogate/submit": { + "200": "horde_sdk.ai_horde_api.apimodels.alchemy._submit.AlchemyJobSubmitResponse" + }, "/v2/interrogate/pop": { "200": "horde_sdk.ai_horde_api.apimodels.alchemy._pop.AlchemyPopResponse" }, "/v2/workers": { - "200": "horde_sdk.ai_horde_api.apimodels.workers._workers_all.AllWorkersDetailsResponse" + "200": "horde_sdk.ai_horde_api.apimodels.workers._workers.AllWorkersDetailsResponse" }, "/v2/generate/status/{id}": { "200": "horde_sdk.ai_horde_api.apimodels.generate._status.ImageGenerateStatusResponse" }, + "/v2/generate/text/status/{id}": { + "200": "horde_sdk.ai_horde_api.apimodels.generate.text._status.TextGenerateStatusResponse" + }, "/v2/find_user": { "200": "horde_sdk.ai_horde_api.apimodels._find_user.FindUserResponse" }, + "/v2/status/performance": { + "200": "horde_sdk.ai_horde_api.apimodels._status.HordePerformanceResponse" + }, + "/v2/status/models": { + "200": "horde_sdk.ai_horde_api.apimodels._status.HordeStatusModelsAllResponse" + }, + "/v2/status/models/{model_name}": { + "200": "horde_sdk.ai_horde_api.apimodels._status.HordeStatusModelsSingleResponse" + }, "/v2/generate/async": { "200": "horde_sdk.ai_horde_api.apimodels.generate._async.ImageGenerateAsyncDryRunResponse", "202": "horde_sdk.ai_horde_api.apimodels.generate._async.ImageGenerateAsyncResponse" @@ -31,6 +49,31 @@ "200": "horde_sdk.ai_horde_api.apimodels.base.JobSubmitResponse" }, "/v2/stats/img/models": { - "200": "horde_sdk.ai_horde_api.apimodels._stats.StatsModelsResponse" + "200": "horde_sdk.ai_horde_api.apimodels._stats.ImageModelStatsResponse" + }, + "/v2/stats/img/totals": { + "200": "horde_sdk.ai_horde_api.apimodels._stats.ImageStatsModelsTotalResponse" + }, + "/v2/status/news": { + "200": "horde_sdk.ai_horde_api.apimodels._status.NewsResponse" + }, + "/v2/workers/{worker_id}": { + "200": "horde_sdk.ai_horde_api.apimodels.workers._workers.SingleWorkerDetailsResponse" + }, + "/v2/generate/text/async": { + "200": "horde_sdk.ai_horde_api.apimodels.generate.text._async.TextGenerateAsyncDryRunResponse", + "202": "horde_sdk.ai_horde_api.apimodels.generate.text._async.TextGenerateAsyncResponse" + }, + "/v2/generate/text/pop": { + "200": "horde_sdk.ai_horde_api.apimodels.generate.text._pop.TextGenerateJobPopResponse" + }, + "/v2/generate/text/submit": { + "200": "horde_sdk.ai_horde_api.apimodels.base.JobSubmitResponse" + }, + "/v2/stats/text/models": { + "200": "horde_sdk.ai_horde_api.apimodels._stats.TextModelStatsResponse" + }, + "/v2/stats/text/totals": { + "200": "horde_sdk.ai_horde_api.apimodels._stats.TextStatsModelsTotalResponse" } } diff --git a/docs/horde_sdk/_version.md b/docs/horde_sdk/_version.md new file mode 100644 index 0000000..f17be70 --- /dev/null +++ b/docs/horde_sdk/_version.md @@ -0,0 +1,2 @@ +# _version +::: horde_sdk._version diff --git a/docs/horde_sdk/ai_horde_api/apimodels/_status.md b/docs/horde_sdk/ai_horde_api/apimodels/_status.md new file mode 100644 index 0000000..a4f711b --- /dev/null +++ b/docs/horde_sdk/ai_horde_api/apimodels/_status.md @@ -0,0 +1,2 @@ +# _status +::: horde_sdk.ai_horde_api.apimodels._status diff --git a/docs/horde_sdk/ai_horde_api/apimodels/generate/text/.pages b/docs/horde_sdk/ai_horde_api/apimodels/generate/text/.pages new file mode 100644 index 0000000..05126b0 --- /dev/null +++ b/docs/horde_sdk/ai_horde_api/apimodels/generate/text/.pages @@ -0,0 +1 @@ +title: text diff --git a/docs/horde_sdk/ai_horde_api/apimodels/generate/text/_async.md b/docs/horde_sdk/ai_horde_api/apimodels/generate/text/_async.md new file mode 100644 index 0000000..3c48ebf --- /dev/null +++ b/docs/horde_sdk/ai_horde_api/apimodels/generate/text/_async.md @@ -0,0 +1,2 @@ +# _async +::: horde_sdk.ai_horde_api.apimodels.generate.text._async diff --git a/docs/horde_sdk/ai_horde_api/apimodels/generate/text/_pop.md b/docs/horde_sdk/ai_horde_api/apimodels/generate/text/_pop.md new file mode 100644 index 0000000..4ec6d45 --- /dev/null +++ b/docs/horde_sdk/ai_horde_api/apimodels/generate/text/_pop.md @@ -0,0 +1,2 @@ +# _pop +::: horde_sdk.ai_horde_api.apimodels.generate.text._pop diff --git a/docs/horde_sdk/ai_horde_api/apimodels/generate/text/_status.md b/docs/horde_sdk/ai_horde_api/apimodels/generate/text/_status.md new file mode 100644 index 0000000..c307f71 --- /dev/null +++ b/docs/horde_sdk/ai_horde_api/apimodels/generate/text/_status.md @@ -0,0 +1,2 @@ +# _status +::: horde_sdk.ai_horde_api.apimodels.generate.text._status diff --git a/docs/horde_sdk/ai_horde_api/apimodels/generate/text/_submit.md b/docs/horde_sdk/ai_horde_api/apimodels/generate/text/_submit.md new file mode 100644 index 0000000..d2803ef --- /dev/null +++ b/docs/horde_sdk/ai_horde_api/apimodels/generate/text/_submit.md @@ -0,0 +1,2 @@ +# _submit +::: horde_sdk.ai_horde_api.apimodels.generate.text._submit diff --git a/docs/horde_sdk/ai_horde_api/apimodels/workers/_workers.md b/docs/horde_sdk/ai_horde_api/apimodels/workers/_workers.md new file mode 100644 index 0000000..dea5d96 --- /dev/null +++ b/docs/horde_sdk/ai_horde_api/apimodels/workers/_workers.md @@ -0,0 +1,2 @@ +# _workers +::: horde_sdk.ai_horde_api.apimodels.workers._workers diff --git a/docs/horde_sdk/ai_horde_api/apimodels/workers/_workers_all.md b/docs/horde_sdk/ai_horde_api/apimodels/workers/_workers_all.md deleted file mode 100644 index 21f655e..0000000 --- a/docs/horde_sdk/ai_horde_api/apimodels/workers/_workers_all.md +++ /dev/null @@ -1,2 +0,0 @@ -# _workers_all -::: horde_sdk.ai_horde_api.apimodels.workers._workers_all diff --git a/docs/horde_sdk/generic_api/decoration.md b/docs/horde_sdk/generic_api/decoration.md new file mode 100644 index 0000000..795e352 --- /dev/null +++ b/docs/horde_sdk/generic_api/decoration.md @@ -0,0 +1,2 @@ +# decoration +::: horde_sdk.generic_api.decoration diff --git a/docs/horde_sdk/horde_logging.md b/docs/horde_sdk/horde_logging.md new file mode 100644 index 0000000..95a74c0 --- /dev/null +++ b/docs/horde_sdk/horde_logging.md @@ -0,0 +1,2 @@ +# horde_logging +::: horde_sdk.horde_logging diff --git a/docs/horde_sdk/logging.md b/docs/horde_sdk/logging.md deleted file mode 100644 index f77a8bc..0000000 --- a/docs/horde_sdk/logging.md +++ /dev/null @@ -1,2 +0,0 @@ -# logging -::: horde_sdk.logging diff --git a/docs/horde_sdk/meta.md b/docs/horde_sdk/meta.md new file mode 100644 index 0000000..c30f2b0 --- /dev/null +++ b/docs/horde_sdk/meta.md @@ -0,0 +1,2 @@ +# meta +::: horde_sdk.meta diff --git a/docs/request_field_names_and_descriptions.json b/docs/request_field_names_and_descriptions.json index 283746e..e33e39f 100644 --- a/docs/request_field_names_and_descriptions.json +++ b/docs/request_field_names_and_descriptions.json @@ -1,4 +1,14 @@ { + "AIHordeHeartbeatRequest": [ + [ + "accept", + null + ], + [ + "client_agent", + null + ] + ], "AlchemyAsyncRequest": [ [ "apikey", @@ -39,6 +49,32 @@ null ] ], + "AlchemyJobSubmitRequest": [ + [ + "apikey", + null + ], + [ + "id_", + null + ], + [ + "accept", + null + ], + [ + "client_agent", + null + ], + [ + "result", + null + ], + [ + "state", + null + ] + ], "AlchemyPopRequest": [ [ "apikey", @@ -115,6 +151,20 @@ null ] ], + "DeleteTextGenerateRequest": [ + [ + "id_", + null + ], + [ + "accept", + null + ], + [ + "client_agent", + null + ] + ], "FindUserRequest": [ [ "apikey", @@ -129,6 +179,56 @@ null ] ], + "HordePerformanceRequest": [ + [ + "accept", + null + ], + [ + "client_agent", + null + ] + ], + "HordeStatusModelsAllRequest": [ + [ + "accept", + null + ], + [ + "client_agent", + null + ], + [ + "type_", + "The type of model to filter by." + ], + [ + "min_count", + null + ], + [ + "max_count", + null + ], + [ + "model_state", + null + ] + ], + "HordeStatusModelsSingleRequest": [ + [ + "accept", + null + ], + [ + "client_agent", + null + ], + [ + "model_name", + null + ] + ], "ImageGenerateAsyncRequest": [ [ "trusted_workers", @@ -227,47 +327,51 @@ ], "ImageGenerateJobPopRequest": [ [ - "apikey", - null + "amount", + "How many jobvs to pop at the same time" ], [ - "accept", - null + "bridge_agent", + "The worker name, version and website." ], [ - "client_agent", + "models", null ], [ "name", - null + "The Name of the Worker." + ], + [ + "nsfw", + "Whether this worker can generate NSFW requests or not." ], [ "priority_usernames", null ], [ - "nsfw", - null + "require_upfront_kudos", + "If True, this worker will only pick up requests where the owner has the required kudos to consume already available." ], [ - "models", - null + "threads", + "How many threads this worker is running. This is used to accurately the current power available in the horde." ], [ - "bridge_version", + "apikey", null ], [ - "bridge_agent", + "accept", null ], [ - "threads", + "client_agent", null ], [ - "require_upfront_kudos", + "bridge_version", null ], [ @@ -299,11 +403,11 @@ null ], [ - "allow_lora", + "allow_sdxl_controlnet", null ], [ - "amount", + "allow_lora", null ] ], @@ -359,7 +463,241 @@ null ] ], - "StatsImageModelsRequest": [ + "ImageStatsModelsRequest": [ + [ + "accept", + null + ], + [ + "client_agent", + null + ], + [ + "model_state", + "The state of the models to get stats for. Known models are models that are known to the system." + ] + ], + "ImageStatsModelsTotalRequest": [ + [ + "accept", + null + ], + [ + "client_agent", + null + ] + ], + "NewsRequest": [ + [ + "accept", + null + ], + [ + "client_agent", + null + ] + ], + "SingleWorkerDetailsRequest": [ + [ + "apikey", + null + ], + [ + "accept", + null + ], + [ + "client_agent", + null + ], + [ + "worker_id", + null + ] + ], + "TextGenerateAsyncRequest": [ + [ + "trusted_workers", + null + ], + [ + "slow_workers", + null + ], + [ + "workers", + null + ], + [ + "worker_blacklist", + null + ], + [ + "models", + null + ], + [ + "dry_run", + null + ], + [ + "apikey", + null + ], + [ + "accept", + null + ], + [ + "client_agent", + null + ], + [ + "params", + null + ], + [ + "prompt", + "The prompt which will be sent to KoboldAI to generate text." + ], + [ + "allow_downgrade", + "When true and the request requires upfront kudos and the account does not have enough The request will be downgraded in max context and max tokens so that it does not need upfront kudos." + ], + [ + "disable_batching", + "When true, This request will not use batching. This will allow you to retrieve accurate seeds. Feature is restricted to Trusted users and Patreons." + ], + [ + "extra_source_images", + null + ], + [ + "proxied_account", + "If using a service account as a proxy, provide this value to identify the actual account from which this request is coming from." + ], + [ + "softprompt", + "Specify which softprompt needs to be used to service this request." + ], + [ + "webhook", + "Provide a URL where the AI Horde will send a POST call after each delivered generation. The request will include the details of the job as well as the request ID." + ] + ], + "TextGenerateJobPopRequest": [ + [ + "amount", + "How many jobvs to pop at the same time" + ], + [ + "bridge_agent", + "The worker name, version and website." + ], + [ + "models", + null + ], + [ + "name", + "The Name of the Worker." + ], + [ + "nsfw", + "Whether this worker can generate NSFW requests or not." + ], + [ + "priority_usernames", + null + ], + [ + "require_upfront_kudos", + "If True, this worker will only pick up requests where the owner has the required kudos to consume already available." + ], + [ + "threads", + "How many threads this worker is running. This is used to accurately the current power available in the horde." + ], + [ + "max_length", + "The maximum amount of tokens this worker can generate." + ], + [ + "max_context_length", + "The max amount of context to submit to this AI for sampling." + ], + [ + "softprompts", + "The available softprompt files on this worker for the currently running model." + ], + [ + "apikey", + null + ], + [ + "accept", + null + ], + [ + "client_agent", + null + ] + ], + "TextGenerateStatusRequest": [ + [ + "id_", + null + ], + [ + "accept", + null + ], + [ + "client_agent", + null + ] + ], + "TextGenerationJobSubmitRequest": [ + [ + "apikey", + null + ], + [ + "id_", + null + ], + [ + "accept", + null + ], + [ + "client_agent", + null + ], + [ + "generation", + null + ], + [ + "state", + null + ], + [ + "gen_metadata", + null + ] + ], + "TextStatsModelsRequest": [ + [ + "accept", + null + ], + [ + "client_agent", + null + ] + ], + "TextStatsModelsTotalRequest": [ [ "accept", null diff --git a/docs/response_field_names_and_descriptions.json b/docs/response_field_names_and_descriptions.json index 6503534..f997245 100644 --- a/docs/response_field_names_and_descriptions.json +++ b/docs/response_field_names_and_descriptions.json @@ -1,4 +1,14 @@ { + "AIHordeHeartbeatResponse": [ + [ + "message", + null + ], + [ + "version", + null + ] + ], "AlchemyAsyncResponse": [ [ "message", @@ -27,6 +37,12 @@ null ] ], + "AlchemyJobSubmitResponse": [ + [ + "reward", + null + ] + ], "AlchemyPopResponse": [ [ "forms", @@ -141,6 +157,96 @@ null ] ], + "TextGenerateStatusResponse": [ + [ + "finished", + null + ], + [ + "processing", + null + ], + [ + "restarted", + null + ], + [ + "waiting", + null + ], + [ + "done", + null + ], + [ + "faulted", + null + ], + [ + "wait_time", + null + ], + [ + "queue_position", + null + ], + [ + "kudos", + null + ], + [ + "is_possible", + null + ], + [ + "generations", + "The generations that have been completed in this request." + ], + [ + "finished", + null + ], + [ + "processing", + null + ], + [ + "restarted", + null + ], + [ + "waiting", + null + ], + [ + "done", + null + ], + [ + "faulted", + null + ], + [ + "wait_time", + null + ], + [ + "queue_position", + null + ], + [ + "kudos", + null + ], + [ + "is_possible", + null + ], + [ + "generations", + "The generations that have been completed in this request." + ] + ], "FindUserResponse": [ [ "admin_comment", @@ -162,6 +268,10 @@ "contributions", null ], + [ + "customizer", + "If this user can run custom models." + ], [ "evaluating_kudos", "(Privileged) The amount of Evaluating Kudos this untrusted user has from generations and uptime. When this number reaches a pre-specified threshold, they automatically become trusted." @@ -247,6 +357,72 @@ "Whether this user has been invited to join a worker to the horde and how many of them. When 0, this user cannot add (new) workers to the horde." ] ], + "HordePerformanceResponse": [ + [ + "interrogator_count", + "How many workers are actively processing image interrogations in this {horde_noun} in the past 5 minutes." + ], + [ + "interrogator_thread_count", + "How many worker threads are actively processing image interrogation in this {horde_noun} in the past 5 minutes." + ], + [ + "past_minute_megapixelsteps", + "How many megapixelsteps this horde generated in the last minute." + ], + [ + "past_minute_tokens", + "How many tokens this horde generated in the last minute." + ], + [ + "queued_forms", + "The amount of image interrogations waiting and processing currently in this horde." + ], + [ + "queued_megapixelsteps", + "The amount of megapixelsteps in waiting and processing requests currently in this horde." + ], + [ + "queued_requests", + "The amount of waiting and processing image requests currently in this horde." + ], + [ + "queued_text_requests", + "The amount of waiting and processing text requests currently in this horde." + ], + [ + "queued_tokens", + "The amount of tokens in waiting and processing requests currently in this horde." + ], + [ + "text_thread_count", + "How many worker threads are actively processing prompt generations in this {horde_noun} in the past 5 minutes." + ], + [ + "text_worker_count", + "How many workers are actively processing prompt generations in this horde in the past 5 minutes." + ], + [ + "thread_count", + "How many worker threads are actively processing prompt generations in this {horde_noun} in the past 5 minutes." + ], + [ + "worker_count", + "How many workers are actively processing prompt generations in this horde in the past 5 minutes." + ] + ], + "HordeStatusModelsAllResponse": [ + [ + "root", + null + ] + ], + "HordeStatusModelsSingleResponse": [ + [ + "root", + null + ] + ], "ImageGenerateAsyncDryRunResponse": [ [ "kudos", @@ -314,6 +490,10 @@ ] ], "ImageGenerateJobPopResponse": [ + [ + "extra_source_images", + null + ], [ "id_", null @@ -346,10 +526,6 @@ "source_mask", null ], - [ - "extra_source_images", - null - ], [ "r2_upload", null @@ -360,12 +536,272 @@ ] ], "JobSubmitResponse": [ + [ + "reward", + null + ], [ "reward", null ] ], - "StatsModelsResponse": [ + "ImageModelStatsResponse": [ + [ + "day", + null + ], + [ + "month", + null + ], + [ + "total", + null + ] + ], + "ImageStatsModelsTotalResponse": [ + [ + "day", + null + ], + [ + "hour", + null + ], + [ + "minute", + null + ], + [ + "month", + null + ], + [ + "total", + null + ] + ], + "NewsResponse": [ + [ + "root", + null + ] + ], + "SingleWorkerDetailsResponse": [ + [ + "type_", + null + ], + [ + "name", + null + ], + [ + "id_", + null + ], + [ + "online", + null + ], + [ + "requests_fulfilled", + null + ], + [ + "kudos_rewards", + null + ], + [ + "kudos_details", + null + ], + [ + "performance", + null + ], + [ + "threads", + null + ], + [ + "uptime", + null + ], + [ + "maintenance_mode", + null + ], + [ + "paused", + null + ], + [ + "info", + null + ], + [ + "nsfw", + null + ], + [ + "owner", + null + ], + [ + "ipaddr", + null + ], + [ + "trusted", + null + ], + [ + "flagged", + null + ], + [ + "suspicious", + null + ], + [ + "uncompleted_jobs", + null + ], + [ + "models", + null + ], + [ + "forms", + null + ], + [ + "team", + null + ], + [ + "contact", + null + ], + [ + "bridge_agent", + null + ], + [ + "max_pixels", + null + ], + [ + "megapixelsteps_generated", + null + ], + [ + "img2img", + null + ], + [ + "painting", + null + ], + [ + "post_processing", + null + ], + [ + "lora", + null + ], + [ + "max_length", + null + ], + [ + "max_context_length", + null + ], + [ + "tokens_generated", + null + ] + ], + "TextGenerateAsyncDryRunResponse": [ + [ + "kudos", + null + ] + ], + "TextGenerateAsyncResponse": [ + [ + "message", + null + ], + [ + "id_", + null + ], + [ + "kudos", + "The expected kudos consumption for this request." + ], + [ + "warnings", + null + ] + ], + "TextGenerateJobPopResponse": [ + [ + "extra_source_images", + null + ], + [ + "payload", + "The settings for this text generation." + ], + [ + "id_", + "The UUID for this text generation." + ], + [ + "ids", + "The UUIDs for this text generations." + ], + [ + "skipped", + "The skipped requests that were not valid for this worker." + ], + [ + "softprompt", + "The soft prompt requested for this generation." + ], + [ + "model", + "The model requested for this generation." + ] + ], + "TextModelStatsResponse": [ + [ + "day", + null + ], + [ + "month", + null + ], + [ + "total", + null + ] + ], + "TextStatsModelsTotalResponse": [ + [ + "minute", + null + ], + [ + "hour", + null + ], [ "day", null diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index 3ba6ed8..4324dd1 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -1,28 +1,3 @@ -/* .md-main .md-grid { - max-width: 80rem; -} - -.md-content { - min-width: 60%; -} - -.md-sidebar { - width: unset; -} - -.md-sidebar.md-sidebar--secondary { - width: 30%; -} - -.md-sidebar.md-sidebar--primary .md-sidebar__scrollwrap .md-sidebar__inner { - padding-right: calc(100% - 14rem); -} - - -.md-sidebar.md-sidebar--secondary .md-sidebar__scrollwrap .md-sidebar__inner { - padding-right: calc(100% - 20rem); -} */ - .doc-object-name { font-weight: bold; letter-spacing: 0.0em; diff --git a/horde_sdk/__init__.py b/horde_sdk/__init__.py index 4029df7..a0ac6f6 100644 --- a/horde_sdk/__init__.py +++ b/horde_sdk/__init__.py @@ -11,7 +11,7 @@ import os # We import the horde_sdk logging module first so that we can use it to configure the logging system before importing -from horde_sdk.logging import COMPLETE_LOGGER_LABEL, PROGRESS_LOGGER_LABEL +from horde_sdk.horde_logging import COMPLETE_LOGGER_LABEL, PROGRESS_LOGGER_LABEL from loguru import logger @@ -66,14 +66,14 @@ def _dev_env_var_warnings() -> None: # pragma: no cover from horde_sdk.exceptions import HordeException from horde_sdk.generic_api.apimodels import ( APIKeyAllowedInRequestMixin, - BaseModel, ContainsMessageResponseMixin, + HordeAPIDataObject, HordeAPIMessage, HordeAPIObject, HordeRequest, RequestErrorResponse, RequestSpecifiesUserIDMixin, - RequestUsesImageWorkerMixin, + RequestUsesWorkerMixin, ResponseRequiringFollowUpMixin, ResponseWithProgressMixin, ) @@ -88,14 +88,14 @@ def _dev_env_var_warnings() -> None: # pragma: no cover "is_error_status_code", "is_success_status_code", "APIKeyAllowedInRequestMixin", - "BaseModel", "HordeRequest", "ContainsMessageResponseMixin", + "HordeAPIDataObject", "HordeAPIMessage", "HordeAPIObject", "RequestErrorResponse", "RequestSpecifiesUserIDMixin", - "RequestUsesImageWorkerMixin", + "RequestUsesWorkerMixin", "ResponseRequiringFollowUpMixin", "ResponseWithProgressMixin", "ANON_API_KEY", diff --git a/horde_sdk/ai_horde_api/ai_horde_clients.py b/horde_sdk/ai_horde_api/ai_horde_clients.py index b549cf8..0975bcf 100644 --- a/horde_sdk/ai_horde_api/ai_horde_clients.py +++ b/horde_sdk/ai_horde_api/ai_horde_clients.py @@ -12,6 +12,7 @@ from abc import ABC, abstractmethod from collections.abc import Callable, Coroutine from enum import auto +from typing import cast import aiohttp import PIL.Image @@ -19,7 +20,7 @@ from loguru import logger from strenum import StrEnum -from horde_sdk import COMPLETE_LOGGER_LABEL, PROGRESS_LOGGER_LABEL, ContainsMessageResponseMixin, HordeRequest +from horde_sdk import COMPLETE_LOGGER_LABEL, PROGRESS_LOGGER_LABEL from horde_sdk.ai_horde_api.apimodels import ( AlchemyAsyncRequest, AlchemyStatusResponse, @@ -38,8 +39,10 @@ from horde_sdk.ai_horde_api.endpoints import AI_HORDE_BASE_URL from horde_sdk.ai_horde_api.exceptions import AIHordeImageValidationError, AIHordeRequestError from horde_sdk.ai_horde_api.fields import JobID -from horde_sdk.ai_horde_api.metadata import AIHordePathData +from horde_sdk.ai_horde_api.metadata import AIHordePathData, AIHordeQueryData from horde_sdk.generic_api.apimodels import ( + ContainsMessageResponseMixin, + HordeRequest, HordeResponse, RequestErrorResponse, ResponseRequiringFollowUpMixin, @@ -188,6 +191,7 @@ def __init__(self) -> None: """Create a new instance of the AIHordeAPIManualClient.""" super().__init__( path_fields=AIHordePathData, + query_fields=AIHordeQueryData, ) def get_generate_check( @@ -269,6 +273,7 @@ def __init__(self, aiohttp_session: aiohttp.ClientSession) -> None: super().__init__( aiohttp_session=aiohttp_session, path_fields=AIHordePathData, + query_fields=AIHordeQueryData, ) async def get_generate_check( @@ -354,6 +359,7 @@ def __init__(self) -> None: """Create a new instance of the RatingsAPIClient.""" super().__init__( path_fields=AIHordePathData, + query_fields=AIHordeQueryData, ) @@ -373,10 +379,11 @@ def __init__( super().__init__( aiohttp_session=aiohttp_session, path_fields=AIHordePathData, + query_fields=AIHordeQueryData, ) -class _PROGRESS_STATE(StrEnum): +class PROGRESS_STATE(StrEnum): waiting = auto() finished = auto() timed_out = auto() @@ -500,9 +507,9 @@ def _handle_progress_response( number_of_responses: int, start_time: float, timeout: int, - check_callback: Callable | None = None, + check_callback: Callable[[HordeResponse], None] | None = None, check_callback_type: type[ResponseWithProgressMixin | ResponseGenerationProgressCombinedMixin] | None = None, - ) -> _PROGRESS_STATE: + ) -> PROGRESS_STATE: """Handle a response from the API when checking the progress of a request. Typically, this is a response from a `check` or `status` request. @@ -541,17 +548,17 @@ def _handle_progress_response( # If the number of finished images is equal to the number of images requested, we're done if check_response.is_job_complete(number_of_responses): logger.log(PROGRESS_LOGGER_LABEL, f"Job finished and available on the server: {job_id}") - return _PROGRESS_STATE.finished + return PROGRESS_STATE.finished # If we've timed out, stop waiting, log a warning, and break out of the loop if timeout and timeout > 0 and time.time() - start_time > timeout: logger.warning( f"Timeout reached, cancelling generations still outstanding: {job_id}: {check_response}:", ) - return _PROGRESS_STATE.timed_out + return PROGRESS_STATE.timed_out # If the job is not complete and the timeout has not been reached, continue waiting - return _PROGRESS_STATE.waiting + return PROGRESS_STATE.waiting class AIHordeAPISimpleClient(BaseAIHordeSimpleClient): @@ -597,7 +604,7 @@ def _do_request_with_check( *, number_of_responses: int = 1, timeout: int = GENERATION_MAX_LIFE, - check_callback: Callable | None = None, + check_callback: Callable[[HordeResponse], None] | None = None, check_callback_type: type[ResponseWithProgressMixin | ResponseGenerationProgressCombinedMixin] | None = None, ) -> tuple[HordeResponse, JobID]: """Submit a request which requires check/status polling to the AI-Horde API, and wait for it to complete. @@ -657,7 +664,7 @@ def _do_request_with_check( check_callback_type=check_callback_type, ) - if progress_state == _PROGRESS_STATE.finished or progress_state == _PROGRESS_STATE.timed_out: + if progress_state == PROGRESS_STATE.finished or progress_state == PROGRESS_STATE.timed_out: break # Wait for 4 seconds before checking again @@ -722,6 +729,11 @@ def image_generate_request( RuntimeError: If the image couldn't be downloaded or parsed for any other reason. """ + # `cast()` returns the value unchanged but tells coerces the type for mypy's benefit + # Static type checkers can't see that `_do_request_with_check` is reliably passing an object of the correct + # type, but we are guaranteed that it is due to the `ImageGenerateCheckResponse` type being passed as an arg. + generic_callback = cast(Callable[[HordeResponse], None], check_callback) + timeout = self.validate_timeout(timeout, log_message=True) n = image_gen_request.params.n if image_gen_request.params and image_gen_request.params.n else 1 @@ -730,7 +742,7 @@ def image_generate_request( image_gen_request, number_of_responses=n, timeout=timeout, - check_callback=check_callback, + check_callback=generic_callback, check_callback_type=ImageGenerateCheckResponse, ) @@ -781,6 +793,11 @@ def alchemy_request( Raises: AIHordeRequestError: If the request failed. The error response is included in the exception. """ + # `cast()` returns the value unchanged but tells coerces the type for mypy's benefit + # Static type checkers can't see that `_do_request_with_check` is reliably passing an object of the correct + # type, but we are guaranteed that it is due to the `ImageGenerateCheckResponse` type being passed as an arg. + generic_callback = cast(Callable[[HordeResponse], None], check_callback) + timeout = self.validate_timeout(timeout, log_message=True) logger.log(PROGRESS_LOGGER_LABEL, f"Requesting {len(alchemy_request.forms)} alchemy requests.") @@ -791,7 +808,7 @@ def alchemy_request( alchemy_request, number_of_responses=len(alchemy_request.forms), timeout=timeout, - check_callback=check_callback, + check_callback=generic_callback, ) if isinstance(response, RequestErrorResponse): # pragma: no cover @@ -897,7 +914,7 @@ async def _do_request_with_check( *, number_of_responses: int = 1, timeout: int = GENERATION_MAX_LIFE, - check_callback: Callable | None = None, + check_callback: Callable[[HordeResponse], None] | None = None, check_callback_type: type[ResponseWithProgressMixin | ResponseGenerationProgressCombinedMixin] | None = None, ) -> tuple[HordeResponse, JobID]: """Submit a request which requires check/status polling to the AI-Horde API, and wait for it to complete. @@ -906,7 +923,7 @@ async def _do_request_with_check( api_request (BaseAIHordeRequest): The request to submit. number_of_responses (int, optional): The number of responses to expect. Defaults to 1. timeout (int, optional): The number of seconds to wait before aborting. - returns any completed images at the end of the timeout. Defaults to DEFAULT_GENERATION_TIMEOUT. + returns any completed images at the end of the timeout. Defaults to GENERATION_MAX_LIFE. Returns: tuple[HordeResponse, JobID]: The final response and the corresponding job ID. @@ -918,7 +935,7 @@ async def _do_request_with_check( if check_callback is not None and len(inspect.getfullargspec(check_callback).args) == 0: raise ValueError("Callback must take at least one argument") - context: contextlib.AbstractContextManager | AIHordeAPIAsyncClientSession + context: contextlib.nullcontext[None] | AIHordeAPIAsyncClientSession ai_horde_session: AIHordeAPIAsyncClientSession if self._horde_client_session is not None: @@ -928,9 +945,11 @@ async def _do_request_with_check( elif self._aiohttp_session is not None: ai_horde_session = AIHordeAPIAsyncClientSession(self._aiohttp_session) context = ai_horde_session + else: + raise RuntimeError("No aiohttp session or AIHordeAPIAsyncClientSession provided") # This session class will cleanup incomplete requests in the event of an exception - async with context: # type: ignore + async with context: # Submit the initial request logger.debug( f"Submitting request: {api_request.log_safe_model_dump()} with timeout {timeout}", @@ -971,7 +990,7 @@ async def _do_request_with_check( check_callback_type=check_callback_type, ) - if progress_state == _PROGRESS_STATE.finished or progress_state == _PROGRESS_STATE.timed_out: + if progress_state == PROGRESS_STATE.finished or progress_state == PROGRESS_STATE.timed_out: break # Wait for 4 seconds before checking again @@ -1044,6 +1063,10 @@ async def image_generate_request( Raises: AIHordeRequestError: If the request failed. The error response is included in the exception. """ + # `cast()` returns the value unchanged but tells coerces the type for mypy's benefit + # Static type checkers can't see that `_do_request_with_check` is reliably passing an object of the correct + # type, but we are guaranteed that it is due to the `ImageGenerateCheckResponse` type being passed as an arg. + generic_callback = cast(Callable[[HordeResponse], None], check_callback) await asyncio.sleep(delay) @@ -1054,7 +1077,7 @@ async def image_generate_request( image_gen_request, number_of_responses=n, timeout=timeout, - check_callback=check_callback, + check_callback=generic_callback, check_callback_type=ImageGenerateCheckResponse, ) @@ -1086,13 +1109,18 @@ async def alchemy_request( Raises: AIHordeRequestError: If the request failed. The error response is included in the exception. """ + # `cast()` returns the value unchanged but tells coerces the type for mypy's benefit + # Static type checkers can't see that `_do_request_with_check` is reliably passing an object of the correct + # type, but we are guaranteed that it is due to the `ImageGenerateCheckResponse` type being passed as an arg. + generic_callback = cast(Callable[[HordeResponse], None], check_callback) + timeout = self.validate_timeout(timeout, log_message=True) response, job_id = await self._do_request_with_check( alchemy_request, number_of_responses=len(alchemy_request.forms), timeout=timeout, - check_callback=check_callback, + check_callback=generic_callback, check_callback_type=AlchemyStatusResponse, ) if isinstance(response, RequestErrorResponse): # pragma: no cover diff --git a/horde_sdk/ai_horde_api/apimodels/__init__.py b/horde_sdk/ai_horde_api/apimodels/__init__.py index bd637f6..c0bfec2 100644 --- a/horde_sdk/ai_horde_api/apimodels/__init__.py +++ b/horde_sdk/ai_horde_api/apimodels/__init__.py @@ -11,9 +11,36 @@ UserRecords, UserThingRecords, ) -from horde_sdk.ai_horde_api.apimodels._stats import StatsImageModelsRequest, StatsModelsResponse, StatsModelsTimeframe +from horde_sdk.ai_horde_api.apimodels._stats import ( + ImageModelStatsResponse, + ImageStatsModelsRequest, + ImageStatsModelsTotalRequest, + ImageStatsModelsTotalResponse, + SinglePeriodImgStat, + SinglePeriodTxtStat, + StatsModelsTimeframe, + TextModelStatsResponse, + TextStatsModelsRequest, + TextStatsModelsTotalRequest, + TextStatsModelsTotalResponse, +) +from horde_sdk.ai_horde_api.apimodels._status import ( + ActiveModel, + ActiveModelLite, + AIHordeHeartbeatRequest, + AIHordeHeartbeatResponse, + HordeModes, + HordePerformanceRequest, + HordePerformanceResponse, + HordeStatusModelsAllRequest, + HordeStatusModelsAllResponse, + HordeStatusModelsSingleRequest, + HordeStatusModelsSingleResponse, + Newspiece, + NewsRequest, + NewsResponse, +) from horde_sdk.ai_horde_api.apimodels.alchemy._async import ( - KNOWN_ALCHEMY_TYPES, AlchemyAsyncRequest, AlchemyAsyncRequestFormItem, AlchemyAsyncResponse, @@ -29,6 +56,7 @@ AlchemyCaptionResult, AlchemyDeleteRequest, AlchemyFormStatus, + AlchemyInterrogationDetails, AlchemyInterrogationResult, AlchemyInterrogationResultItem, AlchemyNSFWResult, @@ -36,11 +64,19 @@ AlchemyStatusResponse, AlchemyUpscaleResult, ) +from horde_sdk.ai_horde_api.apimodels.alchemy._submit import AlchemyJobSubmitRequest, AlchemyJobSubmitResponse from horde_sdk.ai_horde_api.apimodels.base import ( ExtraSourceImageEntry, + ExtraTextEntry, GenMetadataEntry, + ImageGenerateParamMixin, + JobRequestMixin, + JobResponseMixin, + JobSubmitResponse, LorasPayloadEntry, + SingleWarningEntry, TIPayloadEntry, + WorkerRequestMixin, ) from horde_sdk.ai_horde_api.apimodels.generate._async import ( ImageGenerateAsyncDryRunResponse, @@ -54,6 +90,7 @@ ImageGenerateJobPopRequest, ImageGenerateJobPopResponse, ImageGenerateJobPopSkippedStatus, + PopInput, ) from horde_sdk.ai_horde_api.apimodels.generate._progress import ( ResponseGenerationProgressCombinedMixin, @@ -67,63 +104,146 @@ ) from horde_sdk.ai_horde_api.apimodels.generate._submit import ( ImageGenerationJobSubmitRequest, - JobSubmitResponse, ) -from horde_sdk.ai_horde_api.apimodels.workers._workers_all import AllWorkersDetailsRequest, AllWorkersDetailsResponse +from horde_sdk.ai_horde_api.apimodels.generate.text._async import ( + ModelGenerationInputKobold, + ModelPayloadRootKobold, + TextGenerateAsyncDryRunResponse, + TextGenerateAsyncRequest, + TextGenerateAsyncResponse, +) +from horde_sdk.ai_horde_api.apimodels.generate.text._pop import ( + ModelPayloadKobold, + NoValidRequestFoundKobold, + TextGenerateJobPopRequest, + TextGenerateJobPopResponse, + _PopInputKobold, +) +from horde_sdk.ai_horde_api.apimodels.generate.text._status import ( + DeleteTextGenerateRequest, + GenerationKobold, + TextGenerateStatusRequest, + TextGenerateStatusResponse, +) +from horde_sdk.ai_horde_api.apimodels.generate.text._submit import ( + TextGenerationJobSubmitRequest, +) +from horde_sdk.ai_horde_api.apimodels.workers._workers import ( + AllWorkersDetailsRequest, + AllWorkersDetailsResponse, + SingleWorkerDetailsRequest, + SingleWorkerDetailsResponse, + TeamDetailsLite, + WorkerDetailItem, + WorkerKudosDetails, +) +from horde_sdk.ai_horde_api.consts import KNOWN_ALCHEMY_TYPES __all__ = [ + "ContributionsDetails", + "FindUserRequest", + "FindUserResponse", + "MonthlyKudos", + "UsageDetails", + "UserAmountRecords", + "UserKudosDetails", + "UserRecords", + "UserThingRecords", + "ImageStatsModelsRequest", + "ImageStatsModelsTotalRequest", + "ImageStatsModelsTotalResponse", + "HordeModes", + "HordePerformanceRequest", + "HordePerformanceResponse", + "HordeStatusModelsAllRequest", + "HordeStatusModelsAllResponse", + "HordeStatusModelsSingleRequest", + "HordeStatusModelsSingleResponse", + "Newspiece", + "NewsRequest", + "NewsResponse", + "ActiveModel", + "ActiveModelLite", + "SinglePeriodImgStat", + "SinglePeriodTxtStat", + "ImageModelStatsResponse", + "StatsModelsTimeframe", + "TextModelStatsResponse", + "TextStatsModelsRequest", + "TextStatsModelsTotalRequest", + "TextStatsModelsTotalResponse", + "AIHordeHeartbeatRequest", + "AIHordeHeartbeatResponse", + "KNOWN_ALCHEMY_TYPES", "AlchemyAsyncRequest", "AlchemyAsyncRequestFormItem", "AlchemyAsyncResponse", + "AlchemyFormPayloadStable", + "AlchemyPopFormPayload", + "AlchemyPopRequest", + "AlchemyPopResponse", + "NoValidAlchemyFound", "AlchemyCaptionResult", "AlchemyDeleteRequest", - "AlchemyFormPayloadStable", "AlchemyFormStatus", + "AlchemyInterrogationDetails", "AlchemyInterrogationResult", "AlchemyInterrogationResultItem", "AlchemyNSFWResult", - "AlchemyPopFormPayload", - "AlchemyPopRequest", - "AlchemyPopResponse", "AlchemyStatusRequest", "AlchemyStatusResponse", "AlchemyUpscaleResult", - "AllWorkersDetailsRequest", - "AllWorkersDetailsResponse", - "ContributionsDetails", - "DeleteImageGenerateRequest", - "FindUserRequest", - "FindUserResponse", + "AlchemyJobSubmitRequest", + "AlchemyJobSubmitResponse", + "ExtraSourceImageEntry", + "ExtraTextEntry", + "GenMetadataEntry", + "ImageGenerateParamMixin", + "JobRequestMixin", + "JobResponseMixin", + "LorasPayloadEntry", + "SingleWarningEntry", + "TIPayloadEntry", + "WorkerRequestMixin", + "ImageGenerateAsyncDryRunResponse", "ImageGenerateAsyncRequest", "ImageGenerateAsyncResponse", + "ImageGenerationInputPayload", "ImageGenerateCheckRequest", "ImageGenerateCheckResponse", - "ImageGenerateAsyncDryRunResponse", - "ImageGeneration", - "ImageGenerationInputPayload", - "ImageGenerateJobPopRequest", "ImageGenerateJobPopPayload", - "ImageGenerateJobPopSkippedStatus", + "ImageGenerateJobPopRequest", "ImageGenerateJobPopResponse", + "ImageGenerateJobPopSkippedStatus", + "PopInput", + "ResponseGenerationProgressCombinedMixin", + "ResponseGenerationProgressInfoMixin", + "DeleteImageGenerateRequest", "ImageGenerateStatusRequest", "ImageGenerateStatusResponse", + "ImageGeneration", "ImageGenerationJobSubmitRequest", "JobSubmitResponse", - "KNOWN_ALCHEMY_TYPES", - "LorasPayloadEntry", - "MonthlyKudos", - "NoValidAlchemyFound", - "StatsImageModelsRequest", - "StatsModelsResponse", - "StatsModelsTimeframe", - "TIPayloadEntry", - "ExtraSourceImageEntry", - "GenMetadataEntry", - "UsageDetails", - "UserAmountRecords", - "UserKudosDetails", - "UserRecords", - "UserThingRecords", - "ResponseGenerationProgressInfoMixin", - "ResponseGenerationProgressCombinedMixin", + "ModelGenerationInputKobold", + "ModelPayloadRootKobold", + "TextGenerateAsyncRequest", + "TextGenerateAsyncResponse", + "ModelPayloadKobold", + "NoValidRequestFoundKobold", + "TextGenerateJobPopRequest", + "TextGenerateJobPopResponse", + "_PopInputKobold", + "TextGenerateAsyncDryRunResponse", + "DeleteTextGenerateRequest", + "GenerationKobold", + "TextGenerateStatusRequest", + "TextGenerateStatusResponse", + "TextGenerationJobSubmitRequest", + "AllWorkersDetailsRequest", + "AllWorkersDetailsResponse", + "SingleWorkerDetailsRequest", + "SingleWorkerDetailsResponse", + "TeamDetailsLite", + "WorkerDetailItem", + "WorkerKudosDetails", ] diff --git a/horde_sdk/ai_horde_api/apimodels/_find_user.py b/horde_sdk/ai_horde_api/apimodels/_find_user.py index 91689d3..a1c9e79 100644 --- a/horde_sdk/ai_horde_api/apimodels/_find_user.py +++ b/horde_sdk/ai_horde_api/apimodels/_find_user.py @@ -7,6 +7,7 @@ from horde_sdk.ai_horde_api.endpoints import AI_HORDE_API_ENDPOINT_SUBPATH from horde_sdk.consts import HTTPMethod from horde_sdk.generic_api.apimodels import APIKeyAllowedInRequestMixin, HordeAPIDataObject, HordeResponseBaseModel +from horde_sdk.generic_api.decoration import Unequatable, Unhashable class ContributionsDetails(HordeAPIDataObject): @@ -64,6 +65,8 @@ class UsageDetails(HordeAPIDataObject): requests: int | None = Field(default=None, description="How many images this user has requested.") +@Unhashable +@Unequatable class FindUserResponse(HordeResponseBaseModel): @override @classmethod @@ -92,6 +95,12 @@ def get_api_model_name(cls) -> str | None: contributions: ContributionsDetails | None = None """How many images and megapixelsteps this user has generated.""" + customizer: bool | None = Field( + default=None, + description="If this user can run custom models.", + examples=[False], + ) + evaluating_kudos: float | None = Field( default=None, description=( @@ -193,12 +202,6 @@ def get_api_model_name(cls) -> str | None: """Whether this user has been invited to join a worker to the horde and how many of them. When 0, this user cannot add (new) workers to the horde.""" - def __eq__(self, other: object) -> bool: - raise NotImplementedError("TODO") - - def __hash__(self) -> int: - raise NotImplementedError("TODO") - class FindUserRequest(BaseAIHordeRequest, APIKeyAllowedInRequestMixin): @override @@ -213,10 +216,10 @@ def get_api_endpoint_subpath(cls) -> AI_HORDE_API_ENDPOINT_SUBPATH: @override @classmethod - def get_http_method(self) -> HTTPMethod: + def get_http_method(cls) -> HTTPMethod: return HTTPMethod.GET @override @classmethod - def get_default_success_response_type(self) -> type[FindUserResponse]: + def get_default_success_response_type(cls) -> type[FindUserResponse]: return FindUserResponse diff --git a/horde_sdk/ai_horde_api/apimodels/_stats.py b/horde_sdk/ai_horde_api/apimodels/_stats.py index 968213e..ae82340 100644 --- a/horde_sdk/ai_horde_api/apimodels/_stats.py +++ b/horde_sdk/ai_horde_api/apimodels/_stats.py @@ -1,13 +1,15 @@ from enum import auto -from pydantic import field_validator +from pydantic import ConfigDict, Field, field_validator from strenum import StrEnum from typing_extensions import override from horde_sdk.ai_horde_api.apimodels.base import BaseAIHordeRequest +from horde_sdk.ai_horde_api.consts import MODEL_STATE from horde_sdk.ai_horde_api.endpoints import AI_HORDE_API_ENDPOINT_SUBPATH from horde_sdk.consts import HTTPMethod -from horde_sdk.generic_api.apimodels import HordeResponseBaseModel +from horde_sdk.generic_api.apimodels import HordeAPIDataObject, HordeResponseBaseModel +from horde_sdk.generic_api.decoration import Unequatable, Unhashable class StatsModelsTimeframe(StrEnum): @@ -16,7 +18,9 @@ class StatsModelsTimeframe(StrEnum): total = auto() -class StatsModelsResponse(HordeResponseBaseModel): +@Unequatable +@Unhashable +class ImageModelStatsResponse(HordeResponseBaseModel): """Represents the data returned from the `/v2/stats/img/models` endpoint. v2 API Model: `ImgModelStats` @@ -76,15 +80,18 @@ def get_timeframe(self, timeframe: StatsModelsTimeframe) -> dict[str, int]: raise ValueError(f"Invalid timeframe: {timeframe}") - def __eq__(self, other: object) -> bool: - raise NotImplementedError("Cannot compare StatsModelsResponse objects") - def __hash__(self) -> int: - raise NotImplementedError("Cannot hash StatsModelsResponse objects") +class ImageStatsModelsRequest(BaseAIHordeRequest): + """Represents the data needed to make a request to the `/v2/stats/img/models` endpoint.""" + model_config = ConfigDict( + protected_namespaces=(), # Allows the "model_" prefix on attrs + ) -class StatsImageModelsRequest(BaseAIHordeRequest): - """Represents the data needed to make a request to the `/v2/stats/img/models` endpoint.""" + model_state: MODEL_STATE = Field( + MODEL_STATE.all, + description="The state of the models to get stats for. Known models are models that are known to the system.", + ) @override @classmethod @@ -103,5 +110,166 @@ def get_api_endpoint_subpath(cls) -> AI_HORDE_API_ENDPOINT_SUBPATH: @override @classmethod - def get_default_success_response_type(cls) -> type[StatsModelsResponse]: - return StatsModelsResponse + def get_default_success_response_type(cls) -> type[ImageModelStatsResponse]: + return ImageModelStatsResponse + + +class SinglePeriodImgStat(HordeAPIDataObject): + images: int | None = Field(None, description="The amount of images generated during this period.") + ps: int | None = Field(None, description="The amount of pixelsteps generated during this period.") + + @property + def mps(self) -> int | None: + """The amount of megapixelsteps generated during this period.""" + if self.ps is None: + return None + + return self.ps // 1_000_000 + + +class ImageStatsModelsTotalResponse(HordeResponseBaseModel): + """Represents the data returned from the `/v2/stats/img/totals` endpoint.""" + + day: SinglePeriodImgStat | None = None + hour: SinglePeriodImgStat | None = None + minute: SinglePeriodImgStat | None = None + month: SinglePeriodImgStat | None = None + total: SinglePeriodImgStat | None = None + + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return "StatsImgTotals" + + +class ImageStatsModelsTotalRequest(BaseAIHordeRequest): + """Represents the data needed to make a request to the `/v2/stats/img/totals` endpoint.""" + + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return None + + @override + @classmethod + def get_http_method(cls) -> HTTPMethod: + return HTTPMethod.GET + + @override + @classmethod + def get_api_endpoint_subpath(cls) -> AI_HORDE_API_ENDPOINT_SUBPATH: + return AI_HORDE_API_ENDPOINT_SUBPATH.v2_stats_img_totals + + @override + @classmethod + def get_default_success_response_type(cls) -> type[ImageStatsModelsTotalResponse]: + return ImageStatsModelsTotalResponse + + +@Unhashable +class TextModelStatsResponse(HordeResponseBaseModel): + + day: dict[str, int] + month: dict[str, int] + total: dict[str, int] + + @field_validator("day", "month", "total", mode="before") + @classmethod + def validate_timeframe_data(cls, v: dict[str, int | None]) -> dict[str, int]: + """Validates the data for a timeframe. + + Args: + v (dict[str, int | None]): The data for a timeframe. + + Raises: + ValueError: If the data is invalid. + + Returns: + dict[str, int]: The data for a timeframe. + """ + if v is None: + return {} + + return_v = {} + # Replace all `None` values with 0 + for key, value in v.items(): + if value is None: + return_v[key] = 0 + else: + return_v[key] = value + + return return_v + + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return "TxtModelStats" + + +class TextStatsModelsRequest(BaseAIHordeRequest): + """Represents the data needed to make a request to the `/v2/stats/text/models` endpoint.""" + + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return None + + @override + @classmethod + def get_http_method(cls) -> HTTPMethod: + return HTTPMethod.GET + + @override + @classmethod + def get_api_endpoint_subpath(cls) -> AI_HORDE_API_ENDPOINT_SUBPATH: + return AI_HORDE_API_ENDPOINT_SUBPATH.v2_stats_text_models + + @override + @classmethod + def get_default_success_response_type(cls) -> type[TextModelStatsResponse]: + return TextModelStatsResponse + + +class SinglePeriodTxtStat(HordeAPIDataObject): + requests: int | None = Field(None, description="The number of requests made during this period.") + tokens: int | None = Field(None, description="The number of tokens generated during this period.") + + +@Unhashable +class TextStatsModelsTotalResponse(HordeResponseBaseModel): + """Represents the data returned from the `/v2/stats/text/totals` endpoint.""" + + minute: dict[str, int] + hour: dict[str, int] + day: dict[str, int] + month: dict[str, int] + total: dict[str, int] + + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return "StatsTxtTotals" + + +class TextStatsModelsTotalRequest(BaseAIHordeRequest): + """Represents the data needed to make a request to the `/v2/stats/text/totals` endpoint.""" + + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return None + + @override + @classmethod + def get_http_method(cls) -> HTTPMethod: + return HTTPMethod.GET + + @override + @classmethod + def get_api_endpoint_subpath(cls) -> AI_HORDE_API_ENDPOINT_SUBPATH: + return AI_HORDE_API_ENDPOINT_SUBPATH.v2_stats_text_totals + + @override + @classmethod + def get_default_success_response_type(cls) -> type[TextStatsModelsTotalResponse]: + return TextStatsModelsTotalResponse diff --git a/horde_sdk/ai_horde_api/apimodels/_status.py b/horde_sdk/ai_horde_api/apimodels/_status.py new file mode 100644 index 0000000..fcdb124 --- /dev/null +++ b/horde_sdk/ai_horde_api/apimodels/_status.py @@ -0,0 +1,351 @@ +from collections.abc import Iterator + +from pydantic import ConfigDict, Field, RootModel +from typing_extensions import override + +from horde_sdk.ai_horde_api.apimodels.base import BaseAIHordeRequest +from horde_sdk.ai_horde_api.consts import MODEL_STATE, MODEL_TYPE +from horde_sdk.ai_horde_api.endpoints import AI_HORDE_API_ENDPOINT_SUBPATH +from horde_sdk.consts import HTTPMethod +from horde_sdk.generic_api.apimodels import ( + ContainsMessageResponseMixin, + HordeAPIObject, + HordeResponse, + HordeResponseBaseModel, +) +from horde_sdk.generic_api.decoration import Unhashable + + +class AIHordeHeartbeatResponse(HordeResponseBaseModel, ContainsMessageResponseMixin): + version: str + + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return None + + +class AIHordeHeartbeatRequest(BaseAIHordeRequest): + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return None + + @override + @classmethod + def get_http_method(cls) -> HTTPMethod: + return HTTPMethod.GET + + @override + @classmethod + def get_api_endpoint_subpath(cls) -> AI_HORDE_API_ENDPOINT_SUBPATH: + return AI_HORDE_API_ENDPOINT_SUBPATH.v2_status_heartbeat + + @override + @classmethod + def get_default_success_response_type(cls) -> type[AIHordeHeartbeatResponse]: + return AIHordeHeartbeatResponse + + +class HordePerformanceResponse(HordeResponseBaseModel): + interrogator_count: int | None = Field( + None, + description=( + "How many workers are actively processing image interrogations in this {horde_noun} in the past 5 minutes." + ), + ) + interrogator_thread_count: int | None = Field( + None, + description=( + "How many worker threads are actively processing image interrogation in this {horde_noun} in the past 5" + " minutes." + ), + ) + past_minute_megapixelsteps: float | None = Field( + None, + description="How many megapixelsteps this horde generated in the last minute.", + ) + past_minute_tokens: float | None = Field( + None, + description="How many tokens this horde generated in the last minute.", + ) + queued_forms: float | None = Field( + None, + description="The amount of image interrogations waiting and processing currently in this horde.", + ) + queued_megapixelsteps: float | None = Field( + None, + description="The amount of megapixelsteps in waiting and processing requests currently in this horde.", + ) + queued_requests: int | None = Field( + None, + description="The amount of waiting and processing image requests currently in this horde.", + ) + queued_text_requests: int | None = Field( + None, + description="The amount of waiting and processing text requests currently in this horde.", + ) + queued_tokens: float | None = Field( + None, + description="The amount of tokens in waiting and processing requests currently in this horde.", + ) + text_thread_count: int | None = Field( + None, + description=( + "How many worker threads are actively processing prompt generations in this {horde_noun} in the past 5" + " minutes." + ), + ) + text_worker_count: int | None = Field( + None, + description="How many workers are actively processing prompt generations in this horde in the past 5 minutes.", + ) + thread_count: int | None = Field( + None, + description=( + "How many worker threads are actively processing prompt generations in this {horde_noun} in the past 5" + " minutes." + ), + ) + worker_count: int | None = Field( + None, + description="How many workers are actively processing prompt generations in this horde in the past 5 minutes.", + ) + + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return "HordePerformance" + + +class HordePerformanceRequest(BaseAIHordeRequest): + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return None + + @override + @classmethod + def get_http_method(cls) -> HTTPMethod: + return HTTPMethod.GET + + @override + @classmethod + def get_api_endpoint_subpath(cls) -> AI_HORDE_API_ENDPOINT_SUBPATH: + return AI_HORDE_API_ENDPOINT_SUBPATH.v2_status_performance + + @override + @classmethod + def get_default_success_response_type(cls) -> type[HordePerformanceResponse]: + return HordePerformanceResponse + + +class Newspiece(HordeAPIObject): + date_published: str | None = Field(None, description="The date this newspiece was published.") + importance: str | None = Field(None, description="How critical this piece of news is.", examples=["Information"]) + newspiece: str | None = Field(None, description="The actual piece of news.") + + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return "Newspiece" + + +@Unhashable +class NewsResponse(HordeResponse, RootModel[list[Newspiece]]): + def __iter__(self) -> Iterator[Newspiece]: # type: ignore + return iter(self.root) + + def __getitem__(self, index: int) -> Newspiece: + return self.root[index] + + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return None # FIXME + + def __eq__(self, other: object) -> bool: + if not isinstance(other, NewsResponse): + return False + return all(newspiece in other.root for newspiece in self.root) + + +class NewsRequest(BaseAIHordeRequest): + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return None + + @override + @classmethod + def get_http_method(cls) -> HTTPMethod: + return HTTPMethod.GET + + @override + @classmethod + def get_api_endpoint_subpath(cls) -> AI_HORDE_API_ENDPOINT_SUBPATH: + return AI_HORDE_API_ENDPOINT_SUBPATH.v2_status_news + + @override + @classmethod + def get_default_success_response_type(cls) -> type[NewsResponse]: + return NewsResponse + + +class ActiveModelLite(HordeAPIObject): + count: int | None = Field(None, description="How many of workers in this horde are running this model.") + name: str | None = Field(None, description="The Name of a model available by workers in this horde.") + + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return "ActiveModelLite" + + +class ActiveModel(ActiveModelLite): + eta: int | None = Field(None, description="Estimated time in seconds for this model's queue to be cleared.") + jobs: float | None = Field(None, description="The job count waiting to be generated by this model.") + performance: float | None = Field(None, description="The average speed of generation for this model.") + queued: float | None = Field(None, description="The amount waiting to be generated by this model.") + type_: MODEL_TYPE | None = Field( + description="The model type (text or image).", + examples=[MODEL_TYPE.image, MODEL_TYPE.text], + alias="type", + ) + + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return "ActiveModel" + + +@Unhashable +class HordeStatusModelsAllResponse(HordeResponse, RootModel[list[ActiveModel]]): + def __iter__(self) -> Iterator[ActiveModel]: # type: ignore + return iter(self.root) + + def __getitem__(self, index: int) -> ActiveModel: + return self.root[index] + + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return None # FIXME + + def __eq__(self, other: object) -> bool: + if not isinstance(other, HordeStatusModelsAllResponse): + return False + return all(model in other.root for model in self.root) + + +class HordeStatusModelsAllRequest(BaseAIHordeRequest): + model_config = ConfigDict( + protected_namespaces=(), # Allows the "model_" prefix on attrs + ) + + type_: MODEL_TYPE = Field( + MODEL_TYPE.image, + description="The type of model to filter by.", + examples=[MODEL_TYPE.image, MODEL_TYPE.text], + alias="type", + ) + + min_count: int | None = None + max_count: int | None = None + + model_state: MODEL_STATE = MODEL_STATE.all + + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return None + + @override + @classmethod + def get_http_method(cls) -> HTTPMethod: + return HTTPMethod.GET + + @override + @classmethod + def get_api_endpoint_subpath(cls) -> AI_HORDE_API_ENDPOINT_SUBPATH: + return AI_HORDE_API_ENDPOINT_SUBPATH.v2_status_models_all + + @override + @classmethod + def get_default_success_response_type(cls) -> type[HordeStatusModelsAllResponse]: + return HordeStatusModelsAllResponse + + @override + @classmethod + def get_query_fields(cls) -> list[str]: + return ["type_", "min_count", "max_count"] + + +@Unhashable +class HordeStatusModelsSingleResponse(HordeResponse, RootModel[list[ActiveModel]]): + # This is a list because of an oversight in the structure of the API response. # FIXME + def __iter__(self) -> Iterator[ActiveModel]: # type: ignore + return iter(self.root) + + def __getitem__(self, index: int) -> ActiveModel: + return self.root[index] + + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return None + + def __eq__(self, other: object) -> bool: + if not isinstance(other, HordeStatusModelsSingleResponse): + return False + return all(model in other.root for model in self.root) + + +class HordeStatusModelsSingleRequest(BaseAIHordeRequest): + model_config = ConfigDict( + protected_namespaces=(), # Allows the "model_" prefix on attrs + ) + + model_name: str + + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return None + + @override + @classmethod + def get_http_method(cls) -> HTTPMethod: + return HTTPMethod.GET + + @override + @classmethod + def get_api_endpoint_subpath(cls) -> AI_HORDE_API_ENDPOINT_SUBPATH: + return AI_HORDE_API_ENDPOINT_SUBPATH.v2_status_models_single + + @override + @classmethod + def get_default_success_response_type(cls) -> type[HordeStatusModelsSingleResponse]: + return HordeStatusModelsSingleResponse + + +class HordeModes(HordeAPIObject): + maintenance_mode: bool = Field( + False, + description="Whether the horde is in maintenance mode.", + ) + + invite_only_mode: bool = Field( + False, + description="Whether the horde is in invite-only mode.", + ) + + raid_mode: bool = Field( + False, + description="Whether the horde is in raid mode.", + ) + + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return "HordeModes" diff --git a/horde_sdk/ai_horde_api/apimodels/alchemy/_pop.py b/horde_sdk/ai_horde_api/apimodels/alchemy/_pop.py index a8d45a8..38538b3 100644 --- a/horde_sdk/ai_horde_api/apimodels/alchemy/_pop.py +++ b/horde_sdk/ai_horde_api/apimodels/alchemy/_pop.py @@ -46,7 +46,6 @@ def get_api_model_name(cls) -> str | None: return "InterrogationPopFormPayload" form: KNOWN_ALCHEMY_TYPES | str = Field( - None, description="The name of this interrogation form", examples=["caption"], ) diff --git a/horde_sdk/ai_horde_api/apimodels/alchemy/_submit.py b/horde_sdk/ai_horde_api/apimodels/alchemy/_submit.py index 5d40783..1f5d2aa 100644 --- a/horde_sdk/ai_horde_api/apimodels/alchemy/_submit.py +++ b/horde_sdk/ai_horde_api/apimodels/alchemy/_submit.py @@ -4,10 +4,15 @@ from horde_sdk.ai_horde_api.consts import GENERATION_STATE from horde_sdk.ai_horde_api.endpoints import AI_HORDE_API_ENDPOINT_SUBPATH from horde_sdk.consts import HTTPMethod -from horde_sdk.generic_api.apimodels import APIKeyAllowedInRequestMixin, HordeResponse, HordeResponseBaseModel +from horde_sdk.generic_api.apimodels import APIKeyAllowedInRequestMixin, HordeResponseBaseModel class AlchemyJobSubmitResponse(HordeResponseBaseModel): + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return "GenerationSubmitted" + reward: float @@ -32,5 +37,5 @@ def get_api_endpoint_subpath(cls) -> AI_HORDE_API_ENDPOINT_SUBPATH: @override @classmethod - def get_default_success_response_type(cls) -> type[HordeResponse]: + def get_default_success_response_type(cls) -> type[AlchemyJobSubmitResponse]: return AlchemyJobSubmitResponse diff --git a/horde_sdk/ai_horde_api/apimodels/base.py b/horde_sdk/ai_horde_api/apimodels/base.py index 986ae71..853510f 100644 --- a/horde_sdk/ai_horde_api/apimodels/base.py +++ b/horde_sdk/ai_horde_api/apimodels/base.py @@ -5,6 +5,7 @@ import os import random import uuid +from typing import Any from loguru import logger from pydantic import ConfigDict, Field, field_validator, model_validator @@ -16,9 +17,11 @@ KNOWN_MISC_POST_PROCESSORS, KNOWN_SAMPLERS, KNOWN_UPSCALERS, + KNOWN_WORKFLOWS, METADATA_TYPE, METADATA_VALUE, POST_PROCESSOR_ORDER_TYPE, + WarningCode, _all_valid_post_processors_names_and_values, ) from horde_sdk.ai_horde_api.endpoints import AI_HORDE_BASE_URL @@ -58,7 +61,7 @@ def __hash__(self) -> int: return hash(self.id_) -class JobResponseMixin(HordeAPIDataObject): # TODO: this model may not actually exist as such in the API +class JobResponseMixin(HordeAPIDataObject): """Mix-in class for data relating to any generation jobs.""" id_: JobID = Field(alias="id") @@ -140,17 +143,39 @@ class ExtraSourceImageEntry(HordeAPIDataObject): """The strength to apply to this image on various operations.""" +class ExtraTextEntry(HordeAPIDataObject): + """Represents a single extra text. + + v2 API Model: `ExtraText` + """ + + text: str = Field(min_length=1) + """Extra text required for this generation.""" + reference: str = Field(min_length=3) + """Reference pointing to how this text is to be used.""" + + class SingleWarningEntry(HordeAPIDataObject): """Represents a single warning. v2 API Model: `RequestSingleWarning` """ - code: str = Field(min_length=1) + code: WarningCode | str = Field(min_length=1) """The code uniquely identifying this warning.""" message: str = Field(min_length=1) """The human-readable description of this warning""" + @field_validator("code") + def code_must_be_known(cls, v: str | WarningCode) -> str | WarningCode: + """Ensure that the warning code is in this list of supported warning codes.""" + if isinstance(v, WarningCode): + return v + if v not in WarningCode.__members__ or v not in WarningCode.__members__.values(): + logger.warning(f"Unknown warning code {v}. Is your SDK out of date or did the API change?") + + return v + class ImageGenerateParamMixin(HordeAPIDataObject): """Mix-in class of some of the data included in a request to the `/v2/generate/async` endpoint. @@ -204,7 +229,11 @@ class ImageGenerateParamMixin(HordeAPIDataObject): """A list of lora parameters to use.""" tis: list[TIPayloadEntry] = Field(default_factory=list) """A list of textual inversion (embedding) parameters to use.""" - special: dict = Field(default_factory=dict) + extra_texts: list[ExtraTextEntry] = Field(default_factory=list) + """A list of extra texts and prompts to use in the comfyUI workflow.""" + workflow: str | KNOWN_WORKFLOWS | None = None + """The specific comfyUI workflow to use.""" + special: dict[Any, Any] = Field(default_factory=dict) """Reserved for future use.""" @field_validator("width", "height", mode="before") @@ -219,10 +248,13 @@ def width_divisible_by_64(cls, value: int) -> int: @field_validator("sampler_name") def sampler_name_must_be_known(cls, v: str | KNOWN_SAMPLERS) -> str | KNOWN_SAMPLERS: """Ensure that the sampler name is in this list of supported samplers.""" - if (isinstance(v, str) and v in KNOWN_SAMPLERS.__members__) or (isinstance(v, KNOWN_SAMPLERS)): + if isinstance(v, KNOWN_SAMPLERS): return v - logger.warning(f"Unknown sampler name {v}. Is your SDK out of date or did the API change?") + try: + KNOWN_SAMPLERS(v) + except ValueError: + logger.warning(f"Unknown sampler name {v}. Is your SDK out of date or did the API change?") return v @@ -266,10 +298,12 @@ def control_type_must_be_known(cls, v: str | KNOWN_CONTROLNETS | None) -> str | return None if isinstance(v, KNOWN_CONTROLNETS): return v - if v in KNOWN_CONTROLNETS.__members__: - return v - logger.warning(f"Unknown control type {v}. Is your SDK out of date or did the API change?") + try: + KNOWN_CONTROLNETS(v) + except ValueError: + logger.warning(f"Unknown control type {v}. Is your SDK out of date or did the API change?") + return v diff --git a/horde_sdk/ai_horde_api/apimodels/generate/_async.py b/horde_sdk/ai_horde_api/apimodels/generate/_async.py index fcfdcdc..2510496 100644 --- a/horde_sdk/ai_horde_api/apimodels/generate/_async.py +++ b/horde_sdk/ai_horde_api/apimodels/generate/_async.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from loguru import logger from pydantic import AliasChoices, Field, field_validator, model_validator from typing_extensions import override @@ -20,7 +22,7 @@ HordeAPIObject, HordeResponse, HordeResponseBaseModel, - RequestUsesImageWorkerMixin, + RequestUsesWorkerMixin, ResponseRequiringFollowUpMixin, ) @@ -40,6 +42,16 @@ class ImageGenerateAsyncResponse( kudos: float warnings: list[SingleWarningEntry] | None = None + @model_validator(mode="after") + def validate_warnings(self) -> ImageGenerateAsyncResponse: + if self.warnings is None: + return self + + for warning in self.warnings: + logger.warning(f"Warning from server ({warning.code}): {warning.message}") + + return self + @override def get_follow_up_returned_params(self, *, as_python_field_name: bool = False) -> list[dict[str, object]]: if as_python_field_name: @@ -112,7 +124,7 @@ def validate_n(cls, value: int) -> int: class ImageGenerateAsyncRequest( BaseAIHordeRequest, APIKeyAllowedInRequestMixin, - RequestUsesImageWorkerMixin, + RequestUsesWorkerMixin, ): """Represents the data needed to make a request to the `/v2/generate/async` endpoint. @@ -137,11 +149,11 @@ class ImageGenerateAsyncRequest( extra_source_images: list[ExtraSourceImageEntry] | None = None """Additional uploaded images which can be used for further operations.""" - @model_validator(mode="before") - def validate_censor_nsfw(cls, values: dict) -> dict: - if values.get("censor_nsfw") and values.get("nsfw"): - raise ValueError("censor_nsfw is only valid when nsfw is False") - return values + @model_validator(mode="after") + def validate_censor_nsfw(self) -> ImageGenerateAsyncRequest: + if self.nsfw and self.censor_nsfw: + raise ValueError("Cannot censor NSFW content when NSFW detection is enabled.") + return self @override @classmethod diff --git a/horde_sdk/ai_horde_api/apimodels/generate/_pop.py b/horde_sdk/ai_horde_api/apimodels/generate/_pop.py index 9c620ca..92db2d7 100644 --- a/horde_sdk/ai_horde_api/apimodels/generate/_pop.py +++ b/horde_sdk/ai_horde_api/apimodels/generate/_pop.py @@ -26,40 +26,84 @@ from horde_sdk.consts import HTTPMethod from horde_sdk.generic_api.apimodels import ( APIKeyAllowedInRequestMixin, - HordeAPIDataObject, + HordeAPIObject, HordeResponseBaseModel, ResponseRequiringDownloadMixin, ResponseRequiringFollowUpMixin, ) -class ImageGenerateJobPopSkippedStatus(HordeAPIDataObject): +class NoValidRequestFound(HordeAPIObject): + blacklist: int | None = Field( + None, + description=( + "How many waiting requests were skipped because they demanded a generation with a word that this worker" + " does not accept." + ), + ge=0, + ) + bridge_version: int | None = Field( + None, + description=( + "How many waiting requests were skipped because they require a higher version of the bridge than this" + " worker is running (upgrade if you see this in your skipped list)." + ), + examples=[0], + ge=0, + ) + kudos: int | None = Field( + None, + description=( + "How many waiting requests were skipped because the user didn't have enough kudos when this worker" + " requires upfront kudos." + ), + ) + models: int | None = Field( + None, + description=( + "How many waiting requests were skipped because they demanded a different model than what this worker" + " provides." + ), + examples=[0], + ge=0, + ) + nsfw: int | None = Field( + None, + description=( + "How many waiting requests were skipped because they demanded a nsfw generation which this worker does not" + " provide." + ), + ge=0, + ) + performance: int | None = Field( + None, + description="How many waiting requests were skipped because they required higher performance.", + ge=0, + ) + untrusted: int | None = Field( + None, + description=( + "How many waiting requests were skipped because they demanded a trusted worker which this worker is not." + ), + ge=0, + ) + worker_id: int | None = Field( + None, + description="How many waiting requests were skipped because they demanded a specific worker.", + ge=0, + ) + + def is_empty(self) -> bool: + """Whether or not this object has any non-zero values.""" + return len(self.model_fields_set) == 0 + + +class ImageGenerateJobPopSkippedStatus(NoValidRequestFound): """Represents the data returned from the `/v2/generate/pop` endpoint for why a worker was skipped. v2 API Model: `NoValidRequestFoundStable` """ - worker_id: int = Field(default=0, ge=0) - """How many waiting requests were skipped because they demanded a specific worker.""" - performance: int = Field(default=0, ge=0) - """How many waiting requests were skipped because they required higher performance.""" - nsfw: int = Field(default=0, ge=0) - """How many waiting requests were skipped because they demanded a nsfw generation which this worker - does not provide.""" - blacklist: int = Field(default=0, ge=0) - """How many waiting requests were skipped because they demanded a generation with a word that this worker does - not accept.""" - untrusted: int = Field(default=0, ge=0) - """How many waiting requests were skipped because they demanded a trusted worker which this worker is not.""" - models: int = Field(default=0, ge=0) - """How many waiting requests were skipped because they demanded a different model than what this worker - provides.""" - bridge_version: int = Field(default=0, ge=0) - """How many waiting requests were skipped because they require a higher version of the bridge than this worker is - running (upgrade if you see this in your skipped list).""" - kudos: int = Field(default=0, ge=0) - """How many waiting requests were skipped because the user didn't have enough kudos when this worker requires - upfront kudos.""" max_pixels: int = Field(default=0, ge=0) """How many waiting requests were skipped because they demanded a higher size than this worker provides.""" unsafe_ip: int = Field(default=0, ge=0) @@ -75,9 +119,10 @@ class ImageGenerateJobPopSkippedStatus(HordeAPIDataObject): controlnet: int = Field(default=0, ge=0) """How many waiting requests were skipped because they requested a controlnet.""" - def is_empty(self) -> bool: - """Whether or not this object has any non-zero values.""" - return len(self.model_fields_set) == 0 + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return "NoValidRequestFoundStable" class ImageGenerateJobPopPayload(ImageGenerateParamMixin): @@ -89,10 +134,104 @@ class ImageGenerateJobPopPayload(ImageGenerateParamMixin): """The number of images to generate. Defaults to 1, maximum is 20.""" +class ExtraSourceImageMixin(ResponseRequiringDownloadMixin): + extra_source_images: list[ExtraSourceImageEntry] | None = None + """Additional uploaded images (as base64) which can be used for further operations.""" + _downloaded_extra_source_images: list[ExtraSourceImageEntry] | None = None + + async def async_download_extra_source_images( + self, + client_session: aiohttp.ClientSession, + *, + max_retries: int = 5, + ) -> list[ExtraSourceImageEntry] | None: + """Download the extra source images concurrently. + + You can also use `get_downloaded_extra_source_images` to get the downloaded images later, if needed. + + Args: + client_session: The aiohttp client session to use for downloading. + max_retries: The maximum number of times to retry downloading an image. + + Returns: + The downloaded extra source images. + """ + if not self.extra_source_images: + logger.info("No extra source images to download.") + return None + + if self._downloaded_extra_source_images is not None: + logger.warning("Extra source images already downloaded.") + return self._downloaded_extra_source_images + + self._downloaded_extra_source_images = [] + for extra_source_image in self.extra_source_images: + await self._download_image_if_needed(client_session, extra_source_image, max_retries) + + self._sort_downloaded_images() + return self._downloaded_extra_source_images.copy() + + def get_downloaded_extra_source_images(self) -> list[ExtraSourceImageEntry] | None: + """Get the downloaded extra source images.""" + return ( + self._downloaded_extra_source_images.copy() if self._downloaded_extra_source_images is not None else None + ) + + async def _download_image_if_needed( + self, + client_session: aiohttp.ClientSession, + extra_source_image: ExtraSourceImageEntry, + max_retries: int, + ) -> None: + """Download an extra source image if it has not already been downloaded. + + Args: + client_session: The aiohttp client session to use for downloading. + extra_source_image: The extra source image to download. + max_retries: The maximum number of times to retry downloading an image. + """ + if self._downloaded_extra_source_images is None: + self._downloaded_extra_source_images = [] + + if extra_source_image.image in (entry.original_url for entry in self._downloaded_extra_source_images): + logger.debug(f"Extra source image {extra_source_image.image} already downloaded.") + return + + for attempt in range(max_retries): + try: + downloaded_image = await self.download_file_as_base64(client_session, extra_source_image.image) + self._downloaded_extra_source_images.append( + ExtraSourceImageEntry( + image=downloaded_image, + strength=extra_source_image.strength, + original_url=extra_source_image.image, + ), + ) + break + except Exception as e: + logger.error(f"Error downloading extra source image {extra_source_image.image}: {e}") + if attempt == max_retries - 1: + logger.error(f"Failed to download image {extra_source_image.image} after {max_retries} attempts.") + + def _sort_downloaded_images(self) -> None: + """Sort the downloaded extra source images in the order they were requested.""" + + if self.extra_source_images is None or self._downloaded_extra_source_images is None: + return + + _extra_source_images = self.extra_source_images.copy() + self._downloaded_extra_source_images.sort( + key=lambda entry: next( + (i for i, image in enumerate(_extra_source_images) if image.image == entry.original_url), + len(_extra_source_images), + ), + ) + + class ImageGenerateJobPopResponse( HordeResponseBaseModel, ResponseRequiringFollowUpMixin, - ResponseRequiringDownloadMixin, + ExtraSourceImageMixin, ): """Represents the data returned from the `/v2/generate/pop` endpoint. @@ -122,10 +261,6 @@ class ImageGenerateJobPopResponse( alpha channel.""" _downloaded_source_mask: str | None = None """The downloaded source mask (as base64), if any. This is not part of the API response.""" - extra_source_images: list[ExtraSourceImageEntry] | None = None - """Additional uploaded images (as base64) which can be used for further operations.""" - _downloaded_extra_source_images: list[ExtraSourceImageEntry] | None = None - """The downloaded extra source images, if any. This is not part of the API response.""" r2_upload: str | None = None """(Obsolete) The r2 upload link to use to upload this image.""" r2_uploads: list[str] | None = None @@ -134,8 +269,13 @@ class ImageGenerateJobPopResponse( @field_validator("source_processing") def source_processing_must_be_known(cls, v: str | KNOWN_SOURCE_PROCESSING) -> str | KNOWN_SOURCE_PROCESSING: """Ensure that the source processing is in this list of supported source processing.""" - if v not in KNOWN_SOURCE_PROCESSING.__members__: - raise ValueError(f"Unknown source processing {v}") + if isinstance(v, KNOWN_SOURCE_PROCESSING): + return v + + try: + KNOWN_SOURCE_PROCESSING(v) + except ValueError: + logger.warning(f"Unknown source processing {v}. Is your SDK out of date or did the API change?") return v @field_validator("id_", mode="before") @@ -232,13 +372,7 @@ def get_downloaded_source_mask(self) -> str | None: """Get the downloaded source mask.""" return self._downloaded_source_mask - def get_downloaded_extra_source_images(self) -> list[ExtraSourceImageEntry] | None: - """Get the downloaded extra source images.""" - return ( - self._downloaded_extra_source_images.copy() if self._downloaded_extra_source_images is not None else None - ) - - def async_download_source_image(self, client_session: aiohttp.ClientSession) -> asyncio.Task: + def async_download_source_image(self, client_session: aiohttp.ClientSession) -> asyncio.Task[None]: """Download the source image concurrently.""" # If the source image is not set, there is nothing to download. @@ -254,7 +388,7 @@ def async_download_source_image(self, client_session: aiohttp.ClientSession) -> self.download_file_to_field_as_base64(client_session, self.source_image, "_downloaded_source_image"), ) - def async_download_source_mask(self, client_session: aiohttp.ClientSession) -> asyncio.Task: + def async_download_source_mask(self, client_session: aiohttp.ClientSession) -> asyncio.Task[None]: """Download the source mask concurrently.""" # If the source mask is not set, there is nothing to download. @@ -262,7 +396,7 @@ def async_download_source_mask(self, client_session: aiohttp.ClientSession) -> a return asyncio.create_task(asyncio.sleep(0)) # If the source mask is not a URL, it is already a base64 string. - if not self.source_mask.startswith("http"): + if urlparse(self.source_mask).scheme not in ["http", "https"]: self._downloaded_source_mask = self.source_mask return asyncio.create_task(asyncio.sleep(0)) @@ -270,91 +404,6 @@ def async_download_source_mask(self, client_session: aiohttp.ClientSession) -> a self.download_file_to_field_as_base64(client_session, self.source_mask, "_downloaded_source_mask"), ) - async def async_download_extra_source_images( - self, - client_session: aiohttp.ClientSession, - *, - max_retries: int = 5, - ) -> list[ExtraSourceImageEntry] | None: - """Download all extra source images concurrently.""" - - if self.extra_source_images is None or len(self.extra_source_images) == 0: - logger.info("No extra source images to download.") - return None - - if self._downloaded_extra_source_images is None: - self._downloaded_extra_source_images = [] - else: - logger.warning("Extra source images already downloaded.") - return self._downloaded_extra_source_images - - attempts = 0 - while attempts < max_retries: - tasks: list[asyncio.Task] = [] - - for extra_source_image in self.extra_source_images: - if extra_source_image.image is None: - continue - - if urlparse(extra_source_image.image).scheme not in ["http", "https"]: - self._downloaded_extra_source_images.append(extra_source_image) - tasks.append(asyncio.create_task(asyncio.sleep(0))) - continue - - if any( - extra_source_image.image == downloaded_extra_source_image.original_url - for downloaded_extra_source_image in self._downloaded_extra_source_images - ): - logger.debug(f"Extra source image {extra_source_image.image} already downloaded.") - tasks.append(asyncio.create_task(asyncio.sleep(0))) - continue - - tasks.append( - asyncio.create_task( - self.download_file_as_base64(client_session, extra_source_image.image), - ), - ) - - results = await asyncio.gather(*tasks, return_exceptions=True) - - for result, extra_source_image in zip(results, self.extra_source_images, strict=True): - if isinstance(result, Exception) or not isinstance(result, str): - logger.error(f"Error downloading extra source image {extra_source_image.image}: {result}") - continue - - self._downloaded_extra_source_images.append( - ExtraSourceImageEntry( - image=result, - strength=extra_source_image.strength, - original_url=extra_source_image.image, - ), - ) - - if len(self._downloaded_extra_source_images) == len(self.extra_source_images): - break - - attempts += 1 - - # If there are any entries in _downloaded_extra_source_images, - # make sure the order matches the order of the original list. - if ( - self.extra_source_images is not None - and self._downloaded_extra_source_images is not None - and len(self._downloaded_extra_source_images) > 0 - ): - - def _sort_key(x: ExtraSourceImageEntry) -> int: - if self.extra_source_images is not None: - for i, extra_source_image in enumerate(self.extra_source_images): - if extra_source_image.image == x.original_url: - return i - - return 0 - - self._downloaded_extra_source_images.sort(key=_sort_key) - - return self._downloaded_extra_source_images.copy() - @override async def async_download_additional_data(self, client_session: aiohttp.ClientSession) -> None: """Download all additional images concurrently.""" @@ -395,20 +444,51 @@ def __hash__(self) -> int: return hash(0) -class ImageGenerateJobPopRequest(BaseAIHordeRequest, APIKeyAllowedInRequestMixin): +class PopInput(HordeAPIObject): + amount: int | None = Field(1, description="How many jobvs to pop at the same time", ge=1, le=20) + bridge_agent: str | None = Field( + "unknown:0:unknown", + description="The worker name, version and website.", + examples=["AI Horde Worker reGen:4.1.0:https://github.com/Haidra-Org/horde-worker-reGen"], + max_length=1000, + ) + models: list[str] | None = None + name: str | None = Field(None, description="The Name of the Worker.") + nsfw: bool | None = Field(False, description="Whether this worker can generate NSFW requests or not.") + priority_usernames: list[str] | None = None + require_upfront_kudos: bool | None = Field( + False, + description=( + "If True, this worker will only pick up requests where the owner has the required kudos to consume already" + " available." + ), + examples=[ + False, + ], + ) + threads: int | None = Field( + 1, + description=( + "How many threads this worker is running. This is used to accurately the current power available in the" + " horde." + ), + ge=1, + le=50, + ) + + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return "PopInput" + + +class ImageGenerateJobPopRequest(BaseAIHordeRequest, APIKeyAllowedInRequestMixin, PopInput): """Represents the data needed to make a job request from a worker to the /v2/generate/pop endpoint. v2 API Model: `PopInputStable` """ - name: str - priority_usernames: list[str] = Field(default_factory=list) - nsfw: bool = True - models: list[str] bridge_version: int | None = None - bridge_agent: str - threads: int = 1 - require_upfront_kudos: bool = False max_pixels: int blacklist: list[str] = Field(default_factory=list) allow_img2img: bool = True @@ -416,8 +496,8 @@ class ImageGenerateJobPopRequest(BaseAIHordeRequest, APIKeyAllowedInRequestMixin allow_unsafe_ipaddr: bool = True allow_post_processing: bool = True allow_controlnet: bool = False + allow_sdxl_controlnet: bool = False allow_lora: bool = False - amount: int = 1 @override @classmethod diff --git a/horde_sdk/ai_horde_api/apimodels/generate/_progress.py b/horde_sdk/ai_horde_api/apimodels/generate/_progress.py index c0188ac..333fb74 100644 --- a/horde_sdk/ai_horde_api/apimodels/generate/_progress.py +++ b/horde_sdk/ai_horde_api/apimodels/generate/_progress.py @@ -1,7 +1,7 @@ -from horde_sdk.generic_api.apimodels import HordeAPIDataObject, ResponseWithProgressMixin +from horde_sdk.generic_api.apimodels import HordeAPIObject, ResponseWithProgressMixin -class ResponseGenerationProgressInfoMixin(HordeAPIDataObject): +class ResponseGenerationProgressInfoMixin(HordeAPIObject): finished: int """The amount of finished jobs in this request.""" processing: int diff --git a/horde_sdk/ai_horde_api/apimodels/generate/_status.py b/horde_sdk/ai_horde_api/apimodels/generate/_status.py index 232e887..7e92575 100644 --- a/horde_sdk/ai_horde_api/apimodels/generate/_status.py +++ b/horde_sdk/ai_horde_api/apimodels/generate/_status.py @@ -1,7 +1,7 @@ import uuid from loguru import logger -from pydantic import BaseModel, Field, field_validator +from pydantic import Field, field_validator from typing_extensions import override from horde_sdk.ai_horde_api.apimodels.base import BaseAIHordeRequest, GenMetadataEntry, JobRequestMixin @@ -10,10 +10,28 @@ from horde_sdk.ai_horde_api.endpoints import AI_HORDE_API_ENDPOINT_SUBPATH from horde_sdk.ai_horde_api.fields import JobID, WorkerID from horde_sdk.consts import HTTPMethod -from horde_sdk.generic_api.apimodels import HordeResponseBaseModel, ResponseWithProgressMixin - - -class ImageGeneration(BaseModel): +from horde_sdk.generic_api.apimodels import HordeAPIObject, HordeResponseBaseModel, ResponseWithProgressMixin + + +class Generation(HordeAPIObject): + model: str = Field(description="The model which generated this image.", title="Generation Model") + state: GENERATION_STATE = Field( + ..., + description="OBSOLETE (Use the gen_metadata field). The state of this generation.", + examples=["ok"], + title="Generation State", + ) + worker_id: str | WorkerID = Field( + description="The UUID of the worker which generated this image.", + title="Worker ID", + ) + worker_name: str = Field( + description="The name of the worker which generated this image.", + title="Worker Name", + ) + + +class ImageGeneration(Generation): """Represents the individual image generation responses in a ImageGenerateStatusResponse. v2 API Model: `GenerationStable` @@ -22,14 +40,6 @@ class ImageGeneration(BaseModel): id_: JobID = Field(alias="id") """The UUID of this generation. Is always returned as a `JobID`, but can initialized from a `str`.""" # todo: remove `str`? - worker_id: str | WorkerID - """The UUID of the worker which generated this image.""" - worker_name: str - """The name of the worker which generated this image.""" - model: str - """The model which generated this image.""" - state: GENERATION_STATE - """The state of this generation.""" img: str """The generated image as a Base64-encoded .webp file.""" seed: str @@ -39,6 +49,11 @@ class ImageGeneration(BaseModel): gen_metadata: list[GenMetadataEntry] | None = None """Extra metadata about faulted or defaulted components of the generation""" + @override + @classmethod + def get_api_model_name(self) -> str | None: + return "GenerationStable" + @field_validator("id_", mode="before") def validate_id(cls, v: str | JobID) -> JobID | str: if isinstance(v, str) and v == "": diff --git a/horde_sdk/ai_horde_api/apimodels/generate/_submit.py b/horde_sdk/ai_horde_api/apimodels/generate/_submit.py index f04457e..8ace859 100644 --- a/horde_sdk/ai_horde_api/apimodels/generate/_submit.py +++ b/horde_sdk/ai_horde_api/apimodels/generate/_submit.py @@ -16,7 +16,11 @@ from horde_sdk.generic_api.apimodels import APIKeyAllowedInRequestMixin -class ImageGenerationJobSubmitRequest(BaseAIHordeRequest, JobRequestMixin, APIKeyAllowedInRequestMixin): +class ImageGenerationJobSubmitRequest( + BaseAIHordeRequest, + JobRequestMixin, + APIKeyAllowedInRequestMixin, +): """Represents the data needed to make a job submit 'request' from a worker to the /v2/generate/submit endpoint. v2 API Model: `SubmitInputStable` diff --git a/horde_sdk/ai_horde_api/apimodels/generate/text/__init__.py b/horde_sdk/ai_horde_api/apimodels/generate/text/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/horde_sdk/ai_horde_api/apimodels/generate/text/_async.py b/horde_sdk/ai_horde_api/apimodels/generate/text/_async.py new file mode 100644 index 0000000..39b0802 --- /dev/null +++ b/horde_sdk/ai_horde_api/apimodels/generate/text/_async.py @@ -0,0 +1,255 @@ +from __future__ import annotations + +from loguru import logger +from pydantic import Field, model_validator +from typing_extensions import override + +from horde_sdk.ai_horde_api.apimodels.base import ( + BaseAIHordeRequest, + ExtraSourceImageEntry, + JobResponseMixin, + SingleWarningEntry, +) +from horde_sdk.ai_horde_api.apimodels.generate.text._status import DeleteTextGenerateRequest, TextGenerateStatusRequest +from horde_sdk.ai_horde_api.endpoints import AI_HORDE_API_ENDPOINT_SUBPATH +from horde_sdk.consts import HTTPMethod, HTTPStatusCode +from horde_sdk.generic_api.apimodels import ( + APIKeyAllowedInRequestMixin, + ContainsMessageResponseMixin, + HordeAPIDataObject, + HordeResponse, + HordeResponseBaseModel, + RequestUsesWorkerMixin, + ResponseRequiringFollowUpMixin, +) +from horde_sdk.generic_api.decoration import Unhashable + + +class TextGenerateAsyncResponse( + HordeResponseBaseModel, + JobResponseMixin, + ResponseRequiringFollowUpMixin, + ContainsMessageResponseMixin, +): + kudos: float | None = Field(None, description="The expected kudos consumption for this request.") + warnings: list[SingleWarningEntry] | None = None + + @model_validator(mode="after") + def validate_warnings(self) -> TextGenerateAsyncResponse: + if self.warnings is None: + return self + + for warning in self.warnings: + logger.warning(f"Warning from server ({warning.code}): {warning.message}") + + return self + + @override + def get_follow_up_returned_params(self, *, as_python_field_name: bool = False) -> list[dict[str, object]]: + if as_python_field_name: + return [{"id_": self.id_}] + return [{"id": self.id_}] + + @classmethod + def get_follow_up_default_request_type(cls) -> type[TextGenerateStatusRequest]: + return TextGenerateStatusRequest + + @override + @classmethod + def get_follow_up_request_types( # type: ignore[override] + cls, + ) -> list[type[TextGenerateStatusRequest]]: + return [TextGenerateStatusRequest] + + @override + @classmethod + def get_follow_up_failure_cleanup_request_type(cls) -> type[DeleteTextGenerateRequest]: + return DeleteTextGenerateRequest + + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return "RequestAsync" + + def __hash__(self) -> int: + return hash(self.id_) + + def __eq__(self, __value: object) -> bool: + return isinstance(__value, TextGenerateAsyncResponse) and self.id_ == __value.id_ + + +@Unhashable +class ModelPayloadRootKobold(HordeAPIDataObject): + dynatemp_exponent: float | None = Field(1, description="Dynamic temperature exponent value.", ge=0.0, le=5.0) + dynatemp_range: float | None = Field(0, description="Dynamic temperature range value.", ge=0.0, le=5.0) + frmtadsnsp: bool | None = Field( + None, + description=( + "Input formatting option. When enabled, adds a leading space to your input if there is no trailing" + " whitespace at the end of the previous action." + ), + examples=[ + False, + ], + ) + frmtrmblln: bool | None = Field( + None, + description=( + "Output formatting option. When enabled, replaces all occurrences of two or more consecutive newlines in" + " the output with one newline." + ), + examples=[ + False, + ], + ) + frmtrmspch: bool | None = Field( + None, + description="Output formatting option. When enabled, removes #/@%}{+=~|\\^<> from the output.", + examples=[ + False, + ], + ) + frmttriminc: bool | None = Field( + None, + description=( + "Output formatting option. When enabled, removes some characters from the end of the output such that the" + " output doesn't end in the middle of a sentence. If the output is less than one sentence long, does" + " nothing." + ), + examples=[ + False, + ], + ) + max_context_length: int | None = Field( + 1024, + description="Maximum number of tokens to send to the model.", + ge=80, + le=32000, + ) + max_length: int | None = Field(80, description="Number of tokens to generate.", ge=16, le=1024) + min_p: float | None = Field(0, description="Min-p sampling value.", ge=0.0, le=1.0) + n: int | None = Field(None, examples=[1], ge=1, le=20) + rep_pen: float | None = Field(None, description="Base repetition penalty value.", ge=1.0, le=3.0) + rep_pen_range: int | None = Field(None, description="Repetition penalty range.", ge=0, le=4096) + rep_pen_slope: float | None = Field(None, description="Repetition penalty slope.", ge=0.0, le=10.0) + sampler_order: list[int] | None = None + singleline: bool | None = Field( + None, + description=( + "Output formatting option. When enabled, removes everything after the first line of the output, including" + " the newline." + ), + examples=[ + False, + ], + ) + smoothing_factor: float | None = Field(0, description="Quadratic sampling value.", ge=0.0, le=10.0) + stop_sequence: list[str] | None = None + temperature: float | None = Field(None, description="Temperature value.", ge=0.0, le=5.0) + tfs: float | None = Field(None, description="Tail free sampling value.", ge=0.0, le=1.0) + top_a: float | None = Field(None, description="Top-a sampling value.", ge=0.0, le=1.0) + top_k: int | None = Field(None, description="Top-k sampling value.", ge=0, le=100) + top_p: float | None = Field(None, description="Top-p sampling value.", ge=0.001, le=1.0) + typical: float | None = Field(None, description="Typical sampling value.", ge=0.0, le=1.0) + use_default_badwordsids: bool | None = Field( + None, + description="When True, uses the default KoboldAI bad word IDs.", + examples=[True], + ) + + +@Unhashable +class ModelGenerationInputKobold(ModelPayloadRootKobold): + pass + + +class TextGenerateAsyncDryRunResponse(HordeResponseBaseModel): + kudos: float + + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return "UNDOCUMENTED" + + +@Unhashable +class TextGenerateAsyncRequest( + BaseAIHordeRequest, + APIKeyAllowedInRequestMixin, + RequestUsesWorkerMixin, +): + """Represents the data needed to make a request to the `/v2/generate/async` endpoint. + + v2 API Model: `GenerationInputKobold` + """ + + params: ModelGenerationInputKobold | None = None + prompt: str | None = Field(None, description="The prompt which will be sent to KoboldAI to generate text.") + + allow_downgrade: bool | None = Field( + False, + description=( + "When true and the request requires upfront kudos and the account does not have enough The request will be" + " downgraded in max context and max tokens so that it does not need upfront kudos." + ), + ) + disable_batching: bool | None = Field( + False, + description=( + "When true, This request will not use batching. This will allow you to retrieve accurate seeds. Feature is" + " restricted to Trusted users and Patreons." + ), + ) + extra_source_images: list[ExtraSourceImageEntry] | None = None + + proxied_account: str | None = Field( + None, + description=( + "If using a service account as a proxy, provide this value to identify the actual account from which this" + " request is coming from." + ), + ) + softprompt: str | None = Field( + None, + description="Specify which softprompt needs to be used to service this request.", + min_length=1, + ) + webhook: str | None = Field( + None, + description=( + "Provide a URL where the AI Horde will send a POST call after each delivered generation. The request will" + " include the details of the job as well as the request ID." + ), + ) + + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return "GenerationInputKobold" + + @override + @classmethod + def get_http_method(cls) -> HTTPMethod: + return HTTPMethod.POST + + @override + @classmethod + def get_api_endpoint_subpath(cls) -> AI_HORDE_API_ENDPOINT_SUBPATH: + return AI_HORDE_API_ENDPOINT_SUBPATH.v2_generate_text_async + + @override + @classmethod + def get_default_success_response_type(cls) -> type[TextGenerateAsyncResponse]: + return TextGenerateAsyncResponse + + @override + @classmethod + def get_success_status_response_pairs(cls) -> dict[HTTPStatusCode, type[HordeResponse]]: + return { + HTTPStatusCode.OK: TextGenerateAsyncDryRunResponse, + HTTPStatusCode.ACCEPTED: cls.get_default_success_response_type(), + } + + @override + def get_extra_fields_to_exclude_from_log(self) -> set[str]: + return {"extra_source_images"} diff --git a/horde_sdk/ai_horde_api/apimodels/generate/text/_pop.py b/horde_sdk/ai_horde_api/apimodels/generate/text/_pop.py new file mode 100644 index 0000000..93f51fe --- /dev/null +++ b/horde_sdk/ai_horde_api/apimodels/generate/text/_pop.py @@ -0,0 +1,179 @@ +from __future__ import annotations + +import uuid + +import aiohttp +from loguru import logger +from pydantic import Field, field_validator, model_validator +from typing_extensions import override + +from horde_sdk.ai_horde_api.apimodels.base import BaseAIHordeRequest +from horde_sdk.ai_horde_api.apimodels.generate._pop import ExtraSourceImageMixin, NoValidRequestFound, PopInput +from horde_sdk.ai_horde_api.apimodels.generate.text._async import ModelPayloadRootKobold +from horde_sdk.ai_horde_api.apimodels.generate.text._status import DeleteTextGenerateRequest, TextGenerateStatusRequest +from horde_sdk.ai_horde_api.endpoints import AI_HORDE_API_ENDPOINT_SUBPATH +from horde_sdk.ai_horde_api.fields import JobID +from horde_sdk.consts import HTTPMethod +from horde_sdk.generic_api.apimodels import ( + APIKeyAllowedInRequestMixin, + HordeResponseBaseModel, + ResponseRequiringFollowUpMixin, +) + + +class ModelPayloadKobold(ModelPayloadRootKobold): + prompt: str | None = Field(None, description="The prompt for the text generation.") + + +class NoValidRequestFoundKobold(NoValidRequestFound): + max_context_length: int | None = Field( + None, + description="How many waiting requests were skipped because they demanded a higher max_context_length than " + "what this worker provides.", + ) + """How many waiting requests were skipped because they demanded a higher max_context_length than what this + worker provides.""" + max_length: int | None = Field( + None, + description="How many waiting requests were skipped because they demanded a higher max_length than what this " + "worker provides.", + ) + matching_softprompt: int | None = Field( + None, + description="How many waiting requests were skipped because they demanded an available soft-prompt which this " + "worker does not have.", + ) + + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return "NoValidRequestFoundKobold" + + +class TextGenerateJobPopResponse( + HordeResponseBaseModel, + ResponseRequiringFollowUpMixin, + ExtraSourceImageMixin, +): + payload: ModelPayloadKobold = Field(..., description="The settings for this text generation.") + id_: JobID | None = Field(None, alias="id", description="The UUID for this text generation.") + """The UUID for this text generation.""" + ids: list[JobID] = Field(description="The UUIDs for this text generations.") + """The UUIDs for this text generations.""" + skipped: NoValidRequestFoundKobold = Field( + NoValidRequestFoundKobold(), + description="The skipped requests that were not valid for this worker.", + ) + softprompt: str | None = Field(None, description="The soft prompt requested for this generation.") + """The soft prompt requested for this generation.""" + model: str | None = Field(None, description="The model requested for this generation.") + """The model requested for this generation.""" + + @field_validator("id_", mode="before") + def validate_id(cls, v: str | JobID) -> JobID | str: + if isinstance(v, str) and v == "": + logger.warning("Job ID is empty") + return JobID(root=uuid.uuid4()) + + return v + + @model_validator(mode="after") + def ids_present(self) -> TextGenerateJobPopResponse: + """Ensure that either id_ or ids is present.""" + if self.model is None: + if self.skipped.is_empty(): + logger.debug("No model or skipped data found in response.") + else: + logger.debug("No model found in response.") + return self + + if self.id_ is None and len(self.ids) == 0: + raise ValueError("Neither id_ nor ids were present in the response.") + + if len(self.ids) > 1: + logger.debug("Sorting IDs") + self.ids.sort() + + return self + + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return "GenerationPayloadKobold" + + @override + @classmethod + def get_follow_up_default_request_type(cls) -> type[TextGenerateStatusRequest]: + return TextGenerateStatusRequest + + @override + @classmethod + def get_follow_up_failure_cleanup_request_type(cls) -> type[DeleteTextGenerateRequest]: + return DeleteTextGenerateRequest + + @override + def get_follow_up_returned_params(self, *, as_python_field_name: bool = False) -> list[dict[str, object]]: + if as_python_field_name: + return [{"id_": self.id_}] + return [{"id": self.id_}] + + @override + async def async_download_additional_data(self, client_session: aiohttp.ClientSession) -> None: + await self.async_download_extra_source_images(client_session) + + @override + def download_additional_data(self) -> None: + raise NotImplementedError("This method has not been implemented for this class.") + + def __eq__(self, value: object) -> bool: + if not isinstance(value, TextGenerateJobPopResponse): + return False + + if self.ids: + return all(id_ in value.ids for id_ in self.ids) + + return self.id_ == value.id_ + + def __hash__(self) -> int: + if self.ids: + return hash(tuple(self.ids)) + + return hash(self.id_) + + +class _PopInputKobold(PopInput): + max_length: int = Field(512, description="The maximum amount of tokens this worker can generate.") + """The maximum amount of tokens this worker can generate.""" + max_context_length: int = Field(2048, description="The max amount of context to submit to this AI for sampling.") + """The max amount of context to submit to this AI for sampling.""" + softprompts: list[str] | None = Field( + None, + description="The available softprompt files on this worker for the currently running model.", + ) + """The available softprompt files on this worker for the currently running model.""" + + +class TextGenerateJobPopRequest( + BaseAIHordeRequest, + APIKeyAllowedInRequestMixin, + _PopInputKobold, +): + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return "PopInputKobold" + + @override + @classmethod + def get_http_method(cls) -> HTTPMethod: + return HTTPMethod.POST + + @override + @classmethod + def get_api_endpoint_subpath(cls) -> AI_HORDE_API_ENDPOINT_SUBPATH: + return AI_HORDE_API_ENDPOINT_SUBPATH.v2_generate_text_pop + + @override + @classmethod + def get_default_success_response_type(cls) -> type[TextGenerateJobPopResponse]: + return TextGenerateJobPopResponse diff --git a/horde_sdk/ai_horde_api/apimodels/generate/text/_status.py b/horde_sdk/ai_horde_api/apimodels/generate/text/_status.py new file mode 100644 index 0000000..3fe846c --- /dev/null +++ b/horde_sdk/ai_horde_api/apimodels/generate/text/_status.py @@ -0,0 +1,135 @@ +import uuid + +from loguru import logger +from pydantic import Field, field_validator +from typing_extensions import override + +from horde_sdk.ai_horde_api.apimodels.base import BaseAIHordeRequest, GenMetadataEntry, JobRequestMixin +from horde_sdk.ai_horde_api.apimodels.generate._progress import ResponseGenerationProgressInfoMixin +from horde_sdk.ai_horde_api.apimodels.generate._status import Generation +from horde_sdk.ai_horde_api.endpoints import AI_HORDE_API_ENDPOINT_SUBPATH +from horde_sdk.ai_horde_api.fields import JobID +from horde_sdk.consts import HTTPMethod +from horde_sdk.generic_api.apimodels import HordeResponseBaseModel, ResponseWithProgressMixin + + +class GenerationKobold(Generation): + id_: str | None = Field(None, description="The ID for this image.", title="Generation ID") + gen_metadata: list[GenMetadataEntry] | None = None # FIXME: API declares a `GenerationMetadataKobold` here + seed: int | None = Field(0, description="The seed which generated this text.", title="Generation Seed") + text: str | None = Field(None, description="The generated text.", min_length=0, title="Generated Text") + + @override + @classmethod + def get_api_model_name(self) -> str | None: + return "GenerationKobold" + + @field_validator("id_", mode="before") + def validate_id(cls, v: str | JobID) -> JobID | str: + if isinstance(v, str) and v == "": + logger.warning("Job ID is empty") + return JobID(root=uuid.uuid4()) + + return v + + def __eq__(self, other: object) -> bool: + if not isinstance(other, GenerationKobold): + return False + return self.id_ == other.id_ + + def __hash__(self) -> int: + return hash(self.id_) + + +class TextGenerateStatusResponse( + HordeResponseBaseModel, + ResponseWithProgressMixin, + ResponseGenerationProgressInfoMixin, +): + generations: list[GenerationKobold] = Field( + default_factory=list, + description="The generations that have been completed in this request.", + title="Generations", + ) + + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return "RequestStatusKobold" + + @override + @classmethod + def get_finalize_success_request_type(cls) -> None: + return None + + @override + def is_job_complete(self, number_of_result_expected: int) -> bool: + return len(self.generations) == number_of_result_expected + + @override + def is_job_possible(self) -> bool: + return self.is_possible + + @override + @classmethod + def is_final_follow_up(cls) -> bool: + return True + + def __eq__(self, other: object) -> bool: + if not isinstance(other, TextGenerateStatusResponse): + return False + return all(gen in other.generations for gen in self.generations) + + def __hash__(self) -> int: + return hash(tuple(self.generations)) + + +class DeleteTextGenerateRequest( + BaseAIHordeRequest, + JobRequestMixin, +): + """Represents a DELETE request to the `/v2/generate/text/status/{id}` endpoint.""" + + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return None + + @override + @classmethod + def get_http_method(cls) -> HTTPMethod: + return HTTPMethod.DELETE + + @override + @classmethod + def get_api_endpoint_subpath(cls) -> AI_HORDE_API_ENDPOINT_SUBPATH: + return AI_HORDE_API_ENDPOINT_SUBPATH.v2_generate_text_status + + @override + @classmethod + def get_default_success_response_type(cls) -> type[TextGenerateStatusResponse]: + return TextGenerateStatusResponse + + +class TextGenerateStatusRequest(BaseAIHordeRequest, JobRequestMixin): + """Represents a GET request to the `/v2/generate/status/{id}` endpoint.""" + + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return None + + @override + @classmethod + def get_http_method(cls) -> HTTPMethod: + return HTTPMethod.GET + + @override + @classmethod + def get_api_endpoint_subpath(cls) -> AI_HORDE_API_ENDPOINT_SUBPATH: + return AI_HORDE_API_ENDPOINT_SUBPATH.v2_generate_text_status + + @override + @classmethod + def get_default_success_response_type(cls) -> type[TextGenerateStatusResponse]: + return TextGenerateStatusResponse diff --git a/horde_sdk/ai_horde_api/apimodels/generate/text/_submit.py b/horde_sdk/ai_horde_api/apimodels/generate/text/_submit.py new file mode 100644 index 0000000..fd24313 --- /dev/null +++ b/horde_sdk/ai_horde_api/apimodels/generate/text/_submit.py @@ -0,0 +1,62 @@ +from __future__ import annotations + +from loguru import logger +from pydantic import model_validator +from typing_extensions import override + +from horde_sdk.ai_horde_api.apimodels.base import ( + BaseAIHordeRequest, + GenMetadataEntry, + JobRequestMixin, + JobSubmitResponse, +) +from horde_sdk.ai_horde_api.consts import GENERATION_STATE +from horde_sdk.ai_horde_api.endpoints import AI_HORDE_API_ENDPOINT_SUBPATH +from horde_sdk.consts import HTTPMethod +from horde_sdk.generic_api.apimodels import APIKeyAllowedInRequestMixin + + +class TextGenerationJobSubmitRequest( + BaseAIHordeRequest, + JobRequestMixin, + APIKeyAllowedInRequestMixin, +): + """Represents the data needed to make a job submit 'request' from a worker to the /v2/generate/submit endpoint. + + v2 API Model: `SubmitInputStable` + """ + + generation: str = "" + """R2 result was uploaded to R2, else the string of the result.""" + state: GENERATION_STATE + """The state of this generation.""" + gen_metadata: list[GenMetadataEntry] | None = None + """Extra metadata about faulted or defaulted components of the generation""" + + @model_validator(mode="after") + def validate_generation(self) -> TextGenerationJobSubmitRequest: + if self.generation == "": + logger.error("Generation cannot be an empty string.") + logger.error(self.log_safe_model_dump()) + + return self + + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return "SubmitInputKobold" + + @override + @classmethod + def get_http_method(cls) -> HTTPMethod: + return HTTPMethod.POST + + @override + @classmethod + def get_api_endpoint_subpath(cls) -> AI_HORDE_API_ENDPOINT_SUBPATH: + return AI_HORDE_API_ENDPOINT_SUBPATH.v2_generate_text_submit + + @override + @classmethod + def get_default_success_response_type(cls) -> type[JobSubmitResponse]: + return JobSubmitResponse diff --git a/horde_sdk/ai_horde_api/apimodels/workers/_workers_all.py b/horde_sdk/ai_horde_api/apimodels/workers/_workers.py similarity index 81% rename from horde_sdk/ai_horde_api/apimodels/workers/_workers_all.py rename to horde_sdk/ai_horde_api/apimodels/workers/_workers.py index 839389e..ef3f5a2 100644 --- a/horde_sdk/ai_horde_api/apimodels/workers/_workers_all.py +++ b/horde_sdk/ai_horde_api/apimodels/workers/_workers.py @@ -13,6 +13,7 @@ HordeAPIObject, HordeResponse, ) +from horde_sdk.generic_api.decoration import Unequatable, Unhashable class TeamDetailsLite(HordeAPIObject): @@ -39,6 +40,7 @@ def get_api_model_name(cls) -> str | None: return "WorkerKudosDetails" +@Unhashable class WorkerDetailItem(HordeAPIObject): type_: WORKER_TYPE = Field(alias="type") name: str @@ -124,10 +126,9 @@ def __eq__(self, other: object) -> bool: and self.tokens_generated == other.tokens_generated ) - def __hash__(self) -> int: - raise NotImplementedError("Hashing is not implemented for WorkerDetailItem") - +@Unhashable +@Unequatable class AllWorkersDetailsResponse(HordeResponse, RootModel[list[WorkerDetailItem]]): # @tazlin: The typing of __iter__ in BaseModel seems to assume that RootModel wouldn't also be a parent class. # without a `type: ignore``, mypy feels that this is a bad override. This is probably a sub-optimal solution @@ -144,19 +145,11 @@ def __getitem__(self, item: int) -> WorkerDetailItem: def get_api_model_name(cls) -> str | None: return "WorkerDetails" - def __eq__(self, other: object) -> bool: - if not isinstance(other, AllWorkersDetailsResponse): - return False - return all(worker in other.root for worker in self.root) - - def __hash__(self) -> int: - return hash(tuple(self.root)) - class AllWorkersDetailsRequest(BaseAIHordeRequest, APIKeyAllowedInRequestMixin): """Returns information on all works. If a moderator API key is specified, it will return additional information.""" - type_: WORKER_TYPE = Field(alias="type") + type_: WORKER_TYPE = Field(WORKER_TYPE.all, alias="type") @override @classmethod @@ -175,15 +168,57 @@ def get_http_method(cls) -> HTTPMethod: @override @classmethod - def get_default_success_response_type(cls) -> type[HordeResponse]: + def get_default_success_response_type(cls) -> type[AllWorkersDetailsResponse]: return AllWorkersDetailsResponse @override @classmethod - def get_header_fields(cls) -> list[str]: + def get_query_fields(cls) -> list[str]: return ["type_"] @classmethod def is_api_key_required(cls) -> bool: """Return whether this endpoint requires an API key.""" return False + + +@Unhashable +@Unequatable +class SingleWorkerDetailsResponse(HordeResponse, WorkerDetailItem): + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return "WorkerDetails" + + +class SingleWorkerDetailsRequest(BaseAIHordeRequest, APIKeyAllowedInRequestMixin): + """Returns information on a single worker. + + If a moderator API key is specified, additional information is returned.""" + + worker_id: str | WorkerID = Field(alias="id") + + @override + @classmethod + def get_api_model_name(cls) -> str | None: + return None + + @override + @classmethod + def get_api_endpoint_subpath(cls) -> AI_HORDE_API_ENDPOINT_SUBPATH: + return AI_HORDE_API_ENDPOINT_SUBPATH.v2_workers_single + + @override + @classmethod + def get_http_method(cls) -> HTTPMethod: + return HTTPMethod.GET + + @override + @classmethod + def get_default_success_response_type(cls) -> type[SingleWorkerDetailsResponse]: + return SingleWorkerDetailsResponse + + @classmethod + def is_api_key_required(cls) -> bool: + """Return whether this endpoint requires an API key.""" + return False diff --git a/horde_sdk/ai_horde_api/consts.py b/horde_sdk/ai_horde_api/consts.py index 3fe6148..3d770a7 100644 --- a/horde_sdk/ai_horde_api/consts.py +++ b/horde_sdk/ai_horde_api/consts.py @@ -36,6 +36,7 @@ class WORKER_TYPE(StrEnum): (alchemy, image, text, etc...) """ + all = auto() image = auto() text = auto() interrogation = auto() @@ -90,6 +91,12 @@ class KNOWN_CONTROLNETS(StrEnum): hough = auto() +class KNOWN_WORKFLOWS(StrEnum): + """The controlnets that are known to the API.""" + + qr_code = auto() + + class KNOWN_SOURCE_PROCESSING(StrEnum): """The source processing methods that are known to the API. @@ -223,3 +230,166 @@ class METADATA_VALUE(StrEnum): csam = auto() nsfw = auto() see_ref = auto() + + +class MODEL_STATE(StrEnum): + all = auto() + known = auto() + custom = auto() + + +class MODEL_TYPE(StrEnum): + text = auto() + image = auto() + + +class WarningCode(StrEnum): + NoAvailableWorker = auto() + ClipSkipMismatch = auto() + StepsTooFew = auto() + StepsTooMany = auto() + CfgScaleMismatch = auto() + CfgScaleTooSmall = auto() + CfgScaleTooLarge = auto() + SamplerMismatch = auto() + SchedulerMismatch = auto() + + +class RC(StrEnum): + MissingPrompt = auto() + CorruptPrompt = auto() + KudosValidationError = auto() + NoValidActions = auto() + InvalidSize = auto() + InvalidPromptSize = auto() + TooManySteps = auto() + Profanity = auto() + ProfaneWorkerName = auto() + ProfaneBridgeAgent = auto() + ProfaneWorkerInfo = auto() + ProfaneUserName = auto() + ProfaneUserContact = auto() + ProfaneAdminComment = auto() + ProfaneTeamName = auto() + ProfaneTeamInfo = auto() + TooLong = auto() + TooLongWorkerName = auto() + TooLongUserName = auto() + NameAlreadyExists = auto() + WorkerNameAlreadyExists = auto() + TeamNameAlreadyExists = auto() + PolymorphicNameConflict = auto() + ImageValidationFailed = auto() + SourceImageResolutionExceeded = auto() + SourceImageSizeExceeded = auto() + SourceImageUrlInvalid = auto() + SourceImageUnreadable = auto() + InpaintingMissingMask = auto() + SourceMaskUnnecessary = auto() + UnsupportedSampler = auto() + UnsupportedModel = auto() + ControlNetUnsupported = auto() + ControlNetSourceMissing = auto() + ControlNetInvalidPayload = auto() + SourceImageRequiredForModel = auto() + UnexpectedModelName = auto() + TooManyUpscalers = auto() + ProcGenNotFound = auto() + InvalidAestheticAttempt = auto() + AestheticsNotCompleted = auto() + AestheticsNotPublic = auto() + AestheticsDuplicate = auto() + AestheticsMissing = auto() + AestheticsSolo = auto() + AestheticsConfused = auto() + AestheticsAlreadyExist = auto() + AestheticsServerRejected = auto() + AestheticsServerError = auto() + AestheticsServerDown = auto() + AestheticsServerTimeout = auto() + InvalidAPIKey = auto() + WrongCredentials = auto() + NotAdmin = auto() + NotModerator = auto() + NotOwner = auto() + NotPrivileged = auto() + AnonForbidden = auto() + AnonForbiddenWorker = auto() + AnonForbiddenUserMod = auto() + NotTrusted = auto() + UntrustedTeamCreation = auto() + UntrustedUnsafeIP = auto() + WorkerMaintenance = auto() + WorkerFlaggedMaintenance = auto() + TooManySameIPs = auto() + WorkerInviteOnly = auto() + UnsafeIP = auto() + TimeoutIP = auto() + TooManyNewIPs = auto() + KudosUpfront = auto() + SharedKeyEmpty = auto() + SharedKeyExpired = auto() + SharedKeyInsufficientKudos = auto() + InvalidJobID = auto() + RequestNotFound = auto() + WorkerNotFound = auto() + TeamNotFound = auto() + FilterNotFound = auto() + UserNotFound = auto() + DuplicateGen = auto() + AbortedGen = auto() + RequestExpired = auto() + TooManyPrompts = auto() + NoValidWorkers = auto() + MaintenanceMode = auto() + TargetAccountFlagged = auto() + SourceAccountFlagged = auto() + FaultWhenKudosReceiving = auto() + FaultWhenKudosSending = auto() + TooFastKudosTransfers = auto() + KudosTransferToAnon = auto() + KudosTransferToSelf = auto() + KudosTransferNotEnough = auto() + NegativeKudosTransfer = auto() + KudosTransferFromAnon = auto() + InvalidAwardUsername = auto() + KudosAwardToAnon = auto() + NotAllowedAwards = auto() + NoWorkerModSelected = auto() + NoUserModSelected = auto() + NoHordeModSelected = auto() + NoTeamModSelected = auto() + NoFilterModSelected = auto() + NoSharedKeyModSelected = auto() + BadRequest = auto() + Forbidden = auto() + Locked = auto() + ControlNetMismatch = auto() + HiResFixMismatch = auto() + TooManyLoras = auto() + BadLoraVersion = auto() + TooManyTIs = auto() + BetaAnonForbidden = auto() + BetaComparisonFault = auto() + BadCFGDecimals = auto() + BadCFGNumber = auto() + BadClientAgent = auto() + SpecialMissingPayload = auto() + SpecialForbidden = auto() + SpecialMissingUsername = auto() + SpecialModelNeedsSpecialUser = auto() + SpecialFieldNeedsSpecialUser = auto() + Img2ImgMismatch = auto() + TilingMismatch = auto() + EducationCannotSendKudos = auto() + InvalidPriorityUsername = auto() + OnlyServiceAccountProxy = auto() + RequiresTrust = auto() + InvalidRemixModel = auto() + InvalidExtraSourceImages = auto() + TooManyExtraSourceImages = auto() + MissingFullSamplerOrder = auto() + TooManyStopSequences = auto() + ExcessiveStopSequence = auto() + TokenOverflow = auto() + MoreThanMinExtraSourceImage = auto() diff --git a/horde_sdk/ai_horde_api/endpoints.py b/horde_sdk/ai_horde_api/endpoints.py index cef9dd0..367aa8d 100644 --- a/horde_sdk/ai_horde_api/endpoints.py +++ b/horde_sdk/ai_horde_api/endpoints.py @@ -50,20 +50,25 @@ class AI_HORDE_API_ENDPOINT_SUBPATH(GENERIC_API_ENDPOINT_SUBPATH): v2_generate_text_pop = "/v2/generate/text/pop" v2_generate_text_status = "/v2/generate/text/status/{id}" + v2_generate_rate_id = "/v2/generate/rate/{id}" + v2_interrogate_async = "/v2/interrogate/async" v2_interrogate_status = "/v2/interrogate/status/{id}" v2_interrogate_pop = "/v2/interrogate/pop" v2_interrogate_submit = "/v2/interrogate/submit" + v2_kudos_award = "/v2/kudos/award" v2_kudos_transfer = "/v2/kudos/transfer" v2_sharedkeys_create = "/v2/sharedkeys" - v2_sharedkeys = "/v2_sharedkeys/{sharedkey_id}" + v2_sharedkeys = "/v2/sharedkeys/{sharedkey_id}" v2_status_heartbeat = "/v2/status/heartbeat" + v2_status_modes = "/v2/status/modes" + v2_status_news = "/v2/status/news" v2_status_models_all = "/v2/status/models" - v2_status_models = "/v2/status/models/{model_id}" + v2_status_models_single = "/v2/status/models/{model_name}" v2_status_performance = "/v2/status/performance" @@ -77,7 +82,15 @@ class AI_HORDE_API_ENDPOINT_SUBPATH(GENERIC_API_ENDPOINT_SUBPATH): v2_users = "/v2/users/{user_id}" v2_workers_all = "/v2/workers" - v2_workers = "/v2/workers/{worker_id}" + v2_workers_single = "/v2/workers/{worker_id}" + + v2_filters = "/v2/filters" + v2_filters_regex = "/v2/filters/regex" + v2_filters_regex_single = "/v2/filters/{filter_id}" + + v2_operations_block_worker_ipaddr_single = "/v2/operations/block_worker_ipaddr/{worker_id}" + v2_operations_ipaddr = "/v2/operations/ipaddr" + v2_operations_ipaddr_single = "/v2/operations/ipaddr/{ipaddr}" def get_ai_horde_swagger_url() -> str: diff --git a/horde_sdk/ai_horde_api/exceptions.py b/horde_sdk/ai_horde_api/exceptions.py index bcae490..1882f61 100644 --- a/horde_sdk/ai_horde_api/exceptions.py +++ b/horde_sdk/ai_horde_api/exceptions.py @@ -1,6 +1,8 @@ +from typing import Any + from loguru import logger -from horde_sdk.ai_horde_api.consts import GENERATION_MAX_LIFE +from horde_sdk.ai_horde_api.consts import GENERATION_MAX_LIFE, RC from horde_sdk.exceptions import HordeException from horde_sdk.generic_api.apimodels import RequestErrorResponse @@ -9,10 +11,16 @@ class AIHordeRequestError(HordeException): def __init__(self, error_response: RequestErrorResponse) -> None: logger.error(f"The AI Horde API returned an error response. Response: {error_response.message}") super().__init__(error_response.message) + try: + RC(error_response.rc) + except ValueError: + logger.error( + f"Failed to parse the RC from the error response. RC: {error_response.rc}. Is the SDK out of date?", + ) class AIHordePayloadValidationError(HordeException): - def __init__(self, errors: dict, message: str) -> None: + def __init__(self, errors: dict[str, Any], message: str) -> None: """Exception for when the AI Horde API cannot parse a request payload.""" logger.error(f"The AI Horde API returned an error response. Response: {message}. Errors: {errors}") super().__init__(message) diff --git a/horde_sdk/ai_horde_api/fields.py b/horde_sdk/ai_horde_api/fields.py index 417b51e..1a087e7 100644 --- a/horde_sdk/ai_horde_api/fields.py +++ b/horde_sdk/ai_horde_api/fields.py @@ -55,7 +55,7 @@ def __eq__(self, other: Any) -> bool: if isinstance(other, uuid.UUID): return self.root == other - raise NotImplementedError(f"Cannot compare {self.__class__.__name__} with {other.__class__.__name__}") + return NotImplemented @override def __hash__(self) -> int: @@ -71,7 +71,7 @@ def __lt__(self, other: object) -> bool: if isinstance(other, uuid.UUID): return self.root < other - raise NotImplementedError(f"Cannot compare {self.__class__.__name__} with {other.__class__.__name__}") + return NotImplemented def __gt__(self, other: object) -> bool: if isinstance(other, UUID_Identifier): @@ -83,7 +83,7 @@ def __gt__(self, other: object) -> bool: if isinstance(other, uuid.UUID): return self.root > other - raise NotImplementedError(f"Cannot compare {self.__class__.__name__} with {other.__class__.__name__}") + return NotImplemented def __le__(self, other: object) -> bool: if isinstance(other, UUID_Identifier): @@ -95,7 +95,7 @@ def __le__(self, other: object) -> bool: if isinstance(other, uuid.UUID): return self.root <= other - raise NotImplementedError(f"Cannot compare {self.__class__.__name__} with {other.__class__.__name__}") + return NotImplemented def __ge__(self, other: object) -> bool: if isinstance(other, UUID_Identifier): @@ -107,7 +107,7 @@ def __ge__(self, other: object) -> bool: if isinstance(other, uuid.UUID): return self.root >= other - raise NotImplementedError(f"Cannot compare {self.__class__.__name__} with {other.__class__.__name__}") + return NotImplemented class JobID(UUID_Identifier): diff --git a/horde_sdk/ai_horde_api/metadata.py b/horde_sdk/ai_horde_api/metadata.py index fcf6752..307a118 100644 --- a/horde_sdk/ai_horde_api/metadata.py +++ b/horde_sdk/ai_horde_api/metadata.py @@ -1,6 +1,8 @@ """Request metadata specific to the AI-Horde API.""" -from horde_sdk.generic_api.metadata import GenericPathFields +from enum import auto + +from horde_sdk.generic_api.metadata import GenericPathFields, GenericQueryFields class AIHordePathData(GenericPathFields): @@ -8,13 +10,24 @@ class AIHordePathData(GenericPathFields): id_ = "id" """A job UUID.""" - user_id = "user_id" + user_id = auto() """The horde user id.""" - filter_id = "filter_id" + filter_id = auto() """The ID of a content filter.""" - team_id = "team_id" + team_id = auto() """The UUID of a team.""" - worker_id = "worker_id" + worker_id = auto() """The UUID of a worker.""" - sharedkey_id = "sharedkey_id" + sharedkey_id = auto() """The UUID representing a shared key.""" + model_name = auto() + """The name of a model.""" + ipaddr = auto() + """An IP address.""" + + +class AIHordeQueryData(GenericQueryFields): + """AI Horde specific query data. See parent class for more information.""" + + model_state = auto() + """The level of official support by the API.""" diff --git a/horde_sdk/ai_horde_worker/bridge_data.py b/horde_sdk/ai_horde_worker/bridge_data.py index 15a1abf..d5ce06b 100644 --- a/horde_sdk/ai_horde_worker/bridge_data.py +++ b/horde_sdk/ai_horde_worker/bridge_data.py @@ -44,11 +44,9 @@ class BaseHordeBridgeData(BaseModel): @model_validator(mode="after") def validate_extra_params_warning(self) -> BaseHordeBridgeData: """Warn on extra parameters being passed.""" - if not self.model_extra: - return self - - for key in self.model_extra: - logger.warning(f"Unknown parameter {key} in bridge data file.") + if self.model_extra is not None: + for key in self.model_extra: + logger.warning(f"Unknown parameter {key} in bridge data file.") return self @@ -143,6 +141,9 @@ class ImageWorkerBridgeData(SharedHordeBridgeData): allow_controlnet: bool = False """Whether to allow the use of ControlNet. This requires img2img to be enabled.""" + allow_sdxl_controlnet: bool = False + """Whether to allow the use of SDXL ControlNet. This requires controlnet to be enabled.""" + allow_img2img: bool = True """Whether to allow the use of img2img.""" @@ -224,7 +225,7 @@ class ImageWorkerBridgeData(SharedHordeBridgeData): """A list of models to load. This can be a list of model names, or a list of model loading instructions, such as `top 100` or `all models`.""" - image_models_to_skip: list = Field( + image_models_to_skip: list[str] = Field( default_factory=list, alias="models_to_skip", ) @@ -300,6 +301,15 @@ def validate_model(self) -> ImageWorkerBridgeData: ) self.allow_controlnet = False + if not self.allow_controlnet and self.allow_sdxl_controlnet: + logger.warning( + ( + "allow_sdxl_controlnet is set to True, but allow_controlnet is set to False. " + "SDXL ControlNet requires allow_controlnet to be enabled. Setting allow_sdxl_controlnet to false." + ), + ) + self.allow_sdxl_controlnet = False + self.image_models_to_skip.append("SDXL_beta::stability.ai#6901") # FIXME: no magic strings return self @@ -344,7 +354,7 @@ def handle_meta_skip_instructions(self) -> ImageWorkerBridgeData: return self @field_validator("image_models_to_load") - def validate_models_to_load(cls, v: list) -> list: + def validate_models_to_load(cls, v: list[str]) -> list[str]: """Validate and parse the models to load.""" if not isinstance(v, list): v = [v] @@ -369,7 +379,7 @@ def validate_ram_to_leave_free(cls, v: str | int | float) -> str | int | float: return v @field_validator("forms") - def validate_alchemy_forms(cls, v: list) -> list: + def validate_alchemy_forms(cls, v: list[str]) -> list[str | ALCHEMY_FORMS]: """Validate the alchemy forms (services offered).""" if not isinstance(v, list): raise ValueError("forms must be a list") diff --git a/horde_sdk/ai_horde_worker/model_meta.py b/horde_sdk/ai_horde_worker/model_meta.py index a2757fe..1624e81 100644 --- a/horde_sdk/ai_horde_worker/model_meta.py +++ b/horde_sdk/ai_horde_worker/model_meta.py @@ -6,7 +6,7 @@ from loguru import logger from horde_sdk.ai_horde_api.ai_horde_clients import AIHordeAPIManualClient -from horde_sdk.ai_horde_api.apimodels import StatsImageModelsRequest, StatsModelsResponse, StatsModelsTimeframe +from horde_sdk.ai_horde_api.apimodels import ImageModelStatsResponse, ImageStatsModelsRequest, StatsModelsTimeframe from horde_sdk.ai_horde_worker.bridge_data import MetaInstruction from horde_sdk.generic_api.apimodels import RequestErrorResponse @@ -30,7 +30,7 @@ def resolve_meta_instructions( client: AIHordeAPIManualClient, ) -> set[str]: # Get model stats from the API - stats_response = client.submit_request(StatsImageModelsRequest(), StatsModelsResponse) + stats_response = client.submit_request(ImageStatsModelsRequest(), ImageModelStatsResponse) if isinstance(stats_response, RequestErrorResponse): raise Exception(f"Error getting stats for models: {stats_response.message}") @@ -253,7 +253,7 @@ def resolve_all_models_of_baseline(self, baseline: str) -> set[str]: @staticmethod def resolve_top_n_model_names( number_of_top_models: int, - response: StatsModelsResponse, + response: ImageModelStatsResponse, timeframe: StatsModelsTimeframe, ) -> list[str]: """Get the names of the top N models based on usage statistics. @@ -283,7 +283,7 @@ def resolve_top_n_model_names( @staticmethod def resolve_bottom_n_model_names( number_of_bottom_models: int, - response: StatsModelsResponse, + response: ImageModelStatsResponse, timeframe: StatsModelsTimeframe, ) -> list[str]: """Get the names of the bottom N models based on usage statistics. diff --git a/horde_sdk/generic_api/apimodels.py b/horde_sdk/generic_api/apimodels.py index c97365b..bf5b0c3 100644 --- a/horde_sdk/generic_api/apimodels.py +++ b/horde_sdk/generic_api/apimodels.py @@ -6,6 +6,7 @@ import base64 import os import uuid +from typing import Any import aiohttp from loguru import logger @@ -17,6 +18,15 @@ from horde_sdk.generic_api.endpoints import GENERIC_API_ENDPOINT_SUBPATH, url_with_path from horde_sdk.generic_api.metadata import GenericAcceptTypes +try: + from horde_sdk._version import __version__ +except ImportError: + __version__ = "0.0.0" + logger.warning( + "Could not import version from _version.py. If this is a development environment, this is normal." + " You can fix this by building the package with `python -m build`.", + ) + class HordeAPIObject(BaseModel, abc.ABC): """Base class for all Horde API data models, requests, or responses.""" @@ -35,8 +45,9 @@ def get_api_model_name(cls) -> str | None: class HordeAPIDataObject(BaseModel): """Base class for all Horde API data models which appear as objects within other data models. - These are objects which might not be specifically defined by the API docs, but (logically or otherwise) are - returned by the API. + These are objects which are not specifically defined by the API docs, but (logically or otherwise) are + returned by the API. Occasionally, objects derived from this class may also be used as a mixin to compose other + models. """ model_config = ( @@ -48,13 +59,13 @@ class HordeAPIMessage(HordeAPIObject): """Represents any request or response from any Horde API.""" @classmethod - def get_sensitive_fields(self) -> set[str]: + def get_sensitive_fields(cls) -> set[str]: return {"apikey"} def get_extra_fields_to_exclude_from_log(self) -> set[str]: return set() - def log_safe_model_dump(self) -> dict: + def log_safe_model_dump(self) -> dict[Any, Any]: """Return a dict of the model's fields, with any sensitive fields redacted.""" return self.model_dump(exclude=self.get_sensitive_fields() | self.get_extra_fields_to_exclude_from_log()) @@ -64,6 +75,8 @@ class HordeResponse(HordeAPIMessage): class HordeResponseBaseModel(HordeResponse, BaseModel): + """Base class for all Horde API response data models (leveraging pydantic).""" + model_config = ( ConfigDict(frozen=True) if not os.getenv("TESTS_ONGOING") else ConfigDict(frozen=True, extra="forbid") ) @@ -293,7 +306,7 @@ def get_http_method(cls) -> HTTPMethod: # X_Fields # TODO client_agent: str = Field( - default="horde_sdk:0.7.10:https://githib.com/haidra-org/horde-sdk", # FIXME + default=f"horde_sdk:{__version__}:https://githib.com/haidra-org/horde-sdk", alias="Client-Agent", ) @@ -335,6 +348,15 @@ def get_header_fields(cls) -> list[str]: """ return [] + @classmethod + def get_query_fields(cls) -> list[str]: + """Return a list of field names from this request object that should be sent as query parameters. + + This is in addition to `GenericQueryFields`'s values, and possibly the API specific class + which inherits from `GenericQueryFields`, typically found in the `horde_sdk._api.metadata` module. + """ + return [] + def get_number_of_results_expected(self) -> int: """Return the number of (job) results expected from this request. @@ -357,7 +379,7 @@ def get_requires_follow_up(self) -> bool: @override @classmethod - def get_sensitive_fields(self) -> set[str]: + def get_sensitive_fields(cls) -> set[str]: return {"apikey"} @@ -408,7 +430,7 @@ def user_id_is_numeric(cls, value: str) -> str: return value -class RequestUsesImageWorkerMixin(BaseModel): +class RequestUsesWorkerMixin(BaseModel): """Mix-in class to describe an endpoint for which you can specify workers.""" trusted_workers: bool = False @@ -431,7 +453,7 @@ class RequestUsesImageWorkerMixin(BaseModel): "HordeAPIMessage", "RequestErrorResponse", "RequestSpecifiesUserIDMixin", - "RequestUsesImageWorkerMixin", + "RequestUsesWorkerMixin", "ResponseRequiringFollowUpMixin", "ResponseWithProgressMixin", ] diff --git a/horde_sdk/generic_api/decoration.py b/horde_sdk/generic_api/decoration.py new file mode 100644 index 0000000..2dc7dd3 --- /dev/null +++ b/horde_sdk/generic_api/decoration.py @@ -0,0 +1,68 @@ +from typing import Any, TypeVar + +T = TypeVar("T") + + +def Unhashable(cls: type[T]) -> type[T]: + """A decorator that makes a class unhashable. + + Args: + cls (Any): The class to make unhashable. + + Returns: + Any: The unhashable class. + """ + + cls._unhashable = True # type: ignore + + cls.__hash__ = None # type: ignore + + return cls + + +def is_unhashable(obj: type | Any) -> bool: # noqa: ANN401 + """Check if an object is unhashable. + + Args: + obj (Any): The object to check. + + Returns: + bool: True if the object is unhashable, False otherwise. + """ + cls = obj.__class__ if not isinstance(obj, type) else obj + + return getattr(cls, "_unhashable", False) + + +def Unequatable(cls: type[T]) -> type[T]: + """A decorator that makes a class unequatable + + Args: + cls (type[T]): The class to make unequatable + + Returns: + type[T]: The unequatable class + """ + + cls._unequatable = True # type: ignore + + def __eq__(self, other: Any) -> bool: # type: ignore # noqa: ANN001, ANN401 + return NotImplemented + + cls.__eq__ = __eq__ # type: ignore + + return cls + + +def is_unequatable(obj: type | Any) -> bool: # noqa: ANN401 + """Check if an object is unequatable. + + Args: + obj (Any): The object to check. + + Returns: + bool: True if the object is unequatable, False otherwise. + """ + cls = obj.__class__ if not isinstance(obj, type) else obj + + return getattr(cls, "_unequatable", False) diff --git a/horde_sdk/generic_api/generic_clients.py b/horde_sdk/generic_api/generic_clients.py index c2b60c5..0466ea2 100644 --- a/horde_sdk/generic_api/generic_clients.py +++ b/horde_sdk/generic_api/generic_clients.py @@ -5,7 +5,7 @@ import asyncio import os from abc import ABC -from typing import TypeVar +from typing import Any, TypeVar import aiohttp import requests @@ -38,13 +38,13 @@ class ParsedRawRequest(BaseModel): endpoint_no_query: str """The endpoint URL without any query parameters.""" - request_headers: dict + request_headers: dict[str, Any] """The headers to be sent with the request.""" - request_queries: dict + request_queries: dict[str, Any] """The query parameters to be sent with the request.""" - request_params: dict + request_params: dict[str, Any] """The path parameters to be sent with the request.""" - request_body: dict | None + request_body: dict[str, Any] | None """The body to be sent with the request, or `None` if no body should be sent.""" @@ -82,7 +82,7 @@ def __init__( path_fields: type[GenericPathFields] = GenericPathFields, query_fields: type[GenericQueryFields] = GenericQueryFields, accept_types: type[GenericAcceptTypes] = GenericAcceptTypes, - **kwargs: dict, + **kwargs: Any, # noqa: ANN401 ) -> None: """Initialize a new `GenericHordeAPIClient` instance. @@ -147,7 +147,7 @@ def _validate_and_prepare_request(self, api_request: HordeRequest) -> ParsedRawR Raises: TypeError: If `api_request` is not of type `HordeRequest` or a subclass of it. """ - if not issubclass(api_request.__class__, HordeRequest): + if not isinstance(api_request, HordeRequest): raise TypeError("`request` must be of type `HordeRequest` or a subclass of it!") # Define a helper function to extract specified data keys from the request @@ -158,7 +158,7 @@ def get_specified_data_keys(data_keys: type[StrEnum], api_request: HordeRequest) python_field_name: # The value is the API field name, converted to a string. The python name may not match # as is the case with `id`, which is reserved in python, and `id_` is used instead. - str(api_field_name) + api_field_name.value for python_field_name, api_field_name in data_keys._member_map_.items() if hasattr(api_request, python_field_name) and getattr(api_request, python_field_name) is not None } @@ -182,28 +182,38 @@ def get_specified_data_keys(data_keys: type[StrEnum], api_request: HordeRequest) # Extract any extra header fields and the request body data from the request extra_header_keys: list[str] = api_request.get_header_fields() + extra_query_keys: list[str] = api_request.get_query_fields() - request_params_dict: dict[str, object] = {} - request_headers_dict: dict[str, object] = {} - request_queries_dict: dict[str, object] = {} + request_params_dict: dict[str, Any] = {} + request_headers_dict: dict[str, Any] = {} + request_queries_dict: dict[str, Any] = {} # Extract all fields from the request which are not specified headers, paths, or queries # Note: __dict__ allows access to *all* attributes of an instance - for request_key, request_value in api_request.__dict__.items(): + for request_key, request_value in vars(api_request).items(): + if request_value is None: + continue if request_key in specified_paths: continue if request_key in specified_headers: - request_headers_dict[request_key] = request_value + request_headers_dict[specified_headers[request_key]] = request_value continue if request_key in extra_header_keys: # Remove any trailing underscores from the key as they are used to avoid python keyword conflicts api_name = request_key if not request_key.endswith("_") else request_key[:-1] specified_headers[request_key] = api_name - request_headers_dict[request_key] = request_value + request_headers_dict[api_name] = request_value continue if request_key in specified_queries: - request_queries_dict[request_key] = request_value + request_queries_dict[specified_queries[request_key]] = request_value + continue + + if request_key in extra_query_keys: + # Remove any trailing underscores from the key as they are used to avoid python keyword conflicts + api_name = request_key if not request_key.endswith("_") else request_key[:-1] + specified_queries[request_key] = api_name + request_queries_dict[api_name] = request_value continue request_params_dict[request_key] = request_value @@ -217,18 +227,20 @@ def get_specified_data_keys(data_keys: type[StrEnum], api_request: HordeRequest) ) # Convert the request body data to a dictionary - request_body_data_dict: dict | None = api_request.model_dump( + request_body_data_dict: dict[str, Any] | None = api_request.model_dump( by_alias=True, exclude_none=True, exclude_unset=True, exclude=all_fields_to_exclude_from_body, ) - if request_body_data_dict == {}: + if not request_body_data_dict: + # This is explicitly set to None for clarity that it is unspecified + # i.e., and empty body is not the same as an unspecified body request_body_data_dict = None # Add the API key to the request headers if the request is authenticated and an API key is provided - if request_headers_dict.get("apikey") is None and isinstance(api_request, APIKeyAllowedInRequestMixin): + if isinstance(api_request, APIKeyAllowedInRequestMixin) and "apikey" not in request_headers_dict: request_headers_dict["apikey"] = self._apikey return ParsedRawRequest( @@ -242,7 +254,7 @@ def get_specified_data_keys(data_keys: type[StrEnum], api_request: HordeRequest) def _after_request_handling( self, *, - raw_response_json: dict, + raw_response_json: dict[str, Any], returned_status_code: int, expected_response_type: type[HordeResponseTypeVar], ) -> HordeResponseTypeVar | RequestErrorResponse: @@ -386,7 +398,7 @@ def __init__( path_fields: type[GenericPathFields] = GenericPathFields, query_fields: type[GenericQueryFields] = GenericQueryFields, accept_types: type[GenericAcceptTypes] = GenericAcceptTypes, - **kwargs: dict, + **kwargs: Any, # noqa: ANN401 ) -> None: super().__init__( apikey=apikey, @@ -423,7 +435,7 @@ async def submit_request( parsed_request = self._validate_and_prepare_request(api_request) - raw_response_json: dict = {} + raw_response_json: dict[str, Any] = {} response_status: int = 599 if not self._aiohttp_session: @@ -496,7 +508,7 @@ def submit_request( # Check if this request is a cleanup or follow up request for a prior request # Loop through each item in self._pending_follow_ups list for index, (prior_request, prior_response, cleanup_request) in enumerate(self._pending_follow_ups): - if api_request is cleanup_request: + if cleanup_request is not None and api_request in cleanup_request: if not isinstance(response, RequestErrorResponse): self._pending_follow_ups.pop(index) else: @@ -541,7 +553,7 @@ def __enter__(self) -> GenericHordeAPISession: """Enter the context manager.""" return self - def __exit__(self, exc_type: type[Exception], exc_val: Exception, exc_tb: object) -> bool: + def __exit__(self, exc_type: type[BaseException], exc_val: Exception, exc_tb: object) -> bool: """Exit the context manager.""" # If there was no exception, return True. if exc_type is None: @@ -687,7 +699,7 @@ async def submit_request( # Loop through each item in self._pending_follow_ups list for index, (prior_request, prior_response, cleanup_request) in enumerate(self._pending_follow_ups): - if api_request is cleanup_request: + if cleanup_request is not None and api_request in cleanup_request: if not isinstance(response, RequestErrorResponse): self._pending_follow_ups.pop(index) break @@ -734,7 +746,7 @@ async def __aenter__(self) -> GenericAsyncHordeAPISession: """Enter the context manager asynchronously.""" return self - async def __aexit__(self, exc_type: type[Exception], exc_val: Exception, exc_tb: object) -> bool: + async def __aexit__(self, exc_type: type[BaseException], exc_val: Exception, exc_tb: object) -> bool: """Exit the context manager asynchronously.""" # If there are any requests that haven't been returned yet, log a warning. if self._awaiting_requests: diff --git a/horde_sdk/generic_api/utils/swagger.py b/horde_sdk/generic_api/utils/swagger.py index d3dde79..37d8f6b 100644 --- a/horde_sdk/generic_api/utils/swagger.py +++ b/horde_sdk/generic_api/utils/swagger.py @@ -6,7 +6,7 @@ import re from abc import ABC from pathlib import Path -from typing import ClassVar +from typing import Any, ClassVar import requests from loguru import logger @@ -108,7 +108,7 @@ class SwaggerModelDefinitionSchemaValidation(SwaggerModelEntry): """The model must match at least one of the schemas in this list.""" @model_validator(mode="before") - def one_method_specified(cls, v: dict) -> dict: + def one_method_specified(cls, v: dict[Any, Any]) -> dict[Any, Any]: """Ensure at least one of the validation methods is specified.""" if not any([v.get("allOf"), v.get("oneOf"), v.get("anyOf")]): raise ValueError("At least one of allOf, oneOf, or anyOf must be specified.") @@ -283,6 +283,19 @@ def get_endpoint_method_from_http_method(self, http_method: HTTPMethod | str) -> http_method = HTTPMethod(http_method) return getattr(self, http_method.value.lower(), None) + def _remove_ref_syntax(self, ref: str) -> str: + """Remove the reference syntax from a ref string. + + Args: + ref: The reference string to remove the syntax from. + + Returns: + A string representing the reference without the syntax. + """ + if not ref.startswith("#/definitions/"): + raise ValueError("Reference must start with '#/definitions/'") + return ref[len("#/definitions/") :] + def get_defined_endpoints(self) -> dict[str, SwaggerEndpointMethod]: """Get the endpoints that are specified in the swagger doc.""" return_dict = {} @@ -291,8 +304,32 @@ def get_defined_endpoints(self) -> dict[str, SwaggerEndpointMethod]: return_dict[http_method] = endpoint_method return return_dict + def get_all_request_models(self) -> list[str]: + """Get all the request models for the endpoint.""" + request_models = [] + for _http_method, endpoint_method in self.get_defined_endpoints().items(): + if endpoint_method.parameters: + for param in endpoint_method.parameters: + if ( + param.schema_ + and isinstance(param.schema_, SwaggerEndpointMethodParameterSchemaRef) + and param.schema_.ref is not None + ): + request_models.append(self._remove_ref_syntax(param.schema_.ref)) + return request_models + + def get_all_response_models(self) -> list[str]: + """Get all the response models for the endpoint.""" + response_models = [] + for _http_method, endpoint_method in self.get_defined_endpoints().items(): + if endpoint_method.responses: + for _status_code, response in endpoint_method.responses.items(): + if response.schema_ and response.schema_.ref: + response_models.append(self._remove_ref_syntax(response.schema_.ref)) + return response_models + @model_validator(mode="before") - def at_least_one_method_specified(cls, v: dict) -> dict: + def at_least_one_method_specified(cls, v: dict[Any, Any]) -> dict[Any, Any]: """Ensure at least one method is specified.""" if not any( [ @@ -334,14 +371,16 @@ class SwaggerDoc(BaseModel): definitions: dict[str, SwaggerModelDefinition | SwaggerModelDefinitionSchemaValidation] """The definitions of the models (data structures) used in the API.""" - def get_all_response_examples(self) -> dict[str, dict[HTTPMethod, dict[HTTPStatusCode, dict[str, object] | list]]]: + def get_all_response_examples( + self, + ) -> dict[str, dict[HTTPMethod, dict[HTTPStatusCode, dict[str, object] | list[Any]]]]: """Extract all response examples from the swagger doc. This in the form of: `dict[endpoint_path, dict[http_method, dict[http_status_code, example_response]]]` """ # Create an empty dictionary to hold all endpoint examples. - every_endpoint_example: dict[str, dict[HTTPMethod, dict[HTTPStatusCode, dict[str, object] | list]]] = {} + every_endpoint_example: dict[str, dict[HTTPMethod, dict[HTTPStatusCode, dict[str, object] | list[Any]]]] = {} # Iterate through each endpoint in the Swagger documentation. for endpoint_path, endpoint in self.paths.items(): @@ -362,7 +401,7 @@ def get_all_response_examples(self) -> dict[str, dict[HTTPMethod, dict[HTTPStatu def get_endpoint_examples( self, endpoint: SwaggerEndpoint, - ) -> dict[HTTPMethod, dict[HTTPStatusCode, dict[str, object] | list]]: + ) -> dict[HTTPMethod, dict[HTTPStatusCode, dict[str, object] | list[Any]]]: """Extract response examples for a single endpoint. Args: @@ -373,7 +412,7 @@ def get_endpoint_examples( `dict[http_method, dict[http_status_code, example_response]]` """ # Create an empty dictionary to hold the response examples for each HTTP method used by the endpoint. - endpoint_examples: dict[HTTPMethod, dict[HTTPStatusCode, dict[str, object] | list]] = {} + endpoint_examples: dict[HTTPMethod, dict[HTTPStatusCode, dict[str, object] | list[Any]]] = {} # Iterate through each HTTP method used by the endpoint. for http_method_name, endpoint_method_definition in endpoint.get_defined_endpoints().items(): @@ -390,7 +429,7 @@ def get_endpoint_examples( def get_endpoint_method_examples( self, endpoint_method_definition: SwaggerEndpointMethod, - ) -> dict[HTTPStatusCode, dict[str, object] | list]: + ) -> dict[HTTPStatusCode, dict[str, object] | list[Any]]: """Extract response examples for a single HTTP method used by an endpoint. Args: @@ -401,7 +440,7 @@ def get_endpoint_method_examples( `dict[http_status_code, example_response]` """ # Create an empty dictionary to hold the response examples for each HTTP status code used by the HTTP method. - endpoint_method_examples: dict[HTTPStatusCode, dict[str, object] | list] = {} + endpoint_method_examples: dict[HTTPStatusCode, dict[str, object] | list[Any]] = {} # If there are no defined responses for the HTTP method, return the empty dictionary of response examples. if not endpoint_method_definition.responses: @@ -423,7 +462,7 @@ def get_endpoint_method_examples( # Return the dictionary of response examples for the HTTP method. return endpoint_method_examples - def get_response_example(self, response_definition: SwaggerEndpointResponse) -> dict[str, object] | list: + def get_response_example(self, response_definition: SwaggerEndpointResponse) -> dict[str, object] | list[Any]: """Extract an example response for a single HTTP response definition. Args: @@ -433,7 +472,7 @@ def get_response_example(self, response_definition: SwaggerEndpointResponse) -> A dictionary or list representing an example response for the HTTP response definition. """ # Create an empty dictionary or list to hold the example response. - example_response: dict[str, object] | list = {} + example_response: dict[str, object] | list[Any] = {} # If there is no response schema, return the empty dictionary or list of example response. if not response_definition.schema_: @@ -651,7 +690,7 @@ def _resolve_model_ref_defaults( ref: str | None, *, _recursed_property_name: str | None = None, - ) -> dict[str, object] | list[dict[str, object]]: + ) -> dict[str, Any] | list[dict[str, Any]]: """For ref entries, recursively resolve the default values for all properties. Note that this will combine all properties from all definitions that are referenced. @@ -667,8 +706,8 @@ def _resolve_model_ref_defaults( # objects of the same type. In this case, three dummy entries are created in `return_list`, which are # recognized on recursion and ultimately returned as a dict. # This solution was implemented in response to the SinglePeriodImgModelStats model, see it for an example. - return_dict: dict = {} - return_list: list[dict[str, object]] = [] + return_dict: dict[str, Any] = {} + return_list: list[dict[str, Any]] = [] # Remove the "#/definitions/" prefix from the ref as it is fixed for all refs and not needed if ref.startswith("#/definitions/"): @@ -834,7 +873,7 @@ def get_default_with_constraint(self, model_property: SwaggerModelProperty) -> o class SwaggerParser: """Parse a swagger doc from a URL or a local file.""" - _swagger_json: dict + _swagger_json: dict[str, Any] def __init__( self, diff --git a/horde_sdk/logging.py b/horde_sdk/horde_logging.py similarity index 91% rename from horde_sdk/logging.py rename to horde_sdk/horde_logging.py index 71e0bc1..c1a3eae 100644 --- a/horde_sdk/logging.py +++ b/horde_sdk/horde_logging.py @@ -1,5 +1,6 @@ import os import sys +from typing import Any from loguru import logger @@ -17,25 +18,25 @@ def set_logger_verbosity(count: int) -> None: verbosity = 20 - (count * 10) -def is_stdout_log(record: dict) -> bool: +def is_stdout_log(record: dict[str, Any]) -> bool: if record["level"].no < verbosity: return False return True -def is_msg_log(record: dict) -> bool: +def is_msg_log(record: dict[str, Any]) -> bool: if record["level"].no < verbosity: return False return True -def is_stderr_log(record: dict) -> bool: +def is_stderr_log(record: dict[str, Any]) -> bool: if record["level"].name not in error_levels: return False return True -def is_trace_log(record: dict) -> bool: +def is_trace_log(record: dict[str, Any]) -> bool: if record["level"].name not in error_levels: return False return True diff --git a/horde_sdk/meta.py b/horde_sdk/meta.py new file mode 100644 index 0000000..b3a4ff1 --- /dev/null +++ b/horde_sdk/meta.py @@ -0,0 +1,127 @@ +import importlib +import inspect +import pkgutil +import types +from functools import cache + +import horde_sdk +import horde_sdk.ai_horde_api +import horde_sdk.ai_horde_api.apimodels +import horde_sdk.ai_horde_worker +import horde_sdk.ratings_api +import horde_sdk.ratings_api.apimodels +from horde_sdk import HordeAPIObject, HordeRequest +from horde_sdk.ai_horde_api.endpoints import AI_HORDE_API_ENDPOINT_SUBPATH, get_ai_horde_swagger_url +from horde_sdk.generic_api.utils.swagger import SwaggerParser + + +@cache +def find_subclasses(module_or_package: types.ModuleType, super_type: type) -> list[type]: + subclasses: list[type] = [] + + if hasattr(module_or_package, "__package__") and module_or_package.__package__ is not None: + module_or_package = importlib.import_module(module_or_package.__package__) + + for _importer, modname, _ispkg in pkgutil.walk_packages( + path=module_or_package.__path__, + prefix=module_or_package.__name__ + ".", + onerror=lambda x: None, + ): + module = importlib.import_module(modname) + for name in dir(module): + obj = getattr(module, name) + if ( + isinstance(obj, type) + and issubclass(obj, super_type) + and obj is not super_type + and not inspect.isabstract(obj) + ): + subclasses.append(obj) + return subclasses + + +def any_unimported_classes(module: types.ModuleType, super_type: type) -> tuple[bool, set[type]]: + """Check if any classes in the module are not imported in the `__init__.py` of the apimodels namespace. + + Args: + module (types.ModuleType): The module to check. + super_type (type): The super type of the classes to check. + + Returns: + tuple[bool, set[type]]: A tuple with a boolean indicating if there are any unimported classes and a set of the + unimported classes. + """ + module_found_classes = find_subclasses(module, super_type) + + missing_classes = set() + + for class_type in module_found_classes: + if class_type.__name__ not in module.__all__: + missing_classes.add(class_type) + + return bool(missing_classes), missing_classes + + +def all_undefined_classes(module: types.ModuleType) -> dict[str, str]: + """Return all of the models defined on the API but not in the SDK.""" + + module_found_classes = find_subclasses(module, HordeAPIObject) + + defined_api_object_names: set[str] = set() + + for class_type in module_found_classes: + if not issubclass(class_type, HordeAPIObject): + raise TypeError(f"Expected {class_type} to be a HordeAPIObject") + + api_model_name = class_type.get_api_model_name() + if api_model_name is not None: + defined_api_object_names.add(api_model_name) + + undefined_classes: dict[str, str] = {} + + parser = SwaggerParser(swagger_doc_url=get_ai_horde_swagger_url()) + swagger_doc = parser.get_swagger_doc() + + for path, swagger_endpoint in swagger_doc.paths.items(): + endpoint_request_models: list[str] = swagger_endpoint.get_all_request_models() + endpoint_response_models: list[str] = swagger_endpoint.get_all_response_models() + + for model_name in endpoint_request_models + endpoint_response_models: + if model_name not in defined_api_object_names: + undefined_classes[model_name] = path + + return undefined_classes + + +def all_unknown_endpoints_ai_horde() -> set[str]: + """Return all of the endpoints defined on the API but not known by the SDK.""" + parser = SwaggerParser(swagger_doc_url=get_ai_horde_swagger_url()) + swagger_doc = parser.get_swagger_doc() + + known_paths = set(AI_HORDE_API_ENDPOINT_SUBPATH.__members__.values()) + unknown_paths = set() + + for path, _swagger_endpoint in swagger_doc.paths.items(): + if path not in known_paths: + print(f"Unknown path: {path}") + unknown_paths.add(path) + + return unknown_paths + + +def all_unaddressed_endpoints_ai_horde() -> set[AI_HORDE_API_ENDPOINT_SUBPATH]: + """Return all of the endpoints known by the SDK but with no corresponding request.""" + known_paths = set(AI_HORDE_API_ENDPOINT_SUBPATH.__members__.values()) + known_paths.remove(AI_HORDE_API_ENDPOINT_SUBPATH.swagger) + unaddressed_paths = set() + + all_classes = find_subclasses(horde_sdk.ai_horde_api.apimodels, HordeAPIObject) + + all_classes_paths = {cls.get_api_endpoint_subpath() for cls in all_classes if issubclass(cls, HordeRequest)} + + for path in known_paths: + if path not in all_classes_paths: + print(f"Unaddressed path: {path}") + unaddressed_paths.add(path) + + return unaddressed_paths diff --git a/horde_sdk/ratings_api/apimodels.py b/horde_sdk/ratings_api/apimodels.py index 969f7e0..f1bd127 100644 --- a/horde_sdk/ratings_api/apimodels.py +++ b/horde_sdk/ratings_api/apimodels.py @@ -296,3 +296,24 @@ def get_default_success_response_type(cls) -> type[UserRatingsResponse]: # endregion + +__all__ = [ + "BaseImageRatingRecord", + "BaseRatingsAPIRequest", + "BaseSelectableReturnTypeRequest", + "HordeRequestImageSpecific", + "ImageRatingResponseSubRecord", + "ImageRatingsComparisonTypes", + "ImageRatingsFilterableRequestBase", + "ImageRatingsRequest", + "ImageRatingsResponse", + "SelectableReturnFormats", + "UserCheckRequest", + "UserCheckResponse", + "UserRatingsRequest", + "UserRatingsResponse", + "UserRatingsResponseSubRecord", + "UserValidateRequest", + "UserValidateResponse", + "UserValidateResponseRecord", +] diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index 1d8279e..0000000 --- a/mypy.ini +++ /dev/null @@ -1,3 +0,0 @@ -[mypy] -disallow_untyped_defs = True -exclude = build diff --git a/pyproject.toml b/pyproject.toml index abd0538..00067cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,6 +99,27 @@ exclude = ''' plugins = [ "pydantic.mypy" ] +strict = true +disallow_untyped_defs = true +warn_unused_configs = true +exclude = [ + '^codegen', + '\.git', + '\.hg', + '\.mypy_cache', + '\.tox', + '\.venv', + '_build', + 'buck-out', + 'build', + 'dist', +] [tool.coverage.run] concurrency = ["gevent"] + +[tool.pytest.ini_options] +markers = [ + # "slow: marks tests as slow (deselect with '-m \"not slow\"')", + "object_verify: marks tests that verify the API object structure and layout", +] diff --git a/requirements.dev.txt b/requirements.dev.txt index 0ddc69a..32ff436 100644 --- a/requirements.dev.txt +++ b/requirements.dev.txt @@ -1,8 +1,8 @@ -pytest==8.1.1 -mypy==1.9.0 -black==24.3.0 -ruff==0.3.4 -tox~=4.14.2 +pytest==8.2.0 +mypy==1.10.0 +black==24.4.2 +ruff==0.4.2 +tox~=4.15.0 pre-commit~=3.7.0 build>=0.10.0 coverage>=7.2.7 diff --git a/tests/ai_horde_api/test_ai_horde_api_calls.py b/tests/ai_horde_api/test_ai_horde_api_calls.py index 8f961ff..5eaa54b 100644 --- a/tests/ai_horde_api/test_ai_horde_api_calls.py +++ b/tests/ai_horde_api/test_ai_horde_api_calls.py @@ -71,7 +71,7 @@ def test_workers_all(self) -> None: """Test the all workers endpoint.""" client = AIHordeAPIManualClient() - api_request = AllWorkersDetailsRequest(type=WORKER_TYPE.image) + api_request = AllWorkersDetailsRequest() api_response = client.submit_request( api_request, @@ -101,6 +101,41 @@ def test_workers_all(self) -> None: with open(_PRODUCTION_RESPONSES_FOLDER / Path(filename + "_production.json"), "w", encoding="utf-8") as f: f.write(api_response.model_dump_json()) + api_request_image = AllWorkersDetailsRequest(type=WORKER_TYPE.image) + api_response_image = client.submit_request( + api_request_image, + api_request_image.get_default_success_response_type(), + ) + + if isinstance(api_response_image, RequestErrorResponse): + pytest.fail(f"API Response was an error: {api_response_image.message}") + + assert isinstance(api_response_image, AllWorkersDetailsResponse) + assert all(worker.type_ == WORKER_TYPE.image for worker in api_response_image.root) + + api_request_text = AllWorkersDetailsRequest(type=WORKER_TYPE.text) + api_response_text = client.submit_request( + api_request_text, + api_request_text.get_default_success_response_type(), + ) + + if isinstance(api_response_text, RequestErrorResponse): + pytest.fail(f"API Response was an error: {api_response_text.message}") + + assert isinstance(api_response_text, AllWorkersDetailsResponse) + assert all(worker.type_ == WORKER_TYPE.text for worker in api_response_text.root) + + api_request_interrogation = AllWorkersDetailsRequest(type=WORKER_TYPE.interrogation) + api_response_interrogation = client.submit_request( + api_request_interrogation, + api_request_interrogation.get_default_success_response_type(), + ) + + if isinstance(api_response_interrogation, RequestErrorResponse): + pytest.fail(f"API Response was an error: {api_response_interrogation.message}") + + assert isinstance(api_response_interrogation, AllWorkersDetailsResponse) + def test_HordeRequestSession_cleanup(self, simple_image_gen_request: ImageGenerateAsyncRequest) -> None: """Test that the context manager cleans up correctly.""" with pytest.raises(HordeTestException), AIHordeAPIClientSession() as horde_session: diff --git a/tests/ai_horde_api/test_ai_horde_api_models.py b/tests/ai_horde_api/test_ai_horde_api_models.py index 9d9b671..df5e37c 100644 --- a/tests/ai_horde_api/test_ai_horde_api_models.py +++ b/tests/ai_horde_api/test_ai_horde_api_models.py @@ -31,7 +31,7 @@ ImageGenerateJobPopResponse, ImageGenerateJobPopSkippedStatus, ) -from horde_sdk.ai_horde_api.apimodels.workers._workers_all import ( +from horde_sdk.ai_horde_api.apimodels.workers._workers import ( AllWorkersDetailsResponse, TeamDetailsLite, WorkerDetailItem, diff --git a/tests/ai_horde_api/test_ai_horde_generate_api_calls.py b/tests/ai_horde_api/test_ai_horde_generate_api_calls.py index 101bc96..e252b5f 100644 --- a/tests/ai_horde_api/test_ai_horde_generate_api_calls.py +++ b/tests/ai_horde_api/test_ai_horde_generate_api_calls.py @@ -1,4 +1,5 @@ import asyncio +from typing import Any import aiohttp import pytest @@ -390,7 +391,7 @@ async def test_simple_client_async_image_generate_multiple_with_timeout( assert simple_image_gen_n_requests.params is not None assert len(image_generate_status_response.generations) < simple_image_gen_n_requests.params.n - async def delayed_cancel(self, task: asyncio.Task) -> None: + async def delayed_cancel(self, task: asyncio.Task[Any]) -> None: """Cancel the task after 4 seconds.""" await asyncio.sleep(4) assert task.cancel("Test cancel") diff --git a/tests/ai_horde_api/test_ai_horde_stats_api_calls.py b/tests/ai_horde_api/test_ai_horde_stats_api_calls.py new file mode 100644 index 0000000..1f4e0a0 --- /dev/null +++ b/tests/ai_horde_api/test_ai_horde_stats_api_calls.py @@ -0,0 +1,149 @@ +import aiohttp +import pytest + +from horde_sdk import RequestErrorResponse +from horde_sdk.ai_horde_api.ai_horde_clients import ( + AIHordeAPIAsyncClientSession, +) +from horde_sdk.ai_horde_api.apimodels._stats import ( + ImageModelStatsResponse, + ImageStatsModelsRequest, + ImageStatsModelsTotalRequest, + ImageStatsModelsTotalResponse, + SinglePeriodImgStat, + TextModelStatsResponse, + TextStatsModelsRequest, + TextStatsModelsTotalRequest, + TextStatsModelsTotalResponse, +) + + +class TestAIHordeStats: + + @pytest.mark.asyncio + async def test_get_image_stats_models(self) -> None: + async with ( + aiohttp.ClientSession() as aiohttp_session, + AIHordeAPIAsyncClientSession(aiohttp_session=aiohttp_session) as client, + ): + request = ImageStatsModelsRequest() + response = await client.submit_request( + request, + expected_response_type=ImageModelStatsResponse, + ) + + if isinstance(response, RequestErrorResponse): + raise AssertionError(f"Request failed: {response}") + + assert response is not None + assert isinstance(response, ImageModelStatsResponse) + assert isinstance(response.day, dict) + assert isinstance(response.month, dict) + assert isinstance(response.total, dict) + + request_known = ImageStatsModelsRequest(model_state="known") + response_known = await client.submit_request( + request_known, + expected_response_type=ImageModelStatsResponse, + ) + + if isinstance(response_known, RequestErrorResponse): + raise AssertionError(f"Request failed: {response_known}") + + assert response_known is not None + assert isinstance(response_known, ImageModelStatsResponse) + assert isinstance(response_known.day, dict) + assert isinstance(response_known.month, dict) + assert isinstance(response_known.total, dict) + + request_custom = ImageStatsModelsRequest(model_state="custom") + response_custom = await client.submit_request( + request_custom, + expected_response_type=ImageModelStatsResponse, + ) + + if isinstance(response_custom, RequestErrorResponse): + raise AssertionError(f"Request failed: {response_custom}") + + assert response_custom is not None + assert isinstance(response_custom, ImageModelStatsResponse) + assert isinstance(response_custom.day, dict) + assert isinstance(response_custom.month, dict) + assert isinstance(response_custom.total, dict) + + if (not isinstance(response, ImageModelStatsResponse) or response.month is None) or ( + not isinstance(response_custom, ImageModelStatsResponse) or response_custom.month is None + ): + pytest.skip("No data to compare. Is this a development environment?") + else: + assert len(response.month) != len(response_custom.month) + + @pytest.mark.asyncio + async def test_get_image_stats_models_total(self) -> None: + request = ImageStatsModelsTotalRequest() + async with ( + aiohttp.ClientSession() as aiohttp_session, + AIHordeAPIAsyncClientSession(aiohttp_session=aiohttp_session) as client, + ): + response = await client.submit_request( + request, + expected_response_type=ImageStatsModelsTotalResponse, + ) + + if isinstance(response, RequestErrorResponse): + raise AssertionError(f"Request failed: {response}") + + assert response is not None + assert isinstance(response, ImageStatsModelsTotalResponse) + assert isinstance(response.day, SinglePeriodImgStat) + assert isinstance(response.hour, SinglePeriodImgStat) + assert isinstance(response.minute, SinglePeriodImgStat) + assert isinstance(response.month, SinglePeriodImgStat) + assert isinstance(response.total, SinglePeriodImgStat) + assert response.total.images is not None + assert response.total.ps is not None + assert response.total.mps is not None + + @pytest.mark.asyncio + async def test_get_text_stats_models(self) -> None: + async with ( + aiohttp.ClientSession() as aiohttp_session, + AIHordeAPIAsyncClientSession(aiohttp_session=aiohttp_session) as client, + ): + request = TextStatsModelsRequest() + response = await client.submit_request( + request, + expected_response_type=TextModelStatsResponse, + ) + + if isinstance(response, RequestErrorResponse): + raise AssertionError(f"Request failed: {response}") + + assert response is not None + assert isinstance(response, TextModelStatsResponse) + assert isinstance(response.day, dict) + assert isinstance(response.month, dict) + assert isinstance(response.total, dict) + + @pytest.mark.asyncio + async def test_get_text_stats_models_total(self) -> None: + async with ( + aiohttp.ClientSession() as aiohttp_session, + AIHordeAPIAsyncClientSession(aiohttp_session=aiohttp_session) as client, + ): + request = TextStatsModelsTotalRequest() + response = await client.submit_request( + request, + expected_response_type=TextStatsModelsTotalResponse, + ) + + if isinstance(response, RequestErrorResponse): + raise AssertionError(f"Request failed: {response}") + + assert response is not None + assert isinstance(response, TextStatsModelsTotalResponse) + assert isinstance(response.minute, dict) + assert isinstance(response.hour, dict) + assert isinstance(response.day, dict) + assert isinstance(response.month, dict) + assert isinstance(response.total, dict) diff --git a/tests/ai_horde_api/test_ai_horde_status_api_calls.py b/tests/ai_horde_api/test_ai_horde_status_api_calls.py new file mode 100644 index 0000000..e4e1fd2 --- /dev/null +++ b/tests/ai_horde_api/test_ai_horde_status_api_calls.py @@ -0,0 +1,254 @@ +import aiohttp +import pytest + +from horde_sdk import RequestErrorResponse +from horde_sdk.ai_horde_api.ai_horde_clients import ( + AIHordeAPIAsyncClientSession, +) +from horde_sdk.ai_horde_api.apimodels._status import ( + ActiveModel, + AIHordeHeartbeatRequest, + AIHordeHeartbeatResponse, + HordePerformanceRequest, + HordePerformanceResponse, + HordeStatusModelsAllRequest, + HordeStatusModelsAllResponse, + HordeStatusModelsSingleRequest, + HordeStatusModelsSingleResponse, + Newspiece, + NewsRequest, + NewsResponse, +) + + +class TestAIHordeStatus: + + @pytest.mark.asyncio + async def test_ai_horde_heartbeat(self) -> None: + async with ( + aiohttp.ClientSession() as aiohttp_session, + AIHordeAPIAsyncClientSession(aiohttp_session=aiohttp_session) as client, + ): + request = AIHordeHeartbeatRequest() + response = await client.submit_request( + request, + expected_response_type=AIHordeHeartbeatResponse, + ) + + if isinstance(response, RequestErrorResponse): + raise AssertionError(f"Request failed: {response}") + + assert response is not None + assert isinstance(response, AIHordeHeartbeatResponse) + + @pytest.mark.asyncio + async def test_horde_performance(self) -> None: + async with ( + aiohttp.ClientSession() as aiohttp_session, + AIHordeAPIAsyncClientSession(aiohttp_session=aiohttp_session) as client, + ): + request = HordePerformanceRequest() + response = await client.submit_request( + request, + expected_response_type=HordePerformanceResponse, + ) + + if isinstance(response, RequestErrorResponse): + raise AssertionError(f"Request failed: {response}") + + assert response is not None + assert isinstance(response, HordePerformanceResponse) + + @pytest.mark.asyncio + async def test_horde_status_models_all(self) -> None: + async with ( + aiohttp.ClientSession() as aiohttp_session, + AIHordeAPIAsyncClientSession(aiohttp_session=aiohttp_session) as client, + ): + request = HordeStatusModelsAllRequest() + response = await client.submit_request( + request, + expected_response_type=HordeStatusModelsAllResponse, + ) + + if isinstance(response, RequestErrorResponse): + raise AssertionError(f"Request failed: {response}") + + assert response is not None + assert isinstance(response, HordeStatusModelsAllResponse) + assert len(response.root) > 0 + + for model in response: + assert isinstance(model, ActiveModel) + assert model.type_ == "image" + + text_request = HordeStatusModelsAllRequest(type="text") + text_response = await client.submit_request( + text_request, + expected_response_type=HordeStatusModelsAllResponse, + ) + + if isinstance(text_response, RequestErrorResponse): + raise AssertionError(f"Request failed: {text_response}") + + assert text_response is not None + assert isinstance(text_response, HordeStatusModelsAllResponse) + assert len(text_response.root) > 0 + + for model in text_response: + assert isinstance(model, ActiveModel) + assert model.type_ == "text" + + @pytest.mark.asyncio + async def test_horde_status_models_single_image(self) -> None: + async with ( + aiohttp.ClientSession() as aiohttp_session, + AIHordeAPIAsyncClientSession(aiohttp_session=aiohttp_session) as client, + ): + request = HordeStatusModelsAllRequest() + response = await client.submit_request( + request, + expected_response_type=HordeStatusModelsAllResponse, + ) + + if isinstance(response, RequestErrorResponse): + raise AssertionError(f"Request failed: {response}") + + assert response is not None + assert isinstance(response, HordeStatusModelsAllResponse) + assert len(response.root) > 0 + + for model in response: + assert isinstance(model, ActiveModel) + assert model.type_ == "image" + + # Pick two random models + import random + + random_model_1 = random.choice(response.root) + random_model_2 = random.choice(response.root) + + request_single_1 = HordeStatusModelsSingleRequest(model_name=random_model_1.name) + response_single_1 = await client.submit_request( + request_single_1, + expected_response_type=HordeStatusModelsSingleResponse, + ) + + if isinstance(response_single_1, RequestErrorResponse): + raise AssertionError(f"Request failed: {response_single_1}") + + assert response_single_1 is not None + assert isinstance(response_single_1, HordeStatusModelsSingleResponse) + assert response_single_1[0].name == random_model_1.name + assert response_single_1[0].type_ == "image" + + request_single_2 = HordeStatusModelsSingleRequest(model_name=random_model_2.name) + response_single_2 = await client.submit_request( + request_single_2, + expected_response_type=HordeStatusModelsSingleResponse, + ) + + if isinstance(response_single_2, RequestErrorResponse): + raise AssertionError(f"Request failed: {response_single_2}") + + assert response_single_2 is not None + assert isinstance(response_single_2, HordeStatusModelsSingleResponse) + assert response_single_2[0].name == random_model_2.name + assert response_single_2[0].type_ == "image" + + @pytest.mark.asyncio + async def test_horde_status_models_single_text(self) -> None: + async with ( + aiohttp.ClientSession() as aiohttp_session, + AIHordeAPIAsyncClientSession(aiohttp_session=aiohttp_session) as client, + ): + request = HordeStatusModelsAllRequest(type="text") + response = await client.submit_request( + request, + expected_response_type=HordeStatusModelsAllResponse, + ) + + if isinstance(response, RequestErrorResponse): + raise AssertionError(f"Request failed: {response}") + + assert response is not None + assert isinstance(response, HordeStatusModelsAllResponse) + assert len(response.root) > 0 + + for model in response: + assert isinstance(model, ActiveModel) + assert model.type_ == "text" + + # Pick two random models + import random + + random_model_1 = random.choice(response.root) + random_model_2 = random.choice(response.root) + + import urllib.parse + + assert random_model_1.name is not None + model_1_name_double_encoded = urllib.parse.quote(random_model_1.name, safe="") + model_1_name_double_encoded = urllib.parse.quote(model_1_name_double_encoded, safe="") + + request_single_1 = HordeStatusModelsSingleRequest(model_name=model_1_name_double_encoded) + response_single_1 = await client.submit_request( + request_single_1, + expected_response_type=HordeStatusModelsSingleResponse, + ) + + if isinstance(response_single_1, RequestErrorResponse): + raise AssertionError(f"Request failed: {response_single_1}") + + assert response_single_1 is not None + assert isinstance(response_single_1, HordeStatusModelsSingleResponse) + if len(response_single_1.root) == 0: + pytest.skip("No data to compare. Is this a development environment?") + else: + assert response_single_1[0].name == random_model_1.name + assert response_single_1[0].type_ == "text" + + assert random_model_2.name is not None + model_2_name_double_encoded = urllib.parse.quote(random_model_2.name, safe="") + model_2_name_double_encoded = urllib.parse.quote(model_2_name_double_encoded, safe="") + + request_single_2 = HordeStatusModelsSingleRequest(model_name=model_2_name_double_encoded) + response_single_2 = await client.submit_request( + request_single_2, + expected_response_type=HordeStatusModelsSingleResponse, + ) + + if isinstance(response_single_2, RequestErrorResponse): + raise AssertionError(f"Request failed: {response_single_2}") + + assert response_single_2 is not None + assert isinstance(response_single_2, HordeStatusModelsSingleResponse) + if len(response_single_2.root) == 0: + pytest.skip("No data to compare. Is this a development environment?") + else: + assert response_single_2[0].name == random_model_2.name + assert response_single_2[0].type_ == "text" + + @pytest.mark.asyncio + async def test_news(self) -> None: + async with ( + aiohttp.ClientSession() as aiohttp_session, + AIHordeAPIAsyncClientSession(aiohttp_session=aiohttp_session) as client, + ): + request = NewsRequest() + response = await client.submit_request( + request, + expected_response_type=NewsResponse, + ) + + if isinstance(response, RequestErrorResponse): + raise AssertionError(f"Request failed: {response}") + + assert response is not None + assert isinstance(response, NewsResponse) + assert len(response.root) > 0 + + for news in response: + assert isinstance(news, Newspiece) + assert news.date_published is not None + assert news.newspiece is not None diff --git a/tests/ai_horde_api/test_dynamically_validate_against_swagger.py b/tests/ai_horde_api/test_dynamically_validate_against_swagger.py index 7b8d282..762b546 100644 --- a/tests/ai_horde_api/test_dynamically_validate_against_swagger.py +++ b/tests/ai_horde_api/test_dynamically_validate_against_swagger.py @@ -1,4 +1,7 @@ import json +from typing import Any + +import pytest import horde_sdk.ai_horde_api.apimodels from horde_sdk.ai_horde_api.endpoints import get_ai_horde_swagger_url @@ -13,6 +16,7 @@ ) +@pytest.mark.object_verify def all_ai_horde_model_defs_in_swagger(swagger_doc: SwaggerDoc) -> None: """Ensure all models defined in ai_horde_api are defined in the swagger doc.""" all_request_types: list[type[HordeRequest]] = get_all_request_types(horde_sdk.ai_horde_api.apimodels.__name__) @@ -25,7 +29,7 @@ def all_ai_horde_model_defs_in_swagger(swagger_doc: SwaggerDoc) -> None: swagger_defined_payload_examples: dict[str, dict[HTTPMethod, dict[str, object]]] swagger_defined_payload_examples = swagger_doc.get_all_payload_examples() - swagger_defined_response_examples: dict[str, dict[HTTPMethod, dict[HTTPStatusCode, dict[str, object] | list]]] + swagger_defined_response_examples: dict[str, dict[HTTPMethod, dict[HTTPStatusCode, dict[str, object] | list[Any]]]] swagger_defined_response_examples = swagger_doc.get_all_response_examples() api_to_sdk_payload_model_map: dict[str, dict[HTTPMethod, type[HordeRequest]]] = {} @@ -34,6 +38,8 @@ def all_ai_horde_model_defs_in_swagger(swagger_doc: SwaggerDoc) -> None: request_field_names_and_descriptions: dict[str, list[tuple[str, str | None]]] = {} response_field_names_and_descriptions: dict[str, list[tuple[str, str | None]]] = {} + default_num_request_fields = len(HordeRequest.model_fields) + for request_type in all_request_types: endpoint_subpath: GENERIC_API_ENDPOINT_SUBPATH = request_type.get_api_endpoint_subpath() assert endpoint_subpath, f"Failed to get endpoint subpath for {request_type.__name__}" @@ -70,11 +76,16 @@ def all_ai_horde_model_defs_in_swagger(swagger_doc: SwaggerDoc) -> None: endpoint_subpath in swagger_defined_payload_examples ), f"Missing {request_type.__name__} in swagger examples" - endpoint_http_method_examples = swagger_defined_response_examples.get(endpoint_subpath) - assert endpoint_http_method_examples, f"Failed to get all HTTP method examples for {endpoint_subpath}" + endpoint_http_status_code_responses: dict[HTTPStatusCode, dict[str, object] | list[Any]] | None | None = None - endpoint_http_status_code_responses = endpoint_http_method_examples.get(request_type.get_http_method()) - assert endpoint_http_status_code_responses, f"Failed to get example response for {request_type.__name__}" + if len(request_type.model_fields) == default_num_request_fields: + print(f"Request type {request_type.__name__} has no additional fields") + else: + endpoint_http_method_examples = swagger_defined_response_examples.get(endpoint_subpath) + assert endpoint_http_method_examples, f"Failed to get all HTTP method examples for {endpoint_subpath}" + + endpoint_http_status_code_responses = endpoint_http_method_examples.get(request_type.get_http_method()) + assert endpoint_http_status_code_responses, f"Failed to get example response for {request_type.__name__}" if endpoint_subpath not in api_to_sdk_payload_model_map: api_to_sdk_payload_model_map[endpoint_subpath] = {} @@ -87,23 +98,34 @@ def all_ai_horde_model_defs_in_swagger(swagger_doc: SwaggerDoc) -> None: request_field_names_and_descriptions[request_type.__name__].append((field_name, field_info.description)) - endpoint_success_http_status_codes: list[HTTPStatusCode] = [ - success_code - for success_code in get_all_success_status_codes() - if success_code in endpoint_http_status_code_responses - ] - assert ( - len(endpoint_success_http_status_codes) > 0 - ), f"Failed to find any success status codes in {request_type.__name__}" + endpoint_success_http_status_codes: list[HTTPStatusCode] = [] + + if endpoint_http_status_code_responses is not None: + endpoint_success_http_status_codes = [ + success_code + for success_code in get_all_success_status_codes() + if success_code in endpoint_http_status_code_responses + ] + assert ( + len(endpoint_success_http_status_codes) > 0 + ), f"Failed to find any success status codes in {request_type.__name__}" - for success_code in endpoint_success_http_status_codes: + for success_code in endpoint_success_http_status_codes: + assert ( + success_code in request_type.get_success_status_response_pairs() + ), f"Missing success response type for {request_type.__name__} with status code {success_code}" + else: assert ( - success_code in request_type.get_success_status_response_pairs() - ), f"Missing success response type for {request_type.__name__} with status code {success_code}" + request_type.get_default_success_response_type() is not None + ), f"Failed to get default success response type for {request_type.__name__}" api_to_sdk_response_model_map[endpoint_subpath] = request_type.get_success_status_response_pairs() for response_type in request_type.get_success_status_response_pairs().values(): + if len(response_type.model_fields) == 0: + print(f"Response type {response_type.__name__} has no fields") + continue + for field_name, field_info in response_type.model_fields.items(): if response_type.__name__ not in response_field_names_and_descriptions: response_field_names_and_descriptions[response_type.__name__] = [] @@ -133,11 +155,14 @@ def json_serializer(obj: object) -> object: with open("docs/request_field_names_and_descriptions.json", "w") as f: f.write(json.dumps(request_field_names_and_descriptions, indent=4, default=json_serializer)) + f.write("\n") with open("docs/response_field_names_and_descriptions.json", "w") as f: f.write(json.dumps(response_field_names_and_descriptions, indent=4, default=json_serializer)) + f.write("\n") +@pytest.mark.object_verify def test_all_ai_horde_model_defs_in_swagger_from_prod_swagger() -> None: swagger_doc: SwaggerDoc | None = None try: diff --git a/tests/ai_horde_worker/test_model_meta.py b/tests/ai_horde_worker/test_model_meta_api_calls.py similarity index 92% rename from tests/ai_horde_worker/test_model_meta.py rename to tests/ai_horde_worker/test_model_meta_api_calls.py index d3deee3..9c0fd81 100644 --- a/tests/ai_horde_worker/test_model_meta.py +++ b/tests/ai_horde_worker/test_model_meta_api_calls.py @@ -2,16 +2,16 @@ from horde_model_reference.model_reference_manager import ModelReferenceManager from horde_sdk.ai_horde_api.ai_horde_clients import AIHordeAPIManualClient -from horde_sdk.ai_horde_api.apimodels import StatsImageModelsRequest, StatsModelsResponse, StatsModelsTimeframe +from horde_sdk.ai_horde_api.apimodels import ImageModelStatsResponse, ImageStatsModelsRequest, StatsModelsTimeframe from horde_sdk.ai_horde_worker.model_meta import ImageModelLoadResolver from horde_sdk.generic_api.apimodels import RequestErrorResponse @pytest.fixture(scope="session") -def stats_response() -> StatsModelsResponse: +def stats_response() -> ImageModelStatsResponse: client = AIHordeAPIManualClient() - stats_response = client.submit_request(StatsImageModelsRequest(), StatsModelsResponse) + stats_response = client.submit_request(ImageStatsModelsRequest(), ImageModelStatsResponse) if isinstance(stats_response, RequestErrorResponse): raise Exception(f"Request error: {stats_response.message}. object_data: {stats_response.object_data}") @@ -32,7 +32,7 @@ def test_image_model_load_resolver_all(image_model_load_resolver: ImageModelLoad def test_image_model_load_resolver_top_n( image_model_load_resolver: ImageModelLoadResolver, - stats_response: StatsModelsResponse, + stats_response: ImageModelStatsResponse, ) -> None: resolved_model_names = image_model_load_resolver.resolve_top_n_model_names( 1, @@ -45,7 +45,7 @@ def test_image_model_load_resolver_top_n( def test_image_model_top_10( image_model_load_resolver: ImageModelLoadResolver, - stats_response: StatsModelsResponse, + stats_response: ImageModelStatsResponse, ) -> None: resolved_model_names = image_model_load_resolver.resolve_top_n_model_names( 10, @@ -58,7 +58,7 @@ def test_image_model_top_10( def test_image_model_load_resolver_bottom_n( image_model_load_resolver: ImageModelLoadResolver, - stats_response: StatsModelsResponse, + stats_response: ImageModelStatsResponse, ) -> None: resolved_model_names = image_model_load_resolver.resolve_bottom_n_model_names( 1, @@ -71,7 +71,7 @@ def test_image_model_load_resolver_bottom_n( def test_image_model_load_resolver_bottom_10( image_model_load_resolver: ImageModelLoadResolver, - stats_response: StatsModelsResponse, + stats_response: ImageModelStatsResponse, ) -> None: resolved_model_names = image_model_load_resolver.resolve_bottom_n_model_names( 10, diff --git a/tests/conftest.py b/tests/conftest.py index cea678b..d29c349 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -51,12 +51,17 @@ def simple_image_gen_n_requests(ai_horde_api_key: str) -> ImageGenerateAsyncRequ def pytest_collection_modifyitems(items): # type: ignore # noqa """Modifies test items to ensure test modules run in a given order.""" - MODULES_TO_RUN_FIRST = ["tests_generic", "test_utils", "test_dynamically_check_apimodels"] + MODULES_TO_RUN_FIRST = [ + "tests.tests_generic", + "tests.test_utils", + "tests.test_dynamically_check_apimodels", + "tests.test_verify_api_surface", + ] MODULES_TO_RUN_LAST = [ "tests.ai_horde_api.test_ai_horde_api_calls", - "test.ai_horde_api.test_ai_horde_alchemy_api_calls", - "test.ai_horde_api.test_ai_horde_generate_api_calls", + "tests.ai_horde_api.test_ai_horde_alchemy_api_calls", + "tests.ai_horde_api.test_ai_horde_generate_api_calls", ] # FIXME make dynamic module_mapping = {item: item.module.__name__ for item in items} diff --git a/tests/test_data/ai_horde_api/example_payloads/_v2_generate_async_post.json b/tests/test_data/ai_horde_api/example_payloads/_v2_generate_async_post.json index b58a283..4730732 100644 --- a/tests/test_data/ai_horde_api/example_payloads/_v2_generate_async_post.json +++ b/tests/test_data/ai_horde_api/example_payloads/_v2_generate_async_post.json @@ -1,7 +1,7 @@ { "prompt": "a", "params": { - "sampler_name": "DDIM", + "sampler_name": "k_dpm_fast", "cfg_scale": 7.5, "denoising_strength": 0.75, "seed": "The little seed that could", @@ -40,6 +40,13 @@ "additionalProp2": {}, "additionalProp3": {} }, + "extra_texts": [ + { + "text": "a", + "reference": "aaa" + } + ], + "workflow": "qr_code", "steps": 30, "n": 1 }, @@ -57,11 +64,18 @@ "source_image": "", "source_processing": "img2img", "source_mask": "", + "extra_source_images": [ + { + "image": "a", + "strength": 1.0 + } + ], "r2": true, "shared": false, "replacement_filter": true, "dry_run": false, "proxied_account": "", "disable_batching": false, + "allow_downgrade": false, "webhook": "https://haidra.net/00000000-0000-0000-0000-000000000000" } diff --git a/tests/test_data/ai_horde_api/example_payloads/_v2_generate_pop_post.json b/tests/test_data/ai_horde_api/example_payloads/_v2_generate_pop_post.json index 8899fa8..80f94de 100644 --- a/tests/test_data/ai_horde_api/example_payloads/_v2_generate_pop_post.json +++ b/tests/test_data/ai_horde_api/example_payloads/_v2_generate_pop_post.json @@ -20,5 +20,6 @@ "allow_unsafe_ipaddr": true, "allow_post_processing": true, "allow_controlnet": true, + "allow_sdxl_controlnet": true, "allow_lora": true } diff --git a/tests/test_data/ai_horde_api/example_payloads/_v2_generate_text_async_post.json b/tests/test_data/ai_horde_api/example_payloads/_v2_generate_text_async_post.json index 60469b2..7220284 100644 --- a/tests/test_data/ai_horde_api/example_payloads/_v2_generate_text_async_post.json +++ b/tests/test_data/ai_horde_api/example_payloads/_v2_generate_text_async_post.json @@ -42,6 +42,13 @@ ], "dry_run": false, "proxied_account": "", + "extra_source_images": [ + { + "image": "a", + "strength": 1.0 + } + ], "disable_batching": false, + "allow_downgrade": false, "webhook": "" } diff --git a/tests/test_data/ai_horde_api/example_payloads/_v2_status_modes_put.json b/tests/test_data/ai_horde_api/example_payloads/_v2_status_modes_put.json index 1016d45..a43138a 100644 --- a/tests/test_data/ai_horde_api/example_payloads/_v2_status_modes_put.json +++ b/tests/test_data/ai_horde_api/example_payloads/_v2_status_modes_put.json @@ -1,5 +1,5 @@ { - "maintenance": false, - "invite_only": false, - "raid": false + "maintenance_mode": false, + "invite_only_mode": false, + "raid_mode": false } diff --git a/tests/test_data/ai_horde_api/example_responses/_v2_find_user_get_200.json b/tests/test_data/ai_horde_api/example_responses/_v2_find_user_get_200.json index 23776a6..704497b 100644 --- a/tests/test_data/ai_horde_api/example_responses/_v2_find_user_get_200.json +++ b/tests/test_data/ai_horde_api/example_responses/_v2_find_user_get_200.json @@ -31,6 +31,7 @@ "vpn": false, "service": false, "education": false, + "customizer": false, "special": false, "suspicious": 0, "pseudonymous": false, diff --git a/tests/test_data/ai_horde_api/example_responses/_v2_generate_async_post_202.json b/tests/test_data/ai_horde_api/example_responses/_v2_generate_async_post_202.json index 0d53883..9f0a5df 100644 --- a/tests/test_data/ai_horde_api/example_responses/_v2_generate_async_post_202.json +++ b/tests/test_data/ai_horde_api/example_responses/_v2_generate_async_post_202.json @@ -5,7 +5,7 @@ "warnings": [ { "code": "NoAvailableWorker", - "message": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "message": "a" } ] } diff --git a/tests/test_data/ai_horde_api/example_responses/_v2_generate_pop_post_200.json b/tests/test_data/ai_horde_api/example_responses/_v2_generate_pop_post_200.json index 53a4d94..d6044ba 100644 --- a/tests/test_data/ai_horde_api/example_responses/_v2_generate_pop_post_200.json +++ b/tests/test_data/ai_horde_api/example_responses/_v2_generate_pop_post_200.json @@ -1,6 +1,6 @@ { "payload": { - "sampler_name": "k_dpmpp_sde", + "sampler_name": "k_dpmpp_2m", "cfg_scale": 7.5, "denoising_strength": 0.75, "seed": "The little seed that could", @@ -39,6 +39,13 @@ "additionalProp2": {}, "additionalProp3": {} }, + "extra_texts": [ + { + "text": "a", + "reference": "aaa" + } + ], + "workflow": "qr_code", "prompt": "", "ddim_steps": 30, "n_iter": 1, @@ -69,6 +76,12 @@ "source_image": "", "source_processing": "img2img", "source_mask": "", + "extra_source_images": [ + { + "image": "a", + "strength": 1.0 + } + ], "r2_upload": "", "r2_uploads": [ "" diff --git a/tests/test_data/ai_horde_api/example_responses/_v2_generate_text_async_post_200.json b/tests/test_data/ai_horde_api/example_responses/_v2_generate_text_async_post_200.json new file mode 100644 index 0000000..dd66f2f --- /dev/null +++ b/tests/test_data/ai_horde_api/example_responses/_v2_generate_text_async_post_200.json @@ -0,0 +1,3 @@ +{ + "kudos": 100 +} diff --git a/tests/test_data/ai_horde_api/example_responses/_v2_generate_text_async_post_202.json b/tests/test_data/ai_horde_api/example_responses/_v2_generate_text_async_post_202.json index 0d53883..9f0a5df 100644 --- a/tests/test_data/ai_horde_api/example_responses/_v2_generate_text_async_post_202.json +++ b/tests/test_data/ai_horde_api/example_responses/_v2_generate_text_async_post_202.json @@ -5,7 +5,7 @@ "warnings": [ { "code": "NoAvailableWorker", - "message": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "message": "a" } ] } diff --git a/tests/test_data/ai_horde_api/example_responses/_v2_generate_text_pop_post_200.json b/tests/test_data/ai_horde_api/example_responses/_v2_generate_text_pop_post_200.json index 05ceed7..f130a20 100644 --- a/tests/test_data/ai_horde_api/example_responses/_v2_generate_text_pop_post_200.json +++ b/tests/test_data/ai_horde_api/example_responses/_v2_generate_text_pop_post_200.json @@ -1,10 +1,45 @@ { "payload": { - "prompt": "", "n": 1, - "seed": "" + "frmtadsnsp": false, + "frmtrmblln": false, + "frmtrmspch": false, + "frmttriminc": false, + "max_context_length": 1024, + "max_length": 80, + "rep_pen": 1.0, + "rep_pen_range": 0.0, + "rep_pen_slope": 0.0, + "singleline": false, + "temperature": 0.0, + "tfs": 0.0, + "top_a": 0.0, + "top_k": 0.0, + "top_p": 0.001, + "typical": 0.0, + "sampler_order": [ + 0 + ], + "use_default_badwordsids": true, + "stop_sequence": [ + "" + ], + "min_p": 0.0, + "smoothing_factor": 0.0, + "dynatemp_range": 0.0, + "dynatemp_exponent": 1.0, + "prompt": "" }, "id": "", + "ids": [ + "00000000-0000-0000-0000-000000000000" + ], + "extra_source_images": [ + { + "image": "a", + "strength": 1.0 + } + ], "skipped": { "worker_id": 0.0, "performance": 0.0, @@ -13,6 +48,11 @@ "untrusted": 0.0, "models": 0, "bridge_version": 0, - "kudos": 0 - } + "kudos": 0, + "max_context_length": 0, + "max_length": 0, + "matching_softprompt": 0 + }, + "softprompt": "", + "model": "" } diff --git a/tests/test_data/ai_horde_api/example_responses/_v2_stats_text_totals_get_200.json b/tests/test_data/ai_horde_api/example_responses/_v2_stats_text_totals_get_200.json index f234038..446d752 100644 --- a/tests/test_data/ai_horde_api/example_responses/_v2_stats_text_totals_get_200.json +++ b/tests/test_data/ai_horde_api/example_responses/_v2_stats_text_totals_get_200.json @@ -1,22 +1,22 @@ { "minute": { - "images": 0, - "ps": 0 + "requests": 0, + "tokens": 0 }, "hour": { - "images": 0, - "ps": 0 + "requests": 0, + "tokens": 0 }, "day": { - "images": 0, - "ps": 0 + "requests": 0, + "tokens": 0 }, "month": { - "images": 0, - "ps": 0 + "requests": 0, + "tokens": 0 }, "total": { - "images": 0, - "ps": 0 + "requests": 0, + "tokens": 0 } } diff --git a/tests/test_data/ai_horde_api/example_responses/_v2_status_heartbeat_get_200.json b/tests/test_data/ai_horde_api/example_responses/_v2_status_heartbeat_get_200.json new file mode 100644 index 0000000..f646beb --- /dev/null +++ b/tests/test_data/ai_horde_api/example_responses/_v2_status_heartbeat_get_200.json @@ -0,0 +1,4 @@ +{ + "message": "OK", + "version": "4.34.1" +} diff --git a/tests/test_data/ai_horde_api/example_responses/_v2_status_models_model_name_get_200.json b/tests/test_data/ai_horde_api/example_responses/_v2_status_models_model_name_get_200.json index 5985064..79e65e6 100644 --- a/tests/test_data/ai_horde_api/example_responses/_v2_status_models_model_name_get_200.json +++ b/tests/test_data/ai_horde_api/example_responses/_v2_status_models_model_name_get_200.json @@ -1,9 +1,11 @@ -{ - "name": "", - "count": 0, - "performance": 0.0, - "queued": 0.0, - "jobs": 0.0, - "eta": 0, - "type": "image" -} +[ + { + "name": "", + "count": 0, + "performance": 0.0, + "queued": 0.0, + "jobs": 0.0, + "eta": 0, + "type": "image" + } +] diff --git a/tests/test_data/ai_horde_api/example_responses/_v2_users_get_200.json b/tests/test_data/ai_horde_api/example_responses/_v2_users_get_200.json index 23776a6..704497b 100644 --- a/tests/test_data/ai_horde_api/example_responses/_v2_users_get_200.json +++ b/tests/test_data/ai_horde_api/example_responses/_v2_users_get_200.json @@ -31,6 +31,7 @@ "vpn": false, "service": false, "education": false, + "customizer": false, "special": false, "suspicious": 0, "pseudonymous": false, diff --git a/tests/test_data/ai_horde_api/example_responses/_v2_users_user_id_get_200.json b/tests/test_data/ai_horde_api/example_responses/_v2_users_user_id_get_200.json index 23776a6..704497b 100644 --- a/tests/test_data/ai_horde_api/example_responses/_v2_users_user_id_get_200.json +++ b/tests/test_data/ai_horde_api/example_responses/_v2_users_user_id_get_200.json @@ -31,6 +31,7 @@ "vpn": false, "service": false, "education": false, + "customizer": false, "special": false, "suspicious": 0, "pseudonymous": false, diff --git a/tests/test_data/ai_horde_api/example_responses/_v2_workers_get_200.json b/tests/test_data/ai_horde_api/example_responses/_v2_workers_get_200.json index 11389c0..41eba74 100644 --- a/tests/test_data/ai_horde_api/example_responses/_v2_workers_get_200.json +++ b/tests/test_data/ai_horde_api/example_responses/_v2_workers_get_200.json @@ -41,6 +41,8 @@ "painting": false, "post-processing": false, "lora": false, + "controlnet": false, + "sdxl_controlnet": false, "max_length": 80, "max_context_length": 80, "tokens_generated": 0.0 diff --git a/tests/test_data/ai_horde_api/example_responses/_v2_workers_worker_id_get_200.json b/tests/test_data/ai_horde_api/example_responses/_v2_workers_worker_id_get_200.json index 7997ebf..26bdee0 100644 --- a/tests/test_data/ai_horde_api/example_responses/_v2_workers_worker_id_get_200.json +++ b/tests/test_data/ai_horde_api/example_responses/_v2_workers_worker_id_get_200.json @@ -40,6 +40,8 @@ "painting": false, "post-processing": false, "lora": false, + "controlnet": false, + "sdxl_controlnet": false, "max_length": 80, "max_context_length": 80, "tokens_generated": 0.0 diff --git a/tests/test_data/ai_horde_api/production_responses/_v2_workers_get_200_production.json b/tests/test_data/ai_horde_api/production_responses/_v2_workers_get_200_production.json index 6f04499..fe51488 100644 --- a/tests/test_data/ai_horde_api/production_responses/_v2_workers_get_200_production.json +++ b/tests/test_data/ai_horde_api/production_responses/_v2_workers_get_200_production.json @@ -1 +1 @@ -[{"type_":"image","name":".0xC - Dream. There could have been your ad here, but there isn't.","id_":"d5216a2a-63e5-4bd6-beba-3e77e8c2f96e","online":true,"requests_fulfilled":14840,"kudos_rewards":358506.0,"kudos_details":{"generated":321690.0,"uptime":38832},"performance":"0.3 megapixelsteps per second","threads":1,"uptime":252563,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":155,"models":["ICBINP - I Can't Believe It's Not Photography","iCoMix","AlbedoBase XL (SDXL)","Hentai Diffusion","Deliberate","BB95 Furry Mix","Anything Diffusion","stable_diffusion","SDXL 1.0","Dreamshaper"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker reGen:4.1.1:https://github.com/Haidra-Org/horde-worker-reGen","max_pixels":589824,"megapixelsteps_generated":220693,"img2img":true,"painting":true,"post_processing":true,"lora":true,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"Zeds box of delights","id_":"bf79a253-c3e6-45c1-b041-29ec13fead70","online":true,"requests_fulfilled":1172778,"kudos_rewards":17318488.0,"kudos_details":{"generated":15184018.0,"uptime":2307578},"performance":"0.5 megapixelsteps per second","threads":1,"uptime":17739963,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":1857,"models":["Anything Diffusion","Deliberate","stable_diffusion","ICBINP - I Can't Believe It's Not Photography","AlbedoBase XL (SDXL)"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker reGen:4.0.1:https://github.com/Haidra-Org/horde-worker-reGen","max_pixels":1310720,"megapixelsteps_generated":14868112,"img2img":true,"painting":false,"post_processing":true,"lora":true,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"Workaholic 3","id_":"84146abf-5c47-4a93-af23-79c8f7ed6fc6","online":true,"requests_fulfilled":140154,"kudos_rewards":2231519.0,"kudos_details":{"generated":1910016.0,"uptime":321668},"performance":"0.2 megapixelsteps per second","threads":1,"uptime":3653790,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":398,"models":["AlbedoBase XL (SDXL)"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker reGen:4.0.1:https://github.com/Haidra-Org/horde-worker-reGen","max_pixels":1048576,"megapixelsteps_generated":2056591,"img2img":true,"painting":false,"post_processing":false,"lora":false,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"LameDuckDream1","id_":"195976c9-c9eb-417a-88ef-767ed9b8f9ea","online":true,"requests_fulfilled":422838,"kudos_rewards":12818461.0,"kudos_details":{"generated":11608126.0,"uptime":1210882},"performance":"0.6 megapixelsteps per second","threads":1,"uptime":9068329,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":525,"models":["SDXL 1.0","AlbedoBase XL (SDXL)"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker reGen:4.0.1:https://github.com/Haidra-Org/horde-worker-reGen","max_pixels":1310720,"megapixelsteps_generated":8596740,"img2img":true,"painting":true,"post_processing":true,"lora":true,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"pawkygame 4090 dreamer","id_":"7d34e0cd-cf1e-4f13-9944-d2a17e356e39","online":true,"requests_fulfilled":64912,"kudos_rewards":2104953.0,"kudos_details":{"generated":1801025.0,"uptime":304886},"performance":"1.5 megapixelsteps per second","threads":2,"uptime":470147,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":222,"models":["CamelliaMix 2.5D","Colorful","Classic Animation Diffusion","Protogen Infinity","Dreamshaper","3DKX","GhostMix","RPG","Zeipher Female Model","DreamLikeSamKuvshinov","A-Zovya RPG Inpainting","ChromaV5","Liberty","Healy's Anime Blend","PFG","HASDX","SDXL 1.0","Inkpunk Diffusion","DnD Map Generator","Dark Sushi Mix","Furry Epoch","NeverEnding Dream","Anything Diffusion","DGSpitzer Art Diffusion","BB95 Furry Mix","Fustercluck","Unstable Ink Dream","Double Exposure Diffusion","ChilloutMix","Funko Diffusion","Real Dos Mix","GuoFeng","Abyss OrangeMix","Eimis Anime Diffusion","Sci-Fi Diffusion","Zack3D","stable_diffusion_inpainting","CharHelper","Microcritters","Edge Of Realism","VinteProtogenMix","BRA","Dark Victorian Diffusion","Tron Legacy Diffusion","Laolei New Berry Protogen Mix","Robo-Diffusion","SD-Silicon","Dungeons n Waifus","Henmix Real","Seek.art MEGA","Hentai Diffusion","Analog Madness","RCNZ Dumb Monkey","Counterfeit","Comic-Diffusion","iCoMix","Realism Engine","526Mix-Animated","stable_diffusion","PortraitPlus","Realistic Vision","Graphic-Art","Openniji","waifu_diffusion","Midjourney PaintArt","DucHaiten","Art Of Mtg","ProtoGen","MoistMix","Rev Animated","Uhmami","Dreamlike Diffusion","CyriousMix","Nitro Diffusion","GTA5 Artwork Diffusion","Western Animation Diffusion","Realisian","AIO Pixel Art","AbyssOrangeMix-AfterDark","Experience","Deliberate Inpainting","ExpMix Line","UMI Olympus","Galena Redux","BweshMix","RealBiter","Something","Hassaku","AnyLoRA","HRL","Mistoon Amethyst","Anything v5","Babes","Jim Eidomode","Mega Merge Diffusion","Elldreth's Lucid Mix","Poison","Anime Pencil Diffusion","Microworlds","BPModel","Moedel","DreamShaper Inpainting","MoonMix Fantasy","ICBINP XL","Dreamlike Photoreal","Illuminati Diffusion","MeinaMix","GorynichMix","Deliberate 3.0","DucHaiten Classic Anime","URPM","ACertainThing","Cheese Daddys Landscape Mix","Lawlas's yiff mix","Anything v3","Trinart Characters","PPP","Pretty 2.5D","stable_diffusion_2.1","ICBINP - I Can't Believe It's Not Photography","Analog Diffusion","Epic Diffusion Inpainting","JoMad Diffusion","Fantasy Card Diffusion","Ranma Diffusion","JWST Deep Space Diffusion","Woop-Woop Photo","Pastel Mix","Hassanblend","Ultraskin","Vector Art","Perfect World","Disco Elysium","Protogen Anime","Dungeons and Diffusion","Project Unreal Engine 5","App Icon Diffusion","AlbedoBase XL (SDXL)","GuFeng","Lyriel","ModernArt Diffusion","Neurogen","Fluffusion","majicMIX realistic","DnD Item","Pokemon3D","RCNZ Gorilla With A Brick","Anything Diffusion Inpainting","Papercut Diffusion","ToonYou","Anygen","FaeTastic","vectorartz","Samaritan 3d Cartoon","Deliberate","Aurora","Cetus-Mix","Disney Pixar Cartoon Type A","Ether Real Mix","Movie Diffusion","Grapefruit Hentai","Epic Diffusion","SweetBoys 2D","Dan Mumford Style","Yiffy","OpenJourney Diffusion","Realistic Vision Inpainting","Char","iCoMix Inpainting","Reliberate","Elysium Anime","Pulp Vector Art","Ghibli Diffusion","CyberRealistic","Kenshi"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker reGen:4.0.1:https://github.com/Haidra-Org/horde-worker-reGen","max_pixels":1638400,"megapixelsteps_generated":1684920,"img2img":true,"painting":true,"post_processing":true,"lora":true,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"ComicsMaker.ai-002","id_":"55510058-5a38-4228-b2ea-81e4477b8b40","online":true,"requests_fulfilled":575493,"kudos_rewards":9377329.0,"kudos_details":{"generated":8277342.0,"uptime":1101200},"performance":"1.0 megapixelsteps per second","threads":1,"uptime":9458755,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":1436,"models":["Counterfeit","iCoMix Inpainting","iCoMix","Epic Diffusion Inpainting","Anything Diffusion Inpainting","MeinaMix","Western Animation Diffusion","Epic Diffusion","Deliberate Inpainting","ToonYou","stable_diffusion_inpainting","Anything Diffusion","Deliberate","stable_diffusion"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":1048576,"megapixelsteps_generated":7937178,"img2img":true,"painting":true,"post_processing":true,"lora":false,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"THE AWESOME JOJOworker555","id_":"a49b47ec-ea80-4a88-9f04-5819c74f2777","online":true,"requests_fulfilled":13599,"kudos_rewards":212444.0,"kudos_details":{"generated":157660.0,"uptime":54824},"performance":"0.7 megapixelsteps per second","threads":1,"uptime":421202,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":113,"models":["BB95 Furry Mix"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker reGen:4.0.1-sidorok-colab.26-01-2024:https://github.com/Haidra-Org/horde-worker-reGen","max_pixels":1638400,"megapixelsteps_generated":220072,"img2img":true,"painting":true,"post_processing":false,"lora":true,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"Liane dreamer 4060 16G","id_":"094b4507-bede-4935-882e-a2aed6e51a23","online":true,"requests_fulfilled":204081,"kudos_rewards":6421717.0,"kudos_details":{"generated":6071073.0,"uptime":351004},"performance":"0.8 megapixelsteps per second","threads":1,"uptime":3800397,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":370,"models":["Deliberate","AlbedoBase XL (SDXL)","SDXL 1.0","ICBINP - I Can't Believe It's Not Photography"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker reGen:4.1.0:https://github.com/Haidra-Org/horde-worker-reGen","max_pixels":1310720,"megapixelsteps_generated":5021195,"img2img":true,"painting":true,"post_processing":true,"lora":false,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"CadfalDreamer","id_":"2f1cc997-7bdf-4d3a-ab44-a1581708db50","online":true,"requests_fulfilled":18313,"kudos_rewards":634444.0,"kudos_details":{"generated":596844.0,"uptime":37600},"performance":"0.9 megapixelsteps per second","threads":1,"uptime":232126,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":11,"models":["ICBINP - I Can't Believe It's Not Photography","iCoMix","AlbedoBase XL (SDXL)","Anything Diffusion","Deliberate","BB95 Furry Mix","Hentai Diffusion","stable_diffusion","SDXL 1.0","Dreamshaper"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker reGen:4.0.1:https://github.com/Haidra-Org/horde-worker-reGen","max_pixels":1638400,"megapixelsteps_generated":486316,"img2img":true,"painting":true,"post_processing":true,"lora":true,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"fennec3060","id_":"87032aa6-c9c3-414f-b98b-f5bc2d28859f","online":true,"requests_fulfilled":9089,"kudos_rewards":220148.0,"kudos_details":{"generated":194648.0,"uptime":25500},"performance":"0.6 megapixelsteps per second","threads":1,"uptime":158817,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":35,"models":["Hentai Diffusion","stable_diffusion","BB95 Furry Mix","Deliberate","iCoMix","ICBINP - I Can't Believe It's Not Photography","Dreamshaper","AlbedoBase XL (SDXL)","SDXL 1.0","Anything Diffusion"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker reGen:4.0.1:https://github.com/Haidra-Org/horde-worker-reGen","max_pixels":589824,"megapixelsteps_generated":138640,"img2img":true,"painting":true,"post_processing":true,"lora":true,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"Epi-AI","id_":"ddcb6d5b-3a09-402f-8eab-21fbbe8013b6","online":true,"requests_fulfilled":630444,"kudos_rewards":16312958.0,"kudos_details":{"generated":12512765.0,"uptime":3832820},"performance":"1.0 megapixelsteps per second","threads":2,"uptime":6057903,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":1256,"models":["Dreamlike Photoreal","Illuminati Diffusion","MeinaMix","GorynichMix","Deliberate 3.0","DucHaiten Classic Anime","URPM","ACertainThing","Cheese Daddys Landscape Mix","Lawlas's yiff mix","Anything v3","Trinart Characters","PPP","JoMad Diffusion","Epic Diffusion Inpainting","stable_diffusion_2.1","Pretty 2.5D","Analog Diffusion","Fantasy Card Diffusion","ICBINP - I Can't Believe It's Not Photography","Ranma Diffusion","JWST Deep Space Diffusion","Woop-Woop Photo","Pastel Mix","Perfect World","Ultraskin","Disco Elysium","Vector Art","Hassanblend","Protogen Anime","Dungeons and Diffusion","Project Unreal Engine 5","App Icon Diffusion","AlbedoBase XL (SDXL)","Lyriel","ModernArt Diffusion","GuFeng","Fluffusion","Neurogen","majicMIX realistic","DnD Item","Pokemon3D","RCNZ Gorilla With A Brick","Anything Diffusion Inpainting","Papercut Diffusion","ToonYou","Anygen","FaeTastic","Samaritan 3d Cartoon","vectorartz","Deliberate","Aurora","Cetus-Mix","Disney Pixar Cartoon Type A","Ether Real Mix","Movie Diffusion","Grapefruit Hentai","Epic Diffusion","SweetBoys 2D","Dan Mumford Style","Yiffy","OpenJourney Diffusion","Realistic Vision Inpainting","Char","Reliberate","iCoMix Inpainting","Elysium Anime","Pulp Vector Art","Ghibli Diffusion","CyberRealistic","Kenshi","CamelliaMix 2.5D","Colorful","Classic Animation Diffusion","Protogen Infinity","Dreamshaper","3DKX","GhostMix","RPG","Zeipher Female Model","DreamLikeSamKuvshinov","A-Zovya RPG Inpainting","ChromaV5","PFG","Liberty","Healy's Anime Blend","HASDX","SDXL 1.0","Inkpunk Diffusion","DnD Map Generator","Dark Sushi Mix","Furry Epoch","NeverEnding Dream","Anything Diffusion","DGSpitzer Art Diffusion","BB95 Furry Mix","Fustercluck","Unstable Ink Dream","Double Exposure Diffusion","ChilloutMix","Funko Diffusion","Real Dos Mix","GuoFeng","Abyss OrangeMix","Eimis Anime Diffusion","Sci-Fi Diffusion","Zack3D","stable_diffusion_inpainting","CharHelper","Microcritters","Edge Of Realism","VinteProtogenMix","Dark Victorian Diffusion","Laolei New Berry Protogen Mix","BRA","Tron Legacy Diffusion","Robo-Diffusion","SD-Silicon","Dungeons n Waifus","Henmix Real","Seek.art MEGA","Hentai Diffusion","Analog Madness","RCNZ Dumb Monkey","Counterfeit","Comic-Diffusion","iCoMix","Realism Engine","526Mix-Animated","stable_diffusion","PortraitPlus","Realistic Vision","Graphic-Art","Openniji","waifu_diffusion","Midjourney PaintArt","DucHaiten","Art Of Mtg","ProtoGen","MoistMix","Rev Animated","Uhmami","Dreamlike Diffusion","CyriousMix","Nitro Diffusion","GTA5 Artwork Diffusion","Western Animation Diffusion","Realisian","Experience","AIO Pixel Art","AbyssOrangeMix-AfterDark","Deliberate Inpainting","ExpMix Line","UMI Olympus","Galena Redux","BweshMix","RealBiter","AnyLoRA","Hassaku","Something","HRL","Mistoon Amethyst","Anything v5","Babes","Jim Eidomode","Mega Merge Diffusion","Elldreth's Lucid Mix","Poison","Anime Pencil Diffusion","BPModel","Microworlds","Moedel","DreamShaper Inpainting","MoonMix Fantasy","ICBINP XL"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker reGen:4.1.0:https://github.com/Haidra-Org/horde-worker-reGen","max_pixels":2097152,"megapixelsteps_generated":12627520,"img2img":true,"painting":true,"post_processing":true,"lora":true,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"Dreams of UBIK","id_":"c44cd320-eada-4abb-8316-5df18fe1fec0","online":true,"requests_fulfilled":3069,"kudos_rewards":48388.0,"kudos_details":{"generated":40736.0,"uptime":7652},"performance":"0.2 megapixelsteps per second","threads":1,"uptime":78658,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":false,"flagged":false,"suspicious":null,"uncompleted_jobs":16,"models":["ICBINP - I Can't Believe It's Not Photography","stable_diffusion_inpainting","AlbedoBase XL (SDXL)","Anything Diffusion","Deliberate","stable_diffusion"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker reGen:4.0.1:https://github.com/Haidra-Org/horde-worker-reGen","max_pixels":589824,"megapixelsteps_generated":37689,"img2img":true,"painting":true,"post_processing":true,"lora":false,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"Jrud Dreamer","id_":"4afbb3fa-4307-4b06-b415-eec29358c133","online":true,"requests_fulfilled":324,"kudos_rewards":7178.0,"kudos_details":{"generated":2642.0,"uptime":4536},"performance":"0.1 megapixelsteps per second","threads":1,"uptime":35783,"maintenance_mode":true,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":false,"flagged":false,"suspicious":null,"uncompleted_jobs":119,"models":["Deliberate","ICBINP - I Can't Believe It's Not Photography"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker reGen:4.0.1:https://github.com/Haidra-Org/horde-worker-reGen","max_pixels":262144,"megapixelsteps_generated":2489,"img2img":true,"painting":true,"post_processing":true,"lora":true,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"CounterFlow 2","id_":"cc8a6627-a1f4-4bf8-bb73-fe77b351a0a0","online":true,"requests_fulfilled":58618,"kudos_rewards":1829961.0,"kudos_details":{"generated":1606865.0,"uptime":223912},"performance":"0.5 megapixelsteps per second","threads":1,"uptime":1715217,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":4841,"models":["ICBINP - I Can't Believe It's Not Photography","iCoMix","AlbedoBase XL (SDXL)","Hentai Diffusion","Deliberate","BB95 Furry Mix","Anything Diffusion","stable_diffusion","SDXL 1.0","Dreamshaper"],"forms":null,"team":{"name":"Mutual Aid","id_":"7a5afb3e-6d80-41f0-98bd-9be732d45944"},"contact":null,"bridge_agent":"AI Horde Worker reGen:4.0.1:https://github.com/Haidra-Org/horde-worker-reGen","max_pixels":1048576,"megapixelsteps_generated":1305856,"img2img":true,"painting":true,"post_processing":true,"lora":false,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"CrazyPaintingMachineK","id_":"387d7f1a-01b9-472e-a23c-f626fe02af93","online":true,"requests_fulfilled":103460,"kudos_rewards":1172565.0,"kudos_details":{"generated":910267.0,"uptime":262400},"performance":"0.8 megapixelsteps per second","threads":1,"uptime":1967646,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":93,"models":["Deliberate"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker reGen:4.0.1:https://github.com/Haidra-Org/horde-worker-reGen","max_pixels":819200,"megapixelsteps_generated":1340185,"img2img":true,"painting":false,"post_processing":false,"lora":true,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"NothingBlack","id_":"e2367608-725e-4e5c-9864-aac3a632b06a","online":true,"requests_fulfilled":153550,"kudos_rewards":1759999.0,"kudos_details":{"generated":1316291.0,"uptime":443672},"performance":"2.6 megapixelsteps per second","threads":1,"uptime":4316799,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":84,"models":["PPP","HRL","RealBiter","URPM","PFG","ChilloutMix"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"SD-WebUI Stable Horde Worker Bridge:4:https://github.com/sdwebui-w-horde/sd-webui-stable-horde-worker","max_pixels":4194304,"megapixelsteps_generated":2465891,"img2img":false,"painting":true,"post_processing":true,"lora":false,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"promptus","id_":"78840488-065f-4d02-9ebb-00a70f860646","online":true,"requests_fulfilled":10404,"kudos_rewards":123255.0,"kudos_details":{"generated":94843.0,"uptime":28412},"performance":"0.9 megapixelsteps per second","threads":1,"uptime":330329,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":75,"models":["ICBINP - I Can't Believe It's Not Photography"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":262144,"megapixelsteps_generated":86567,"img2img":true,"painting":true,"post_processing":true,"lora":false,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"x9d6n1h4d3v5","id_":"f3db42e5-1799-4463-974e-07075bd188bd","online":true,"requests_fulfilled":51218,"kudos_rewards":634025.0,"kudos_details":{"generated":436627.0,"uptime":197492},"performance":"0.6 megapixelsteps per second","threads":1,"uptime":2103346,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":14,"models":["stable_diffusion","Anything Diffusion","ICBINP - I Can't Believe It's Not Photography"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":262144,"megapixelsteps_generated":406316,"img2img":true,"painting":true,"post_processing":true,"lora":false,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"Dream On","id_":"0b1adc59-8f08-4dfc-847d-96de31368d74","online":true,"requests_fulfilled":22891,"kudos_rewards":328789.0,"kudos_details":{"generated":194983.0,"uptime":133806},"performance":"0.3 megapixelsteps per second","threads":1,"uptime":933284,"maintenance_mode":false,"paused":null,"info":null,"nsfw":false,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":89,"models":["Edge Of Realism","Deliberate","A to Zovya RPG","Anything Diffusion","RPG","CyberRealistic","Dreamshaper","ICBINP - I Can't Believe It's Not Photography","FaeTastic"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":327680,"megapixelsteps_generated":194683,"img2img":true,"painting":true,"post_processing":true,"lora":true,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"The Portal of Infinite Realities","id_":"dc0704ab-5b42-4c65-8471-561be16ad696","online":true,"requests_fulfilled":3283536,"kudos_rewards":77454909.0,"kudos_details":{"generated":73533472.0,"uptime":4136066},"performance":"2.2 megapixelsteps per second","threads":2,"uptime":28552179,"maintenance_mode":false,"paused":null,"info":"Somewhere far beyond...","nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":9835,"models":["ICBINP XL","Hentai Diffusion","SDXL 1.0","Deliberate 3.0","Abyss OrangeMix","Fustercluck","Anything Diffusion","AlbedoBase XL (SDXL)","RCNZ Gorilla With A Brick","Deliberate","Inkpunk Diffusion","ICBINP - I Can't Believe It's Not Photography","Dreamshaper","NeverEnding Dream","stable_diffusion","BB95 Furry Mix"],"forms":null,"team":{"name":"Mutual Aid","id_":"7a5afb3e-6d80-41f0-98bd-9be732d45944"},"contact":null,"bridge_agent":"AI Horde Worker reGen:4.1.1:https://github.com/Haidra-Org/horde-worker-reGen","max_pixels":3014656,"megapixelsteps_generated":70596009,"img2img":true,"painting":false,"post_processing":true,"lora":true,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"Fox's Regen Worker - 3090 - 2","id_":"2f11df6b-6a4d-486a-9e01-dada2281270e","online":true,"requests_fulfilled":11444,"kudos_rewards":404615.0,"kudos_details":{"generated":317643.0,"uptime":86972},"performance":"0.8 megapixelsteps per second","threads":2,"uptime":126251,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":39,"models":["CamelliaMix 2.5D","Colorful","Classic Animation Diffusion","Protogen Infinity","Dreamshaper","3DKX","GhostMix","RPG","Zeipher Female Model","DreamLikeSamKuvshinov","A-Zovya RPG Inpainting","ChromaV5","PFG","Liberty","Healy's Anime Blend","HASDX","SDXL 1.0","Inkpunk Diffusion","DnD Map Generator","Dark Sushi Mix","Furry Epoch","NeverEnding Dream","Anything Diffusion","DGSpitzer Art Diffusion","BB95 Furry Mix","Fustercluck","Unstable Ink Dream","Double Exposure Diffusion","ChilloutMix","Funko Diffusion","Real Dos Mix","GuoFeng","Abyss OrangeMix","Eimis Anime Diffusion","Sci-Fi Diffusion","Zack3D","stable_diffusion_inpainting","CharHelper","Microcritters","Edge Of Realism","VinteProtogenMix","Laolei New Berry Protogen Mix","Dark Victorian Diffusion","Tron Legacy Diffusion","BRA","Robo-Diffusion","SD-Silicon","Dungeons n Waifus","Henmix Real","Seek.art MEGA","Hentai Diffusion","Analog Madness","RCNZ Dumb Monkey","Counterfeit","Comic-Diffusion","iCoMix","Realism Engine","526Mix-Animated","PortraitPlus","stable_diffusion","Realistic Vision","Graphic-Art","Openniji","waifu_diffusion","Midjourney PaintArt","Art Of Mtg","DucHaiten","ProtoGen","MoistMix","Rev Animated","Uhmami","Dreamlike Diffusion","CyriousMix","Nitro Diffusion","GTA5 Artwork Diffusion","Western Animation Diffusion","Realisian","AIO Pixel Art","AbyssOrangeMix-AfterDark","Experience","Deliberate Inpainting","ExpMix Line","UMI Olympus","Galena Redux","BweshMix","RealBiter","AnyLoRA","Something","Hassaku","HRL","Mistoon Amethyst","Anything v5","Babes","Jim Eidomode","Mega Merge Diffusion","Elldreth's Lucid Mix","Poison","Anime Pencil Diffusion","BPModel","Microworlds","Moedel","DreamShaper Inpainting","MoonMix Fantasy","ICBINP XL","Dreamlike Photoreal","Illuminati Diffusion","MeinaMix","GorynichMix","Deliberate 3.0","DucHaiten Classic Anime","URPM","ACertainThing","Cheese Daddys Landscape Mix","Lawlas's yiff mix","Anything v3","Trinart Characters","PPP","ICBINP - I Can't Believe It's Not Photography","Analog Diffusion","stable_diffusion_2.1","Fantasy Card Diffusion","Epic Diffusion Inpainting","Pretty 2.5D","JoMad Diffusion","Ranma Diffusion","JWST Deep Space Diffusion","Woop-Woop Photo","Pastel Mix","Perfect World","Ultraskin","Disco Elysium","Vector Art","Hassanblend","Protogen Anime","Dungeons and Diffusion","Project Unreal Engine 5","App Icon Diffusion","AlbedoBase XL (SDXL)","GuFeng","ModernArt Diffusion","Lyriel","Fluffusion","Neurogen","majicMIX realistic","DnD Item","Pokemon3D","RCNZ Gorilla With A Brick","Anything Diffusion Inpainting","Papercut Diffusion","ToonYou","Anygen","FaeTastic","vectorartz","Samaritan 3d Cartoon","Aurora","Deliberate","Cetus-Mix","Disney Pixar Cartoon Type A","Ether Real Mix","Movie Diffusion","Grapefruit Hentai","Epic Diffusion","Dan Mumford Style","SweetBoys 2D","Yiffy","OpenJourney Diffusion","Realistic Vision Inpainting","Char","iCoMix Inpainting","Reliberate","Elysium Anime","Pulp Vector Art","Ghibli Diffusion","CyberRealistic","Kenshi"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker reGen:4.1.1:https://github.com/Haidra-Org/horde-worker-reGen","max_pixels":2097152,"megapixelsteps_generated":294876,"img2img":true,"painting":true,"post_processing":true,"lora":true,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"RisadaPHDSteam_Dreamer","id_":"ee014610-072a-49b7-a00a-28d21bc82986","online":true,"requests_fulfilled":54290,"kudos_rewards":615908.0,"kudos_details":{"generated":498969.0,"uptime":117020},"performance":"0.8 megapixelsteps per second","threads":1,"uptime":1325575,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":62,"models":["ICBINP - I Can't Believe It's Not Photography"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":262144,"megapixelsteps_generated":457982,"img2img":true,"painting":true,"post_processing":true,"lora":false,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"zaibas42","id_":"72f23bee-7c9b-4338-86bc-e4098546989f","online":true,"requests_fulfilled":170649,"kudos_rewards":2409677.0,"kudos_details":{"generated":1858422.0,"uptime":551652},"performance":"0.4 megapixelsteps per second","threads":1,"uptime":6042928,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":2336,"models":["Abyss OrangeMix","ICBINP - I Can't Believe It's Not Photography"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":262144,"megapixelsteps_generated":1503133,"img2img":true,"painting":true,"post_processing":true,"lora":false,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"Workaholic","id_":"6cd62afc-f4ee-4ca5-8ce1-0c511c9ed47d","online":true,"requests_fulfilled":62948,"kudos_rewards":972791.0,"kudos_details":{"generated":808120.0,"uptime":164902},"performance":"0.4 megapixelsteps per second","threads":1,"uptime":1953839,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":298,"models":["Deliberate","ICBINP - I Can't Believe It's Not Photography"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":1310720,"megapixelsteps_generated":1022497,"img2img":true,"painting":false,"post_processing":false,"lora":false,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"Cyriak_Kleermaker","id_":"c558eb20-6103-4eb4-9ba2-77824605414a","online":true,"requests_fulfilled":1995,"kudos_rewards":21250.0,"kudos_details":{"generated":17038.0,"uptime":4212},"performance":"0.8 megapixelsteps per second","threads":1,"uptime":47010,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":false,"flagged":false,"suspicious":null,"uncompleted_jobs":0,"models":["Deliberate","ICBINP - I Can't Believe It's Not Photography"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker reGen:4.0.1:https://github.com/Haidra-Org/horde-worker-reGen","max_pixels":262144,"megapixelsteps_generated":16233,"img2img":true,"painting":true,"post_processing":true,"lora":false,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"Imaginary Otter","id_":"e3758e92-495b-4a03-b390-6037f3cc67d2","online":true,"requests_fulfilled":4520,"kudos_rewards":59307.0,"kudos_details":{"generated":52328.0,"uptime":7062},"performance":"0.5 megapixelsteps per second","threads":1,"uptime":64620,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":false,"flagged":false,"suspicious":null,"uncompleted_jobs":3,"models":["ICBINP - I Can't Believe It's Not Photography","Furry Epoch","Yiffy","Zack3D","Deliberate","BB95 Furry Mix","Fluffusion","Lawlas's yiff mix"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker reGen:4.0.1:https://github.com/Haidra-Org/horde-worker-reGen","max_pixels":262144,"megapixelsteps_generated":40351,"img2img":true,"painting":true,"post_processing":true,"lora":false,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"Jason_76952 dreamer","id_":"f1e91798-d2d7-47ef-abbc-e5af18e0de82","online":true,"requests_fulfilled":8573,"kudos_rewards":112311.0,"kudos_details":{"generated":93951.0,"uptime":18378},"performance":"1.1 megapixelsteps per second","threads":1,"uptime":141228,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":false,"flagged":false,"suspicious":null,"uncompleted_jobs":82,"models":["stable_diffusion","Analog Madness","ICBINP - I Can't Believe It's Not Photography","3DKX"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":589824,"megapixelsteps_generated":99578,"img2img":true,"painting":true,"post_processing":false,"lora":true,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"Photodude_2000#2","id_":"b8712179-64b4-4226-a4c3-bc7e850931a4","online":true,"requests_fulfilled":643796,"kudos_rewards":9016312.0,"kudos_details":{"generated":7634965.0,"uptime":1381876},"performance":"0.8 megapixelsteps per second","threads":1,"uptime":9332426,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":1537,"models":["ICBINP - I Can't Believe It's Not Photography","AlbedoBase XL (SDXL)","Anything Diffusion","Deliberate","stable_diffusion"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker reGen:4.0.1:https://github.com/Haidra-Org/horde-worker-reGen","max_pixels":262144,"megapixelsteps_generated":5916015,"img2img":true,"painting":true,"post_processing":true,"lora":true,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"This_Worker_Is_Not_Your_Worker","id_":"0e747414-e819-4b83-9fbc-c91ec745312e","online":true,"requests_fulfilled":79469,"kudos_rewards":944131.0,"kudos_details":{"generated":825707.0,"uptime":118596},"performance":"1.7 megapixelsteps per second","threads":1,"uptime":1316811,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":21,"models":["Anything Diffusion","Deliberate","ICBINP - I Can't Believe It's Not Photography"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker reGen:4.0.1:https://github.com/Haidra-Org/horde-worker-reGen","max_pixels":262144,"megapixelsteps_generated":688828,"img2img":true,"painting":true,"post_processing":true,"lora":false,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"pawkygame 4090_2 Dreamer","id_":"51695520-3b9a-42d4-9463-d5f5c48947b2","online":true,"requests_fulfilled":31338,"kudos_rewards":1073501.0,"kudos_details":{"generated":907588.0,"uptime":166790},"performance":"1.2 megapixelsteps per second","threads":1,"uptime":241474,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":140,"models":["CamelliaMix 2.5D","Colorful","Classic Animation Diffusion","Protogen Infinity","Dreamshaper","3DKX","GhostMix","RPG","Zeipher Female Model","DreamLikeSamKuvshinov","A-Zovya RPG Inpainting","ChromaV5","PFG","Healy's Anime Blend","Liberty","HASDX","SDXL 1.0","Inkpunk Diffusion","DnD Map Generator","Dark Sushi Mix","Furry Epoch","NeverEnding Dream","Anything Diffusion","DGSpitzer Art Diffusion","BB95 Furry Mix","Fustercluck","Unstable Ink Dream","Double Exposure Diffusion","ChilloutMix","Funko Diffusion","Real Dos Mix","GuoFeng","Abyss OrangeMix","Eimis Anime Diffusion","Sci-Fi Diffusion","Zack3D","stable_diffusion_inpainting","CharHelper","Microcritters","Edge Of Realism","VinteProtogenMix","Laolei New Berry Protogen Mix","BRA","Tron Legacy Diffusion","Dark Victorian Diffusion","Robo-Diffusion","SD-Silicon","Dungeons n Waifus","Henmix Real","Seek.art MEGA","Hentai Diffusion","Analog Madness","RCNZ Dumb Monkey","Counterfeit","Comic-Diffusion","iCoMix","Realism Engine","526Mix-Animated","stable_diffusion","PortraitPlus","Realistic Vision","Graphic-Art","Openniji","waifu_diffusion","Midjourney PaintArt","Art Of Mtg","DucHaiten","ProtoGen","MoistMix","Rev Animated","Uhmami","Dreamlike Diffusion","CyriousMix","Nitro Diffusion","GTA5 Artwork Diffusion","Western Animation Diffusion","Realisian","Experience","AbyssOrangeMix-AfterDark","AIO Pixel Art","Deliberate Inpainting","ExpMix Line","UMI Olympus","Galena Redux","BweshMix","RealBiter","Something","AnyLoRA","Hassaku","HRL","Mistoon Amethyst","Anything v5","Babes","Jim Eidomode","Mega Merge Diffusion","Elldreth's Lucid Mix","Poison","Anime Pencil Diffusion","BPModel","Microworlds","Moedel","DreamShaper Inpainting","MoonMix Fantasy","ICBINP XL","Dreamlike Photoreal","Illuminati Diffusion","MeinaMix","GorynichMix","DucHaiten Classic Anime","Deliberate 3.0","URPM","ACertainThing","Cheese Daddys Landscape Mix","Lawlas's yiff mix","Trinart Characters","Anything v3","PPP","JoMad Diffusion","stable_diffusion_2.1","Analog Diffusion","Fantasy Card Diffusion","ICBINP - I Can't Believe It's Not Photography","Pretty 2.5D","Epic Diffusion Inpainting","Ranma Diffusion","JWST Deep Space Diffusion","Woop-Woop Photo","Pastel Mix","Hassanblend","Ultraskin","Vector Art","Disco Elysium","Perfect World","Protogen Anime","Dungeons and Diffusion","Project Unreal Engine 5","App Icon Diffusion","AlbedoBase XL (SDXL)","GuFeng","Lyriel","ModernArt Diffusion","Fluffusion","Neurogen","majicMIX realistic","DnD Item","Pokemon3D","RCNZ Gorilla With A Brick","Anything Diffusion Inpainting","Papercut Diffusion","ToonYou","Anygen","vectorartz","Samaritan 3d Cartoon","FaeTastic","Deliberate","Aurora","Cetus-Mix","Disney Pixar Cartoon Type A","Ether Real Mix","Movie Diffusion","Grapefruit Hentai","Dan Mumford Style","SweetBoys 2D","Epic Diffusion","Yiffy","OpenJourney Diffusion","Realistic Vision Inpainting","Char","Reliberate","iCoMix Inpainting","Elysium Anime","Pulp Vector Art","Ghibli Diffusion","CyberRealistic","Kenshi"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker reGen:4.0.1:https://github.com/Haidra-Org/horde-worker-reGen","max_pixels":1638400,"megapixelsteps_generated":811381,"img2img":true,"painting":true,"post_processing":true,"lora":true,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"plas","id_":"3298f084-8a93-499c-b412-31eab612258a","online":true,"requests_fulfilled":48053,"kudos_rewards":677030.0,"kudos_details":{"generated":611555.0,"uptime":65502},"performance":"0.9 megapixelsteps per second","threads":1,"uptime":737264,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":12,"models":["Deliberate","ICBINP - I Can't Believe It's Not Photography"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker reGen:4.0.1:https://github.com/Haidra-Org/horde-worker-reGen","max_pixels":589824,"megapixelsteps_generated":585523,"img2img":true,"painting":true,"post_processing":true,"lora":false,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"DreamDiffusion","id_":"372be5bf-2c1c-421f-a58d-b7d1692214f1","online":true,"requests_fulfilled":22486,"kudos_rewards":426353.0,"kudos_details":{"generated":355631.0,"uptime":70804},"performance":"0.7 megapixelsteps per second","threads":1,"uptime":535665,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":73,"models":["ICBINP - I Can't Believe It's Not Photography"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker reGen:4.1.0:https://github.com/Haidra-Org/horde-worker-reGen","max_pixels":1310720,"megapixelsteps_generated":426868,"img2img":false,"painting":false,"post_processing":false,"lora":true,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"Liane dreamer 1060 6G","id_":"9683cab3-807c-4cba-a292-ed1aefdf82f0","online":true,"requests_fulfilled":108269,"kudos_rewards":1390874.0,"kudos_details":{"generated":1054427.0,"uptime":336680},"performance":"0.2 megapixelsteps per second","threads":1,"uptime":3857308,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":264,"models":["Anything Diffusion","ICBINP - I Can't Believe It's Not Photography"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker reGen:4.1.0:https://github.com/Haidra-Org/horde-worker-reGen","max_pixels":262144,"megapixelsteps_generated":942254,"img2img":true,"painting":true,"post_processing":true,"lora":false,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"Photodude_2000","id_":"db6b8f00-c97d-4e89-a727-a1947d017480","online":true,"requests_fulfilled":614369,"kudos_rewards":9175060.0,"kudos_details":{"generated":8120483.0,"uptime":1064358},"performance":"0.8 megapixelsteps per second","threads":1,"uptime":7358438,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":5440,"models":["ICBINP - I Can't Believe It's Not Photography","Deliberate","Anything Diffusion","AlbedoBase XL (SDXL)","stable_diffusion"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker reGen:4.0.1:https://github.com/Haidra-Org/horde-worker-reGen","max_pixels":589824,"megapixelsteps_generated":5951899,"img2img":true,"painting":true,"post_processing":true,"lora":true,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"Menavex","id_":"98771acd-498c-4319-8d2f-8621599761c0","online":true,"requests_fulfilled":22291,"kudos_rewards":545670.0,"kudos_details":{"generated":532436.0,"uptime":13288},"performance":"1.0 megapixelsteps per second","threads":1,"uptime":101962,"maintenance_mode":false,"paused":null,"info":null,"nsfw":false,"owner":null,"ipaddr":null,"trusted":false,"flagged":false,"suspicious":null,"uncompleted_jobs":329,"models":["Realistic Vision Inpainting","stable_diffusion","Analog Madness","Dungeons and Diffusion","Comic-Diffusion","MeinaMix","DucHaiten","RCNZ Gorilla With A Brick","Anygen","Protogen Anime","ExpMix Line","Realism Engine","iCoMix","Nitro Diffusion","Project Unreal Engine 5","Fustercluck","3DKX","Elldreth's Lucid Mix","AlbedoBase XL (SDXL)","Elysium Anime","RealBiter","Babes","Pretty 2.5D","Woop-Woop Photo","HASDX","Analog Diffusion","ProtoGen","NeverEnding Dream","OpenJourney Diffusion","ACertainThing","Vector Art","FaeTastic","DreamLikeSamKuvshinov","Midjourney PaintArt","Hassaku","Microworlds","Art Of Mtg","PortraitPlus","Mistoon Amethyst","Neurogen","Hentai Diffusion","Illuminati Diffusion","majicMIX realistic","AIO Pixel Art","DnD Map Generator","Mega Merge Diffusion","BB95 Furry Mix","Dungeons n Waifus","Aurora","Counterfeit","Dan Mumford Style","Deliberate Inpainting","MoistMix","Jim Eidomode","ModernArt Diffusion","Char","Anime Pencil Diffusion","SweetBoys 2D","Lawlas's yiff mix","Papercut Diffusion","Anything v5","Inkpunk Diffusion","ICBINP XL","Dreamlike Photoreal","Henmix Real","URPM","Epic Diffusion","Realistic Vision","Microcritters","Epic Diffusion Inpainting","MoonMix Fantasy","Deliberate","Fluffusion","Perfect World","Anything Diffusion","BweshMix","DGSpitzer Art Diffusion","Tron Legacy Diffusion","Dark Victorian Diffusion","Zack3D","Protogen Infinity","Dreamlike Diffusion","CamelliaMix 2.5D","Ultraskin","Anything Diffusion Inpainting","Fantasy Card Diffusion","Galena Redux","ChilloutMix","Real Dos Mix","Movie Diffusion","JWST Deep Space Diffusion","Ranma Diffusion","App Icon Diffusion","Pastel Mix","GuFeng","Ether Real Mix","Grapefruit Hentai","Deliberate 3.0","UMI Olympus","BPModel","Poison","Colorful","DnD Item","BRA","vectorartz","Rev Animated","SDXL 1.0","stable_diffusion_inpainting","Yiffy","Anything v3","Liberty","DucHaiten Classic Anime","Double Exposure Diffusion","Disco Elysium","AnyLoRA","Seek.art MEGA","PFG","JoMad Diffusion","Furry Epoch","526Mix-Animated","Dreamshaper","Something","stable_diffusion_2.1","Dark Sushi Mix","Pulp Vector Art","DreamShaper Inpainting","Robo-Diffusion","ChromaV5","A-Zovya RPG Inpainting","Kenshi","Hassanblend","Laolei New Berry Protogen Mix","Samaritan 3d Cartoon","Healy's Anime Blend","CharHelper","waifu_diffusion","Realisian","CyriousMix","RPG","Funko Diffusion","ICBINP - I Can't Believe It's Not Photography","Unstable Ink Dream","Reliberate","Uhmami","GTA5 Artwork Diffusion","VinteProtogenMix","Abyss OrangeMix","RCNZ Dumb Monkey","HRL","Experience","Zeipher Female Model","GorynichMix","GuoFeng","Lyriel","GhostMix","Graphic-Art","AbyssOrangeMix-AfterDark","ToonYou","Moedel","Western Animation Diffusion","Trinart Characters","iCoMix Inpainting","CyberRealistic","Eimis Anime Diffusion","SD-Silicon","Ghibli Diffusion","Cetus-Mix","PPP","Pokemon3D","Classic Animation Diffusion","Openniji","Edge Of Realism","Disney Pixar Cartoon Type A","Cheese Daddys Landscape Mix","Sci-Fi Diffusion"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker reGen:4.0.1:https://github.com/Haidra-Org/horde-worker-reGen","max_pixels":3014656,"megapixelsteps_generated":385302,"img2img":true,"painting":true,"post_processing":true,"lora":true,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"MerpleHorde","id_":"3a495dcf-45a2-4114-a25d-85ee45cd26ee","online":true,"requests_fulfilled":809845,"kudos_rewards":9244024.0,"kudos_details":{"generated":8654213.0,"uptime":602098},"performance":"0.7 megapixelsteps per second","threads":2,"uptime":6491702,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":2988,"models":["Deliberate","ICBINP - I Can't Believe It's Not Photography"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker reGen:4.0.1:https://github.com/Haidra-Org/horde-worker-reGen","max_pixels":1048576,"megapixelsteps_generated":8076762,"img2img":true,"painting":true,"post_processing":true,"lora":false,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"3060Ti Dreamer","id_":"1cdf65fe-42b1-4c86-9c3f-eaa86e4cfd04","online":true,"requests_fulfilled":9240,"kudos_rewards":267386.0,"kudos_details":{"generated":252189.0,"uptime":15220},"performance":"0.7 megapixelsteps per second","threads":1,"uptime":165632,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":2,"models":["stable_diffusion","Deliberate","ICBINP - I Can't Believe It's Not Photography"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker reGen:4.0.1:https://github.com/Haidra-Org/horde-worker-reGen","max_pixels":1048576,"megapixelsteps_generated":220143,"img2img":true,"painting":true,"post_processing":true,"lora":false,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"Healthy Fibers","id_":"253f02f8-1f4a-43e9-9495-25bf269de6f1","online":true,"requests_fulfilled":535863,"kudos_rewards":7532301.0,"kudos_details":{"generated":6217484.0,"uptime":1399286},"performance":"0.9 megapixelsteps per second","threads":1,"uptime":15762364,"maintenance_mode":false,"paused":null,"info":"Hi guys!","nsfw":false,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":274,"models":["Realistic Vision"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":393216,"megapixelsteps_generated":5071398,"img2img":false,"painting":false,"post_processing":false,"lora":false,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"Imaginary Ocelot","id_":"2b334ab9-2c3b-4b4e-b2b4-c893500e9454","online":true,"requests_fulfilled":3095,"kudos_rewards":40610.0,"kudos_details":{"generated":34546.0,"uptime":6072},"performance":"0.5 megapixelsteps per second","threads":1,"uptime":55886,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":false,"flagged":false,"suspicious":null,"uncompleted_jobs":0,"models":["Yiffy","BB95 Furry Mix","Furry Epoch","Lawlas's yiff mix","stable_diffusion_2.1","Zack3D","stable_diffusion","Fluffusion"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker reGen:4.0.1:https://github.com/Haidra-Org/horde-worker-reGen","max_pixels":262144,"megapixelsteps_generated":26722,"img2img":true,"painting":true,"post_processing":true,"lora":false,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"image","name":"robertcs-worker-2","id_":"76db1fca-f6fd-4829-bda7-f79db26efe23","online":true,"requests_fulfilled":28194,"kudos_rewards":616442.0,"kudos_details":{"generated":559462.0,"uptime":57014},"performance":"0.5 megapixelsteps per second","threads":2,"uptime":426587,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":87,"models":["Deliberate"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker reGen:2:https://github.com/Haidra-Org/horde-worker-reGen/#20231009_3-kaggle","max_pixels":1638400,"megapixelsteps_generated":571201,"img2img":true,"painting":false,"post_processing":false,"lora":true,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"text","name":"Logicism - Solo's Millenium","id_":"f3e5b4a0-26f2-4e8d-bff5-5ca726bbb424","online":true,"requests_fulfilled":16245,"kudos_rewards":322300.0,"kudos_details":{"generated":199086.0,"uptime":123256},"performance":"1.8 tokens per second","threads":1,"uptime":1345764,"maintenance_mode":false,"paused":null,"info":"Ryzen 7 5800X. Uptime may not be consistent due to usage for Gaming or Streaming. https://twitch.tv/LogicismTV Uses https://github.com/LogicismDev/Java-Horde-Bridge","nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":297,"models":["koboldcpp/Mistral-7B-claude-chat"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"Java Horde Bridge:2:https://github.com/LogicismDev/Java-Horde-Bridge","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":512,"max_context_length":4096,"tokens_generated":null},{"type_":"text","name":"very slow cpu","id_":"7a655fc1-261c-40b3-a841-af33c372e3d8","online":true,"requests_fulfilled":30150,"kudos_rewards":2383139.0,"kudos_details":{"generated":1800515.0,"uptime":582624},"performance":"1.7 tokens per second","threads":1,"uptime":2428779,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":6,"models":["Henk717/airochronos-33B"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"KoboldAI Bridge:10:https://github.com/db0/KoboldAI-Horde-Bridge","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":256,"max_context_length":512,"tokens_generated":null},{"type_":"text","name":"AIKoboldianAuto0","id_":"38975e39-a1fc-458c-a53d-053ff9b1969c","online":true,"requests_fulfilled":43279,"kudos_rewards":818781.0,"kudos_details":{"generated":631769.0,"uptime":200540},"performance":"3.8 tokens per second","threads":1,"uptime":1548098,"maintenance_mode":false,"paused":null,"info":"Maxwell M40 24gb managed via unicorn server","nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":22,"models":["koboldcpp/Silicon-Maid-7B"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"KoboldCppEmbedWorker:2:https://github.com/LostRuins/koboldcpp","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":512,"max_context_length":4096,"tokens_generated":null},{"type_":"text","name":"ElementalNI2-MX150","id_":"8feb9e4f-8ed3-4561-95dc-205f334ae19c","online":true,"requests_fulfilled":7733,"kudos_rewards":77106.0,"kudos_details":{"generated":56248.0,"uptime":21520},"performance":"1.9 tokens per second","threads":1,"uptime":336378,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":8,"models":["koboldcpp/OpenHermes-2.5-Mistral-7B"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"KoboldCppEmbedWorker:2:https://github.com/LostRuins/koboldcpp","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":768,"max_context_length":1024,"tokens_generated":null},{"type_":"text","name":"Inot","id_":"ce680dc3-504f-4bf2-9f59-025d5df413de","online":true,"requests_fulfilled":12469,"kudos_rewards":96748.0,"kudos_details":{"generated":19407.0,"uptime":82622},"performance":"1.3 tokens per second","threads":1,"uptime":1179676,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":567,"models":["koboldcpp/airoboros-l2-7B-gpt4-m2.0.Q4_0"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"KoboldCppEmbedWorker:2:https://github.com/LostRuins/koboldcpp","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":256,"max_context_length":5120,"tokens_generated":null},{"type_":"text","name":"Silmeria","id_":"ff1f24cc-fa49-48fe-adfe-e2f4937f4547","online":true,"requests_fulfilled":148009,"kudos_rewards":3721060.0,"kudos_details":{"generated":3272106.0,"uptime":495304},"performance":"12.9 tokens per second","threads":1,"uptime":1571970,"maintenance_mode":false,"paused":null,"info":"Q4_K_S. Uptime not guaranteed as the machine may also be used for personal stuff. Issues/abuse: @artefact2 on Discord.","nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":99,"models":["koboldcpp/Chronomaid-Storytelling-13b"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"KoboldCppEmbedWorker:2:https://github.com/LostRuins/koboldcpp","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":512,"max_context_length":4096,"tokens_generated":null},{"type_":"text","name":"AIKoboldianAuto2","id_":"bf41c564-bd11-445a-93fd-1edd160d2a93","online":true,"requests_fulfilled":42878,"kudos_rewards":799430.0,"kudos_details":{"generated":611591.0,"uptime":198658},"performance":"3.5 tokens per second","threads":1,"uptime":1546731,"maintenance_mode":false,"paused":null,"info":"Maxwell M40 24gb managed via unicorn server","nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":17,"models":["koboldcpp/Silicon-Maid-7B"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"KoboldCppEmbedWorker:2:https://github.com/LostRuins/koboldcpp","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":512,"max_context_length":4096,"tokens_generated":null},{"type_":"text","name":"slower cpu","id_":"0f382d12-8fa6-4253-bcf8-3ed5749a9e1f","online":true,"requests_fulfilled":192879,"kudos_rewards":7881222.0,"kudos_details":{"generated":5686352.0,"uptime":2195071},"performance":"1.4 tokens per second","threads":1,"uptime":16075952,"maintenance_mode":false,"paused":null,"info":"","nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":680,"models":["Henk717/airochronos-33B"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"KoboldAI Bridge:10:https://github.com/db0/KoboldAI-Horde-Bridge","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":256,"max_context_length":512,"tokens_generated":null},{"type_":"text","name":"ElementalNI2-RTX3060","id_":"628c5994-1abc-41dc-afab-942ec9757e69","online":true,"requests_fulfilled":5818,"kudos_rewards":101253.0,"kudos_details":{"generated":59966.0,"uptime":42500},"performance":"1.6 tokens per second","threads":1,"uptime":320690,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":23,"models":["koboldcpp/OpenHermes-2.5-Mistral-7B"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"KoboldCppEmbedWorker:2:https://github.com/LostRuins/koboldcpp","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":3072,"max_context_length":4096,"tokens_generated":null},{"type_":"text","name":"MarsupialMerengue","id_":"102b84ae-8314-4bd2-8261-3da6bed53b67","online":true,"requests_fulfilled":72983,"kudos_rewards":1559859.0,"kudos_details":{"generated":1243628.0,"uptime":331616},"performance":"5.0 tokens per second","threads":1,"uptime":1315799,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":161,"models":["koboldcpp/LLaMA2-13B-Psyfighter2"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"KoboldCppEmbedWorker:2:https://github.com/LostRuins/koboldcpp","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":250,"max_context_length":4096,"tokens_generated":null},{"type_":"text","name":"flamingGPU","id_":"953aa8ec-602c-421e-8cd0-0646a629442f","online":true,"requests_fulfilled":64008,"kudos_rewards":743215.0,"kudos_details":{"generated":674691.0,"uptime":74987},"performance":"11.8 tokens per second","threads":1,"uptime":715909,"maintenance_mode":false,"paused":null,"info":"4070Ti | Not always online when gaming or streaming.","nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":29,"models":["SanjiWatsuki/Silicon-Maid-7B"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":200,"max_context_length":2048,"tokens_generated":null},{"type_":"text","name":"ozoi!","id_":"8435bc3c-c165-40de-add5-17deaadcf4a3","online":true,"requests_fulfilled":164716,"kudos_rewards":1223457.0,"kudos_details":{"generated":897790.0,"uptime":342056},"performance":"9.4 tokens per second","threads":1,"uptime":2870264,"maintenance_mode":false,"paused":null,"info":"q6_K\\nUses koboldcpp 1.53 due to performance regressions in later versions, so dynatemp is not supported.\\nhttps://www.youtube.com/watch?v=OOHhV52wI2Y","nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":175,"models":["koboldcpp/piano-medley-7b"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"KoboldCppEmbedWorker:2:https://github.com/LostRuins/koboldcpp","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":300,"max_context_length":8192,"tokens_generated":null},{"type_":"text","name":"MarsupialMosh","id_":"89b8c2cf-e73b-4751-ad5e-1ce3bb80fca9","online":true,"requests_fulfilled":58573,"kudos_rewards":1331676.0,"kudos_details":{"generated":1075834.0,"uptime":268636},"performance":"6.6 tokens per second","threads":1,"uptime":1073637,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":160,"models":["koboldcpp/LLaMA2-13B-Psyfighter2"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"KoboldCppEmbedWorker:2:https://github.com/LostRuins/koboldcpp","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":250,"max_context_length":4096,"tokens_generated":null},{"type_":"text","name":"AIKoboldianAuto1","id_":"d5cddf59-60cd-4fd3-8feb-3b8a0178e87a","online":true,"requests_fulfilled":42974,"kudos_rewards":797490.0,"kudos_details":{"generated":610832.0,"uptime":199083},"performance":"3.6 tokens per second","threads":1,"uptime":1546531,"maintenance_mode":false,"paused":null,"info":"Maxwell M40 24gb managed via unicorn server","nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":25,"models":["koboldcpp/Silicon-Maid-7B"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"KoboldCppEmbedWorker:2:https://github.com/LostRuins/koboldcpp","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":512,"max_context_length":4096,"tokens_generated":null},{"type_":"text","name":"MarsupialMambo","id_":"4f394ea6-10e7-487a-adc0-8250aa5c6d31","online":true,"requests_fulfilled":76796,"kudos_rewards":1626744.0,"kudos_details":{"generated":1299218.0,"uptime":343298},"performance":"6.0 tokens per second","threads":1,"uptime":1376821,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":174,"models":["koboldcpp/LLaMA2-13B-Psyfighter2"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"KoboldCppEmbedWorker:2:https://github.com/LostRuins/koboldcpp","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":250,"max_context_length":4096,"tokens_generated":null},{"type_":"text","name":"silver_worker_3","id_":"1bd50998-196d-4318-b847-e2d395a14dca","online":true,"requests_fulfilled":95793,"kudos_rewards":394477.0,"kudos_details":{"generated":345692.0,"uptime":53800},"performance":"12.3 tokens per second","threads":1,"uptime":1664326,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":5,"models":["aphrodite/goliath-120b-gptq"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":256,"max_context_length":1024,"tokens_generated":null},{"type_":"text","name":"Logicism - Mando's Starfighter","id_":"425ec99a-3602-40c3-9e4c-bdec2f480abd","online":true,"requests_fulfilled":831073,"kudos_rewards":12872095.0,"kudos_details":{"generated":12021619.0,"uptime":850476},"performance":"11.0 tokens per second","threads":1,"uptime":11175856,"maintenance_mode":false,"paused":null,"info":"RTX 3070. Uptime may not be consistent due to usage for Gaming or Streaming. https://twitch.tv/LogicismTV Uses https://github.com/LogicismDev/Java-Horde-Bridge","nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":1045,"models":["Undi95/Toppy-M-7B"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"Java Horde Bridge:2:https://github.com/LogicismDev/Java-Horde-Bridge","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":512,"max_context_length":4096,"tokens_generated":null},{"type_":"text","name":"MarsupialShuffle","id_":"8ebf2f6b-ba7a-416c-bb26-3eeea4b5cba3","online":true,"requests_fulfilled":55022,"kudos_rewards":1260742.0,"kudos_details":{"generated":1015100.0,"uptime":257102},"performance":"7.9 tokens per second","threads":1,"uptime":1030080,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":158,"models":["koboldcpp/LLaMA2-13B-Psyfighter2"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"KoboldCppEmbedWorker:2:https://github.com/LostRuins/koboldcpp","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":250,"max_context_length":4096,"tokens_generated":null},{"type_":"text","name":"MarsupialWatusi","id_":"18aef51a-3be1-4b17-a6a2-dd84b4cfb814","online":true,"requests_fulfilled":62233,"kudos_rewards":1314988.0,"kudos_details":{"generated":1050654.0,"uptime":276000},"performance":"6.3 tokens per second","threads":1,"uptime":1095977,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":159,"models":["koboldcpp/LLaMA2-13B-Psyfighter2"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"KoboldCppEmbedWorker:2:https://github.com/LostRuins/koboldcpp","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":250,"max_context_length":4096,"tokens_generated":null},{"type_":"text","name":"Logicism - Rey's Jakku","id_":"7d97ece4-ea76-480e-8f70-100391f9d519","online":true,"requests_fulfilled":615124,"kudos_rewards":11191839.0,"kudos_details":{"generated":9762015.0,"uptime":1429837},"performance":"9.0 tokens per second","threads":1,"uptime":15905928,"maintenance_mode":false,"paused":null,"info":"GTX 1070. Uptime may not be consistent due to usage for Streaming. https://twitch.tv/LogicismTV Uses https://github.com/LogicismDev/Java-Horde-Bridge","nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":2774,"models":["koboldcpp/Inairtra-7B"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"Java Horde Bridge:2:https://github.com/LogicismDev/Java-Horde-Bridge","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":512,"max_context_length":4096,"tokens_generated":null},{"type_":"text","name":"Test Stand","id_":"d7e0b037-7bd9-45d8-9ec7-70125f22dd46","online":true,"requests_fulfilled":170446,"kudos_rewards":1519766.0,"kudos_details":{"generated":1383561.0,"uptime":154648},"performance":"11.6 tokens per second","threads":1,"uptime":1806668,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":117,"models":["koboldcpp/Echidna-13b-v0.3"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":240,"max_context_length":2048,"tokens_generated":null},{"type_":"text","name":"AIKoboldianAuto3","id_":"36339f93-3015-463a-a9d1-34fbcf4c3439","online":true,"requests_fulfilled":33820,"kudos_rewards":533503.0,"kudos_details":{"generated":449542.0,"uptime":88748},"performance":"7.3 tokens per second","threads":1,"uptime":718264,"maintenance_mode":false,"paused":null,"info":"Maxwell M40 24gb managed via unicorn server","nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":11,"models":["koboldcpp/Silicon-Maid-7B"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"KoboldCppEmbedWorker:2:https://github.com/LostRuins/koboldcpp","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":512,"max_context_length":4096,"tokens_generated":null},{"type_":"text","name":"Dimenzio","id_":"300b7bca-4871-4686-a9a9-fcd3d71ab70a","online":true,"requests_fulfilled":229,"kudos_rewards":284.0,"kudos_details":{"generated":232.0,"uptime":56},"performance":"15.1 tokens per second","threads":1,"uptime":1389,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":false,"flagged":false,"suspicious":null,"uncompleted_jobs":0,"models":["koboldcpp/Noromaid-13b-v0.1.1.q5_k_m"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"KoboldCppEmbedWorker:2:https://github.com/LostRuins/koboldcpp","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":256,"max_context_length":2048,"tokens_generated":null},{"type_":"text","name":"MarsupialTwist","id_":"eab901c2-5210-46c0-8e8c-024324952c2d","online":true,"requests_fulfilled":63560,"kudos_rewards":1338345.0,"kudos_details":{"generated":1073043.0,"uptime":278370},"performance":"4.0 tokens per second","threads":1,"uptime":1107132,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":153,"models":["koboldcpp/LLaMA2-13B-Psyfighter2"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"KoboldCppEmbedWorker:2:https://github.com/LostRuins/koboldcpp","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":250,"max_context_length":4096,"tokens_generated":null},{"type_":"text","name":"ElementalNI-Snapdragon888","id_":"0077bfcf-01fc-4f53-8b5e-cfa8b793bdd9","online":true,"requests_fulfilled":21900,"kudos_rewards":166647.0,"kudos_details":{"generated":60054.0,"uptime":108488},"performance":"2.3 tokens per second","threads":1,"uptime":4536066,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":75,"models":["koboldcpp/Marx-3B-V3"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"KoboldCppEmbedWorker:2:https://github.com/LostRuins/koboldcpp","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":256,"max_context_length":512,"tokens_generated":null},{"type_":"text","name":"TwiggyWorker","id_":"dc26bae5-13aa-4583-aefc-0d6ba1e1bcaf","online":true,"requests_fulfilled":11475,"kudos_rewards":15541.0,"kudos_details":{"generated":11478.0,"uptime":4340},"performance":"16.9 tokens per second","threads":1,"uptime":94839,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":false,"flagged":false,"suspicious":null,"uncompleted_jobs":1,"models":["koboldcpp/echidna-tiefigther-25.Q4_K_M"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"KoboldCppEmbedWorker:2:https://github.com/LostRuins/koboldcpp","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":256,"max_context_length":2048,"tokens_generated":null},{"type_":"text","name":"letsmakesomehentai","id_":"ac6ec52c-a3b4-4ec4-a8d8-29dbad0eeeaa","online":true,"requests_fulfilled":8435,"kudos_rewards":118045.0,"kudos_details":{"generated":115702.0,"uptime":2529},"performance":"21.2 tokens per second","threads":1,"uptime":46051,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":30,"models":["aphrodite/KatyTheCutie/EstopianMaid-13B"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"KoboldCppEmbedWorker:2:https://github.com/LostRuins/koboldcpp","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":256,"max_context_length":8192,"tokens_generated":null},{"type_":"text","name":"silver_worker_4","id_":"301e61e1-b1e2-46a8-a42e-16dc2da60918","online":true,"requests_fulfilled":121121,"kudos_rewards":385658.0,"kudos_details":{"generated":348996.0,"uptime":38080},"performance":"18.3 tokens per second","threads":1,"uptime":1145974,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":1,"models":["aphrodite/genshin13b-chatml"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":256,"max_context_length":1024,"tokens_generated":null},{"type_":"text","name":"Random Banana 1","id_":"f57e68f9-2fe6-4c98-8fbb-dc623d2b7e10","online":true,"requests_fulfilled":13406,"kudos_rewards":58254.0,"kudos_details":{"generated":52162.0,"uptime":7144},"performance":"10.4 tokens per second","threads":1,"uptime":178190,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":0,"models":["LLaMA2-13B-Estopia-GPTQ"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":256,"max_context_length":1512,"tokens_generated":null},{"type_":"text","name":"Jellybean","id_":"568ce3d3-24e3-4717-b7c3-15468ba8984e","online":true,"requests_fulfilled":578,"kudos_rewards":2252.0,"kudos_details":{"generated":1378.0,"uptime":884},"performance":"1.3 tokens per second","threads":1,"uptime":25291,"maintenance_mode":true,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":43,"models":["TheBloke/Mistral-7B-OpenOrca-GPTQ"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":200,"max_context_length":2048,"tokens_generated":null},{"type_":"text","name":"Azbios","id_":"d40dd98f-b0bb-4af0-b954-91c421139fef","online":true,"requests_fulfilled":9281,"kudos_rewards":81048.0,"kudos_details":{"generated":64939.0,"uptime":17374},"performance":"16.5 tokens per second","threads":1,"uptime":148382,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":8,"models":["koboldcpp/co-bagel-mi-v2"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"KoboldCppEmbedWorker:2:https://github.com/LostRuins/koboldcpp","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":512,"max_context_length":8192,"tokens_generated":null},{"type_":"text","name":"pi 5","id_":"33bcc73a-7127-4dbb-af44-e59b6a911251","online":true,"requests_fulfilled":7363,"kudos_rewards":87951.0,"kudos_details":{"generated":49263.0,"uptime":38688},"performance":"3.3 tokens per second","threads":1,"uptime":1198866,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":3,"models":["EleutherAI/pythia-13b"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"KoboldAI Bridge:10:https://github.com/db0/KoboldAI-Horde-Bridge","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":256,"max_context_length":512,"tokens_generated":null},{"type_":"text","name":"MarsupialMultiplex","id_":"9ae1dc97-bc22-4e91-9a0b-744c7d4eb17b","online":true,"requests_fulfilled":213761,"kudos_rewards":3566722.0,"kudos_details":{"generated":3553024.0,"uptime":180263},"performance":"6.0 tokens per second","threads":6,"uptime":827337,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":133,"models":["aphrodite/KatyTheCutie/EstopianMaid-13B"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":200,"max_context_length":4096,"tokens_generated":null},{"type_":"text","name":"PyrFallback2","id_":"6ea34d35-ffa7-4b8b-bb17-6398005d2505","online":true,"requests_fulfilled":1536199,"kudos_rewards":32798940.0,"kudos_details":{"generated":33253463.0,"uptime":493634},"performance":"5.3 tokens per second","threads":10,"uptime":2892996,"maintenance_mode":false,"paused":null,"info":null,"nsfw":true,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":320,"models":["aphrodite/derceto-labs/Psycho-Crystal-16B"],"forms":null,"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":512,"max_context_length":4096,"tokens_generated":null},{"type_":"interrogation","name":"YetAnotherAlchemist","id_":"6355e4e4-0bb2-4ef9-83ec-488d4ec9c317","online":true,"requests_fulfilled":45,"kudos_rewards":47805.0,"kudos_details":{"generated":null,"uptime":47760},"performance":"19.9 seconds per form","threads":1,"uptime":727188,"maintenance_mode":false,"paused":null,"info":null,"nsfw":false,"owner":null,"ipaddr":null,"trusted":false,"flagged":false,"suspicious":null,"uncompleted_jobs":1,"models":null,"forms":["nsfw"],"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"interrogation","name":"E","id_":"77ec6eec-5a7a-4a94-b8b1-f1f9efb39cfb","online":true,"requests_fulfilled":1035,"kudos_rewards":206159.0,"kudos_details":{"generated":null,"uptime":203280},"performance":"13.8 seconds per form","threads":1,"uptime":3083970,"maintenance_mode":false,"paused":null,"info":null,"nsfw":false,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":35,"models":null,"forms":["4x_AnimeSharp","caption","CodeFormers","GFPGAN","interrogation","NMKD_Siax","nsfw","RealESRGAN_x2plus","RealESRGAN_x4plus","RealESRGAN_x4plus_anime_6B","strip_background"],"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"interrogation","name":"itsaveryunqiuenametoday123","id_":"4d477114-4c9c-435f-a1e6-9d4ef764e444","online":true,"requests_fulfilled":481,"kudos_rewards":98772.0,"kudos_details":{"generated":null,"uptime":97440},"performance":"5.2 seconds per form","threads":1,"uptime":1476491,"maintenance_mode":false,"paused":null,"info":null,"nsfw":false,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":5,"models":null,"forms":["4x_AnimeSharp","caption","CodeFormers","GFPGAN","interrogation","NMKD_Siax","nsfw","RealESRGAN_x2plus","RealESRGAN_x4plus","RealESRGAN_x4plus_anime_6B","strip_background"],"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"interrogation","name":"The Azure Happiness","id_":"d9e2e140-3622-417a-973f-4a26315ec987","online":true,"requests_fulfilled":22837,"kudos_rewards":1563298.0,"kudos_details":{"generated":null,"uptime":1532245},"performance":"6.4 seconds per form","threads":2,"uptime":23457142,"maintenance_mode":false,"paused":null,"info":null,"nsfw":false,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":1461,"models":null,"forms":["caption","interrogation","nsfw"],"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:20:https://github.com/db0/AI-Horde-Worker","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"interrogation","name":"theblazehen1","id_":"90ee81b3-4fa8-4694-b8cd-c75514facd85","online":true,"requests_fulfilled":2053,"kudos_rewards":311238.0,"kudos_details":{"generated":null,"uptime":308240},"performance":"3.9 seconds per form","threads":1,"uptime":4628386,"maintenance_mode":true,"paused":null,"info":null,"nsfw":false,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":248,"models":null,"forms":["4x_AnimeSharp","caption","CodeFormers","GFPGAN","interrogation","NMKD_Siax","nsfw","RealESRGAN_x2plus","RealESRGAN_x4plus","RealESRGAN_x4plus_anime_6B","strip_background"],"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"interrogation","name":"DockerWorker#2154670470","id_":"c3df5737-df14-4931-95ac-f87fa4493520","online":true,"requests_fulfilled":647,"kudos_rewards":576406.0,"kudos_details":{"generated":null,"uptime":575760},"performance":"7.0 seconds per form","threads":1,"uptime":8655320,"maintenance_mode":false,"paused":null,"info":null,"nsfw":false,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":2,"models":null,"forms":["caption"],"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"interrogation","name":"TR3MOL0","id_":"693231e5-aa2f-4269-8a34-2258fe3266b9","online":true,"requests_fulfilled":414,"kudos_rewards":20950.0,"kudos_details":{"generated":null,"uptime":19800},"performance":"14.2 seconds per form","threads":1,"uptime":302691,"maintenance_mode":false,"paused":null,"info":null,"nsfw":false,"owner":null,"ipaddr":null,"trusted":false,"flagged":false,"suspicious":null,"uncompleted_jobs":21,"models":null,"forms":["4x_AnimeSharp","caption","CodeFormers","GFPGAN","interrogation","NMKD_Siax","nsfw","RealESRGAN_x2plus","RealESRGAN_x4plus","RealESRGAN_x4plus_anime_6B","strip_background"],"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"interrogation","name":"DockerWorker#8370977273","id_":"90389241-fcab-42ea-97c5-0de33ee5c6d2","online":true,"requests_fulfilled":306,"kudos_rewards":352946.0,"kudos_details":{"generated":null,"uptime":352640},"performance":"8.0 seconds per form","threads":1,"uptime":5300208,"maintenance_mode":false,"paused":null,"info":null,"nsfw":false,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":1,"models":null,"forms":["caption"],"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"interrogation","name":"Bordn#4291245741","id_":"48e95875-b7c8-4438-90f9-cc9db38f4c85","online":true,"requests_fulfilled":8133,"kudos_rewards":609593.0,"kudos_details":{"generated":null,"uptime":597960},"performance":"16.6 seconds per form","threads":1,"uptime":9016716,"maintenance_mode":false,"paused":null,"info":null,"nsfw":false,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":626,"models":null,"forms":["4x_AnimeSharp","caption","CodeFormers","GFPGAN","interrogation","NMKD_Siax","nsfw","RealESRGAN_x2plus","RealESRGAN_x4plus","RealESRGAN_x4plus_anime_6B","strip_background"],"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"interrogation","name":"Automated Instance #61757694","id_":"acdafe52-3e66-46a8-ad4c-4bb0c335021d","online":true,"requests_fulfilled":1,"kudos_rewards":401.0,"kudos_details":{"generated":null,"uptime":400},"performance":"32.0 seconds per form","threads":1,"uptime":6019,"maintenance_mode":false,"paused":null,"info":null,"nsfw":false,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":0,"models":null,"forms":["4x_AnimeSharp","caption","CodeFormers","GFPGAN","interrogation","NMKD_Siax","nsfw","RealESRGAN_x2plus","RealESRGAN_x4plus","RealESRGAN_x4plus_anime_6B","strip_background"],"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"interrogation","name":"stablehorde-fs-worker","id_":"8f084136-3824-4e77-a265-2918e23a71c8","online":true,"requests_fulfilled":26018,"kudos_rewards":1564308.0,"kudos_details":{"generated":null,"uptime":1548760},"performance":"4.5 seconds per form","threads":4,"uptime":22936766,"maintenance_mode":false,"paused":null,"info":null,"nsfw":false,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":212,"models":null,"forms":["caption","nsfw"],"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:22:https://github.com/db0/AI-Horde-Worker","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"interrogation","name":"long.johnson.2250","id_":"d1978a23-dbaa-4a73-bade-3953b2b3293a","online":true,"requests_fulfilled":680,"kudos_rewards":225381.0,"kudos_details":{"generated":null,"uptime":223960},"performance":"2.4 seconds per form","threads":1,"uptime":3379016,"maintenance_mode":false,"paused":null,"info":null,"nsfw":false,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":315,"models":null,"forms":["4x_AnimeSharp","CodeFormers","GFPGAN","interrogation","NMKD_Siax","nsfw","RealESRGAN_x2plus","RealESRGAN_x4plus","RealESRGAN_x4plus_anime_6B","strip_background"],"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"interrogation","name":"HeWhoBe3","id_":"bef5c652-26da-48fb-8283-bb98c3bceb71","online":true,"requests_fulfilled":495,"kudos_rewards":100977.0,"kudos_details":{"generated":null,"uptime":99800},"performance":"10.5 seconds per form","threads":1,"uptime":1507076,"maintenance_mode":false,"paused":null,"info":null,"nsfw":false,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":8,"models":null,"forms":["4x_AnimeSharp","caption","CodeFormers","GFPGAN","interrogation","NMKD_Siax","nsfw","RealESRGAN_x2plus","RealESRGAN_x4plus","RealESRGAN_x4plus_anime_6B","strip_background"],"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"interrogation","name":"This is my asweome horder worker that i want to use to help make kudos","id_":"fcdc7e73-7ef1-4c6e-8b38-492c6c18f999","online":true,"requests_fulfilled":1,"kudos_rewards":6160.0,"kudos_details":{"generated":null,"uptime":6160},"performance":"10.6 seconds per form","threads":1,"uptime":93029,"maintenance_mode":false,"paused":null,"info":null,"nsfw":false,"owner":null,"ipaddr":null,"trusted":false,"flagged":false,"suspicious":null,"uncompleted_jobs":0,"models":null,"forms":["4x_AnimeSharp","CodeFormers","GFPGAN","NMKD_Siax","nsfw","RealESRGAN_x2plus","RealESRGAN_x4plus","RealESRGAN_x4plus_anime_6B","strip_background"],"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"interrogation","name":"ElementalNI-Alchemist-As","id_":"d4ab9f03-6a0f-45c6-bfcf-5354d2eee577","online":true,"requests_fulfilled":6379,"kudos_rewards":605593.0,"kudos_details":{"generated":null,"uptime":589360},"performance":"15.9 seconds per form","threads":1,"uptime":8839719,"maintenance_mode":false,"paused":null,"info":null,"nsfw":false,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":171,"models":null,"forms":["4x_AnimeSharp","caption","CodeFormers","GFPGAN","interrogation","NMKD_Siax","nsfw","RealESRGAN_x2plus","RealESRGAN_x4plus","RealESRGAN_x4plus_anime_6B","strip_background"],"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"interrogation","name":"sb0-a2","id_":"b40cee05-445e-4f9d-8a3f-f6fb9acc58bc","online":true,"requests_fulfilled":170,"kudos_rewards":34691.0,"kudos_details":{"generated":null,"uptime":34440},"performance":"10.2 seconds per form","threads":1,"uptime":513938,"maintenance_mode":false,"paused":null,"info":null,"nsfw":false,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":2,"models":null,"forms":["4x_AnimeSharp","caption","CodeFormers","GFPGAN","interrogation","NMKD_Siax","nsfw","RealESRGAN_x2plus","RealESRGAN_x4plus","RealESRGAN_x4plus_anime_6B","strip_background"],"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"interrogation","name":"wlog1730","id_":"c2c779af-36d9-4c2c-a847-472ecb34d74c","online":true,"requests_fulfilled":5418,"kudos_rewards":637294.0,"kudos_details":{"generated":null,"uptime":632160},"performance":"19.1 seconds per form","threads":1,"uptime":9527247,"maintenance_mode":false,"paused":null,"info":null,"nsfw":false,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":57,"models":null,"forms":["caption","nsfw"],"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"interrogation","name":"Gilward Z","id_":"addd4bf2-7397-4723-ad62-b89000621593","online":true,"requests_fulfilled":140,"kudos_rewards":36440.0,"kudos_details":{"generated":null,"uptime":36080},"performance":"22.0 seconds per form","threads":1,"uptime":543310,"maintenance_mode":false,"paused":null,"info":null,"nsfw":false,"owner":null,"ipaddr":null,"trusted":false,"flagged":false,"suspicious":null,"uncompleted_jobs":1,"models":null,"forms":["4x_AnimeSharp","caption","CodeFormers","GFPGAN","interrogation","NMKD_Siax","nsfw","RealESRGAN_x2plus","RealESRGAN_x4plus","RealESRGAN_x4plus_anime_6B","strip_background"],"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"interrogation","name":"vomoto_gen2","id_":"9d6c9786-aab9-4ec4-9845-5c96fea13f76","online":true,"requests_fulfilled":1993,"kudos_rewards":224478.0,"kudos_details":{"generated":null,"uptime":219280},"performance":"9.7 seconds per form","threads":1,"uptime":3337484,"maintenance_mode":false,"paused":null,"info":null,"nsfw":false,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":6,"models":null,"forms":["4x_AnimeSharp","caption","CodeFormers","GFPGAN","interrogation","NMKD_Siax","nsfw","RealESRGAN_x2plus","RealESRGAN_x4plus","RealESRGAN_x4plus_anime_6B","strip_background"],"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"interrogation","name":"DockerWorker#9084404595","id_":"42beeea8-e900-4fb4-b6ff-0963e77d500a","online":true,"requests_fulfilled":318,"kudos_rewards":359318.0,"kudos_details":{"generated":null,"uptime":359000},"performance":"7.1 seconds per form","threads":1,"uptime":5397627,"maintenance_mode":false,"paused":null,"info":null,"nsfw":false,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":0,"models":null,"forms":["caption"],"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"interrogation","name":"Automated Instance #-78674955","id_":"569f4f95-739b-4a45-b1ef-db1be1b3a548","online":true,"requests_fulfilled":5,"kudos_rewards":405.0,"kudos_details":{"generated":null,"uptime":400},"performance":"27.2 seconds per form","threads":1,"uptime":6159,"maintenance_mode":false,"paused":null,"info":null,"nsfw":false,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":0,"models":null,"forms":["4x_AnimeSharp","caption","CodeFormers","GFPGAN","interrogation","NMKD_Siax","nsfw","RealESRGAN_x2plus","RealESRGAN_x4plus","RealESRGAN_x4plus_anime_6B","strip_background"],"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"interrogation","name":"Mach2","id_":"cbbe7972-e415-4018-8633-2f9d879550a8","online":true,"requests_fulfilled":24147,"kudos_rewards":1657020.0,"kudos_details":{"generated":null,"uptime":1633320},"performance":"10.2 seconds per form","threads":3,"uptime":24795691,"maintenance_mode":false,"paused":null,"info":null,"nsfw":false,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":3095,"models":null,"forms":["caption","nsfw"],"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:20:https://github.com/db0/AI-Horde-Worker","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":null,"max_context_length":null,"tokens_generated":null},{"type_":"interrogation","name":"zten-lightning alchemist","id_":"f872c98a-9d89-4edb-a962-c17b3b7d6148","online":true,"requests_fulfilled":548,"kudos_rewards":28592.0,"kudos_details":{"generated":null,"uptime":25880},"performance":"2.6 seconds per form","threads":4,"uptime":389430,"maintenance_mode":false,"paused":null,"info":null,"nsfw":false,"owner":null,"ipaddr":null,"trusted":true,"flagged":false,"suspicious":null,"uncompleted_jobs":1,"models":null,"forms":["4x_AnimeSharp","caption","CodeFormers","GFPGAN","interrogation","NMKD_Siax","nsfw","RealESRGAN_x2plus","RealESRGAN_x4plus","RealESRGAN_x4plus_anime_6B","strip_background"],"team":{"name":null,"id_":null},"contact":null,"bridge_agent":"AI Horde Worker:24:https://github.com/db0/AI-Horde-Worker","max_pixels":null,"megapixelsteps_generated":null,"img2img":null,"painting":null,"post_processing":null,"lora":null,"max_length":null,"max_context_length":null,"tokens_generated":null}] +[] diff --git a/tests/test_dynamically_check_apimodels.py b/tests/test_dynamically_check_apimodels.py index c6ad836..4eeeef8 100644 --- a/tests/test_dynamically_check_apimodels.py +++ b/tests/test_dynamically_check_apimodels.py @@ -5,6 +5,7 @@ from pathlib import Path from types import ModuleType +import pytest from loguru import logger import horde_sdk.ai_horde_api.apimodels @@ -12,6 +13,7 @@ from horde_sdk.consts import HTTPMethod from horde_sdk.generic_api._reflection import get_all_request_types from horde_sdk.generic_api.apimodels import HordeRequest, HordeResponse +from horde_sdk.generic_api.decoration import is_unhashable from horde_sdk.generic_api.utils.swagger import SwaggerDoc EXAMPLE_PAYLOADS: dict[ModuleType, Path] = { @@ -110,6 +112,10 @@ def dynamic_json_load(module: ModuleType) -> None: # Loop through each success status code and test the corresponding success response type. success_status_codes = request_type.get_success_status_response_pairs() for success_status_code, success_response_type in success_status_codes.items(): + if len(success_response_type.model_fields) == 0: + print(f"Response type {success_response_type.__name__} has no fields") + continue + example_response_filename = SwaggerDoc.filename_from_endpoint_path( request_type.get_api_endpoint_subpath(), request_type.get_http_method(), @@ -123,7 +129,10 @@ def dynamic_json_load(module: ModuleType) -> None: try: parsed_model = success_response_type.model_validate(sample_data_json) try: - hash(parsed_model) + if is_unhashable(parsed_model): + logger.debug(f"Unhashable model for {example_response_file_path}") + else: + hash(parsed_model) except NotImplementedError: logger.debug(f"Hashing not implemented for {example_response_file_path}") except Exception as e: @@ -145,7 +154,10 @@ def dynamic_json_load(module: ModuleType) -> None: try: parsed_model = success_response_type.model_validate(sample_data_json) try: - hash(parsed_model) + if is_unhashable(parsed_model): + logger.debug(f"Unhashable model for {example_response_file_path}") + else: + hash(parsed_model) except NotImplementedError: logger.debug(f"Hashing not implemented for {example_response_file_path}") except Exception as e: @@ -168,7 +180,10 @@ def dynamic_json_load(module: ModuleType) -> None: sample_data_json = json.loads(sample_file_handle.read()) parsed_model = success_response_type.model_validate(sample_data_json) try: - hash(parsed_model) + if is_unhashable(parsed_model): + logger.debug(f"Unhashable model for {example_response_file_path}") + else: + hash(parsed_model) except NotImplementedError: logger.debug(f"Hashing not implemented for {example_response_file_path}") except Exception as e: @@ -176,6 +191,7 @@ def dynamic_json_load(module: ModuleType) -> None: print(f"Error: {e}") raise e + @pytest.mark.object_verify def test_horde_api(self) -> None: """Test all models in the `horde_sdk.ai_horde_api.apimodels` module can be instantiated from example JSON.""" self.dynamic_json_load(horde_sdk.ai_horde_api.apimodels) diff --git a/tests/test_ratings_api_models.py b/tests/test_ratings_api_models.py index 3a7ecb1..50df76e 100644 --- a/tests/test_ratings_api_models.py +++ b/tests/test_ratings_api_models.py @@ -38,7 +38,7 @@ def test_user_check_request(self) -> None: ) UserCheckRequest( apikey="key", - accept="non-enum_accept_value", # type: ignore + accept="non-enum_accept_value", user_id="123", divergence=3, minutes=180, diff --git a/tests/test_verify_api_surface.py b/tests/test_verify_api_surface.py new file mode 100644 index 0000000..e567cb3 --- /dev/null +++ b/tests/test_verify_api_surface.py @@ -0,0 +1,103 @@ +import pytest + + +@pytest.mark.object_verify +def test_all_ai_horde_api_models_imported() -> None: + import horde_sdk.ai_horde_api.apimodels + import horde_sdk.meta + + unimported_classes, missing_imports = horde_sdk.meta.any_unimported_classes( + horde_sdk.ai_horde_api.apimodels, + horde_sdk.HordeAPIObject, + ) + + missing_import_names = {cls.__name__ for cls in missing_imports} + assert not unimported_classes, ( + "The following HordeAPIObjects are not imported in the `__init__.py` of the apimodels " + f"namespace: : {missing_imports}" + f"\n\nMissing import names: {missing_import_names}" + ) + + +@pytest.mark.object_verify +def test_all_ai_horde_api_data_objects_imported() -> None: + import horde_sdk.ai_horde_api.apimodels + import horde_sdk.generic_api.apimodels + import horde_sdk.meta + + unimported_classes, missing_imports = horde_sdk.meta.any_unimported_classes( + horde_sdk.ai_horde_api.apimodels, + horde_sdk.generic_api.apimodels.HordeAPIDataObject, + ) + + missing_import_names = {cls.__name__ for cls in missing_imports} + + assert not unimported_classes, ( + "The following HordeAPIDataObjects are not imported in the `__init__.py` of the apimodels " + f"namespace: : {missing_imports}" + f"\n\nMissing import names: {missing_import_names}" + ) + + +@pytest.mark.skip(reason="This test is not yet enforced.") +@pytest.mark.object_verify +def test_all_ai_horde_api_models_defined() -> None: + import horde_sdk.ai_horde_api.apimodels + from horde_sdk.meta import all_undefined_classes + + undefined_classes = all_undefined_classes(horde_sdk.ai_horde_api.apimodels) + + assert ( + "GenerationInputStable" not in undefined_classes + ), "A model which is known to be defined in the SDK was not found. Something critically bad has happened." + + # Pretty print the undefined classes sorted by dict values, NOT by keys + import json + + undefined_classes_sorted = dict(sorted(undefined_classes.items(), key=lambda x: x[1])) + print(json.dumps(undefined_classes_sorted, indent=4)) + + assert not undefined_classes, ( + "The following models are defined in the API but not in the SDK: " f"{undefined_classes}" + ) + + +@pytest.mark.object_verify +def test_all_ai_horde_endpoints_known() -> None: + from horde_sdk.meta import all_unknown_endpoints_ai_horde + + unknown_endpoints = all_unknown_endpoints_ai_horde() + + assert not unknown_endpoints, ( + "The following endpoints are defined in the API but not in the SDK: " f"{unknown_endpoints}" + ) + + +@pytest.mark.skip(reason="This test is not yet enforced.") +@pytest.mark.object_verify +def test_all_ai_horde_endpoints_addressed() -> None: + from horde_sdk.meta import all_unaddressed_endpoints_ai_horde + + unaddressed_endpoints = all_unaddressed_endpoints_ai_horde() + + assert not unaddressed_endpoints, ( + "The following endpoints are defined in the API but not in the SDK: " f"{unaddressed_endpoints}" + ) + + +@pytest.mark.object_verify +def test_all_ratings_api_models_imported() -> None: + import horde_sdk.ratings_api.apimodels # noqa: I001 + import horde_sdk.meta + + unimported_classes, missing_imports = horde_sdk.meta.any_unimported_classes( + horde_sdk.ratings_api.apimodels, + horde_sdk.HordeAPIObject, + ) + + missing_import_names = {cls.__name__ for cls in missing_imports} + assert not unimported_classes, ( + "The following HordeAPIObjects are not imported in the `__init__.py` of the apimodels " + f"namespace: : {missing_imports}" + f"\n\nMissing import names: {missing_import_names}" + )