dsh-plugin-image-workbench 0.0.0-stage → 1.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.en.md +258 -0
- package/README.md +260 -2
- package/client.js +241 -0
- package/cordis.patch.yml +14 -0
- package/host.js +692 -0
- package/install.mjs +333 -0
- package/package.json +58 -4
- package/scripts/fetch-style-images.mjs +104 -0
- package/scripts/vendor-snapshot.py +154 -0
- package/server/access.py +26 -0
- package/server/app.py +1329 -0
- package/server/catalog.json +1 -0
- package/server/catalog.py +147 -0
- package/server/composer.py +391 -0
- package/server/drafts.py +158 -0
- package/server/history.py +575 -0
- package/server/imaging.py +172 -0
- package/server/pool.py +1335 -0
- package/server/pool_admin.py +921 -0
- package/server/pool_service.py +1073 -0
- package/server/requirements.txt +6 -0
- package/server/smoke_check.py +402 -0
- package/server/static/access.html +202 -0
- package/server/static/index.html +4725 -0
- package/vendor/chatgpt2api/README.md +13 -0
- package/vendor/chatgpt2api/SOURCE.json +11 -0
- package/vendor/chatgpt2api/runtime/uv.exe +0 -0
- package/vendor/chatgpt2api/source/.env.example +82 -0
- package/vendor/chatgpt2api/source/.python-version +1 -0
- package/vendor/chatgpt2api/source/LICENSE +21 -0
- package/vendor/chatgpt2api/source/README.md +391 -0
- package/vendor/chatgpt2api/source/VERSION +1 -0
- package/vendor/chatgpt2api/source/api/__init__.py +2 -0
- package/vendor/chatgpt2api/source/api/accounts.py +537 -0
- package/vendor/chatgpt2api/source/api/ai.py +217 -0
- package/vendor/chatgpt2api/source/api/app.py +62 -0
- package/vendor/chatgpt2api/source/api/errors.py +46 -0
- package/vendor/chatgpt2api/source/api/image_inputs.py +309 -0
- package/vendor/chatgpt2api/source/api/image_tasks.py +120 -0
- package/vendor/chatgpt2api/source/api/support.py +134 -0
- package/vendor/chatgpt2api/source/api/system.py +365 -0
- package/vendor/chatgpt2api/source/assets/atlascloud.svg +1 -0
- package/vendor/chatgpt2api/source/assets/ipwo.png +0 -0
- package/vendor/chatgpt2api/source/assets/kunrouai.svg +50 -0
- package/vendor/chatgpt2api/source/config.default.json +108 -0
- package/vendor/chatgpt2api/source/main.py +9 -0
- package/vendor/chatgpt2api/source/pyproject.toml +27 -0
- package/vendor/chatgpt2api/source/scripts/init_proxy_config.py +186 -0
- package/vendor/chatgpt2api/source/scripts/migrate_storage.py +163 -0
- package/vendor/chatgpt2api/source/scripts/privoxy-warp.conf +13 -0
- package/vendor/chatgpt2api/source/scripts/test_outlook_token_mailbox.py +385 -0
- package/vendor/chatgpt2api/source/scripts/test_storage.py +129 -0
- package/vendor/chatgpt2api/source/scripts/verify_oauth_refresh.py +92 -0
- package/vendor/chatgpt2api/source/services/__init__.py +0 -0
- package/vendor/chatgpt2api/source/services/account_service.py +1757 -0
- package/vendor/chatgpt2api/source/services/auth_service.py +253 -0
- package/vendor/chatgpt2api/source/services/backup_service.py +671 -0
- package/vendor/chatgpt2api/source/services/config.py +667 -0
- package/vendor/chatgpt2api/source/services/content_filter.py +242 -0
- package/vendor/chatgpt2api/source/services/cpa_service.py +315 -0
- package/vendor/chatgpt2api/source/services/editable_file_task_service.py +260 -0
- package/vendor/chatgpt2api/source/services/image_service.py +376 -0
- package/vendor/chatgpt2api/source/services/image_storage_service.py +405 -0
- package/vendor/chatgpt2api/source/services/image_tags_service.py +76 -0
- package/vendor/chatgpt2api/source/services/image_task_service.py +550 -0
- package/vendor/chatgpt2api/source/services/log_service.py +337 -0
- package/vendor/chatgpt2api/source/services/model_service.py +182 -0
- package/vendor/chatgpt2api/source/services/oauth_login_service.py +267 -0
- package/vendor/chatgpt2api/source/services/openai_backend_api.py +2763 -0
- package/vendor/chatgpt2api/source/services/openai_oauth.py +32 -0
- package/vendor/chatgpt2api/source/services/protocol/__init__.py +2 -0
- package/vendor/chatgpt2api/source/services/protocol/anthropic_v1_messages.py +307 -0
- package/vendor/chatgpt2api/source/services/protocol/chat_completion_cache.py +238 -0
- package/vendor/chatgpt2api/source/services/protocol/conversation.py +1644 -0
- package/vendor/chatgpt2api/source/services/protocol/error_response.py +124 -0
- package/vendor/chatgpt2api/source/services/protocol/openai_search.py +19 -0
- package/vendor/chatgpt2api/source/services/protocol/openai_v1_chat_complete.py +316 -0
- package/vendor/chatgpt2api/source/services/protocol/openai_v1_image_edit.py +86 -0
- package/vendor/chatgpt2api/source/services/protocol/openai_v1_image_generations.py +42 -0
- package/vendor/chatgpt2api/source/services/protocol/openai_v1_models.py +53 -0
- package/vendor/chatgpt2api/source/services/protocol/openai_v1_response.py +453 -0
- package/vendor/chatgpt2api/source/services/protocol/web_search_tool.py +160 -0
- package/vendor/chatgpt2api/source/services/proxy_service.py +648 -0
- package/vendor/chatgpt2api/source/services/storage/__init__.py +5 -0
- package/vendor/chatgpt2api/source/services/storage/base.py +38 -0
- package/vendor/chatgpt2api/source/services/storage/database_storage.py +194 -0
- package/vendor/chatgpt2api/source/services/storage/factory.py +104 -0
- package/vendor/chatgpt2api/source/services/storage/git_storage.py +183 -0
- package/vendor/chatgpt2api/source/services/storage/json_storage.py +95 -0
- package/vendor/chatgpt2api/source/services/sub2api_service.py +531 -0
- package/vendor/chatgpt2api/source/utils/__init__.py +0 -0
- package/vendor/chatgpt2api/source/utils/helper.py +469 -0
- package/vendor/chatgpt2api/source/utils/image_tokens.py +346 -0
- package/vendor/chatgpt2api/source/utils/log.py +110 -0
- package/vendor/chatgpt2api/source/utils/pkce.py +19 -0
- package/vendor/chatgpt2api/source/utils/pow.py +207 -0
- package/vendor/chatgpt2api/source/utils/sentinel.py +159 -0
- package/vendor/chatgpt2api/source/utils/turnstile.py +184 -0
- package/vendor/chatgpt2api/source/uv.lock +904 -0
- package/vendor/chatgpt2api/source/web/bun.lock +1501 -0
- package/vendor/chatgpt2api/source/web/components.json +24 -0
- package/vendor/chatgpt2api/source/web/eslint.config.mjs +17 -0
- package/vendor/chatgpt2api/source/web/next.config.ts +40 -0
- package/vendor/chatgpt2api/source/web/package.json +58 -0
- package/vendor/chatgpt2api/source/web/postcss.config.mjs +5 -0
- package/vendor/chatgpt2api/source/web/public/github.svg +1 -0
- package/vendor/chatgpt2api/source/web/public/next.svg +1 -0
- package/vendor/chatgpt2api/source/web/public/openai.svg +1 -0
- package/vendor/chatgpt2api/source/web/public/vercel.svg +1 -0
- package/vendor/chatgpt2api/source/web/src/app/accounts/components/account-import-dialog.tsx +925 -0
- package/vendor/chatgpt2api/source/web/src/app/accounts/page.tsx +1307 -0
- package/vendor/chatgpt2api/source/web/src/app/debug/components/chat-panel.tsx +224 -0
- package/vendor/chatgpt2api/source/web/src/app/debug/components/editable-file-panel.tsx +444 -0
- package/vendor/chatgpt2api/source/web/src/app/debug/components/ppt-panel.tsx +9 -0
- package/vendor/chatgpt2api/source/web/src/app/debug/components/psd-panel.tsx +9 -0
- package/vendor/chatgpt2api/source/web/src/app/debug/components/search-panel.tsx +172 -0
- package/vendor/chatgpt2api/source/web/src/app/debug/components/skill-panel.tsx +169 -0
- package/vendor/chatgpt2api/source/web/src/app/debug/components/types.ts +39 -0
- package/vendor/chatgpt2api/source/web/src/app/debug/page.tsx +59 -0
- package/vendor/chatgpt2api/source/web/src/app/favicon.ico +0 -0
- package/vendor/chatgpt2api/source/web/src/app/globals.css +231 -0
- package/vendor/chatgpt2api/source/web/src/app/image/components/image-composer.tsx +542 -0
- package/vendor/chatgpt2api/source/web/src/app/image/components/image-results.tsx +554 -0
- package/vendor/chatgpt2api/source/web/src/app/image/components/image-sidebar.tsx +176 -0
- package/vendor/chatgpt2api/source/web/src/app/image/page.tsx +1799 -0
- package/vendor/chatgpt2api/source/web/src/app/image-manager/page.tsx +732 -0
- package/vendor/chatgpt2api/source/web/src/app/layout.tsx +50 -0
- package/vendor/chatgpt2api/source/web/src/app/login/page.tsx +101 -0
- package/vendor/chatgpt2api/source/web/src/app/logs/page.tsx +356 -0
- package/vendor/chatgpt2api/source/web/src/app/page.tsx +30 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/api-docs-card.tsx +341 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/backup-settings-card.tsx +447 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/base-url-card.tsx +71 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/config-card.tsx +491 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/cpa-pool-dialog.tsx +108 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/cpa-pools-card.tsx +169 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/import-browser-dialog.tsx +185 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/proxy-runtime-card.tsx +379 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/proxy-settings-card.tsx +127 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/proxy-settings.tsx +200 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/settings-header.tsx +12 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/sub2api-connections.tsx +886 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/third-party-apps-card.tsx +81 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/user-keys-card.tsx +422 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/page.tsx +135 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/store.ts +1069 -0
- package/vendor/chatgpt2api/source/web/src/components/date-range-filter.tsx +49 -0
- package/vendor/chatgpt2api/source/web/src/components/header-actions.tsx +24 -0
- package/vendor/chatgpt2api/source/web/src/components/image-lightbox.tsx +434 -0
- package/vendor/chatgpt2api/source/web/src/components/image-thumbnail.tsx +46 -0
- package/vendor/chatgpt2api/source/web/src/components/theme-script.tsx +18 -0
- package/vendor/chatgpt2api/source/web/src/components/theme-toggle.tsx +14 -0
- package/vendor/chatgpt2api/source/web/src/components/top-nav.tsx +263 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/animated-theme-toggler.tsx +256 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/badge.tsx +41 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/button.tsx +59 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/calendar.tsx +46 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/card.tsx +61 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/checkbox.tsx +27 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/dia-text-reveal.tsx +267 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/dialog.tsx +132 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/field.tsx +13 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/input.tsx +19 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/label.tsx +24 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/morphing-text.tsx +149 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/popover.tsx +38 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/select.tsx +175 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/sheet.tsx +143 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/table.tsx +29 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/tabs.tsx +91 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/text-3d-flip.tsx +269 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/textarea.tsx +18 -0
- package/vendor/chatgpt2api/source/web/src/components/version-release-dialog.tsx +127 -0
- package/vendor/chatgpt2api/source/web/src/constants/common-env.ts +6 -0
- package/vendor/chatgpt2api/source/web/src/hooks/use-version-check.ts +85 -0
- package/vendor/chatgpt2api/source/web/src/lib/api.ts +926 -0
- package/vendor/chatgpt2api/source/web/src/lib/auth-session.ts +26 -0
- package/vendor/chatgpt2api/source/web/src/lib/release.ts +25 -0
- package/vendor/chatgpt2api/source/web/src/lib/request.ts +93 -0
- package/vendor/chatgpt2api/source/web/src/lib/use-auth-guard.ts +89 -0
- package/vendor/chatgpt2api/source/web/src/lib/utils.ts +6 -0
- package/vendor/chatgpt2api/source/web/src/store/auth.ts +112 -0
- package/vendor/chatgpt2api/source/web/src/store/editable-file-history.ts +33 -0
- package/vendor/chatgpt2api/source/web/src/store/image-conversations.ts +318 -0
- package/vendor/chatgpt2api/source/web/tsconfig.json +42 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from fastapi.responses import JSONResponse
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _message_from_value(value: object) -> str:
|
|
9
|
+
if isinstance(value, str):
|
|
10
|
+
return value
|
|
11
|
+
if not isinstance(value, dict):
|
|
12
|
+
return ""
|
|
13
|
+
message = value.get("message")
|
|
14
|
+
if isinstance(message, str) and message:
|
|
15
|
+
return message
|
|
16
|
+
return _message_from_value(value.get("error"))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def error_message_from_detail(detail: object) -> str:
|
|
20
|
+
if isinstance(detail, list):
|
|
21
|
+
messages = []
|
|
22
|
+
for item in detail:
|
|
23
|
+
if not isinstance(item, dict):
|
|
24
|
+
continue
|
|
25
|
+
location = ".".join(str(part) for part in item.get("loc", []) if part != "body")
|
|
26
|
+
message = str(item.get("msg") or "").strip()
|
|
27
|
+
if location and message:
|
|
28
|
+
messages.append(f"{location}: {message}")
|
|
29
|
+
elif message:
|
|
30
|
+
messages.append(message)
|
|
31
|
+
return "; ".join(messages)
|
|
32
|
+
if isinstance(detail, dict):
|
|
33
|
+
message = _message_from_value(detail.get("error")) or _message_from_value(detail)
|
|
34
|
+
if message:
|
|
35
|
+
return message
|
|
36
|
+
return str(detail or "").strip()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _default_error_type(status_code: int) -> str:
|
|
40
|
+
if status_code == 401:
|
|
41
|
+
return "authentication_error"
|
|
42
|
+
if status_code == 403:
|
|
43
|
+
return "permission_error"
|
|
44
|
+
if status_code == 429:
|
|
45
|
+
return "rate_limit_error"
|
|
46
|
+
if 400 <= status_code < 500:
|
|
47
|
+
return "invalid_request_error"
|
|
48
|
+
return "server_error"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _default_error_code(status_code: int) -> str:
|
|
52
|
+
if status_code == 401:
|
|
53
|
+
return "invalid_api_key"
|
|
54
|
+
if status_code == 403:
|
|
55
|
+
return "permission_denied"
|
|
56
|
+
if status_code == 429:
|
|
57
|
+
return "rate_limit_exceeded"
|
|
58
|
+
if 400 <= status_code < 500:
|
|
59
|
+
return "bad_request"
|
|
60
|
+
return "upstream_error"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def openai_error_payload(
|
|
64
|
+
detail: object,
|
|
65
|
+
status_code: int,
|
|
66
|
+
*,
|
|
67
|
+
error_type: str | None = None,
|
|
68
|
+
code: object | None = None,
|
|
69
|
+
param: object | None = None,
|
|
70
|
+
) -> dict[str, Any]:
|
|
71
|
+
error_detail = detail.get("error") if isinstance(detail, dict) else None
|
|
72
|
+
if isinstance(error_detail, dict):
|
|
73
|
+
return {
|
|
74
|
+
"error": {
|
|
75
|
+
"message": error_message_from_detail(error_detail) or "request failed",
|
|
76
|
+
"type": str(error_detail.get("type") or error_type or _default_error_type(status_code)),
|
|
77
|
+
"param": error_detail.get("param", param),
|
|
78
|
+
"code": error_detail.get("code", code if code is not None else _default_error_code(status_code)),
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
"error": {
|
|
83
|
+
"message": error_message_from_detail(detail) or "request failed",
|
|
84
|
+
"type": error_type or _default_error_type(status_code),
|
|
85
|
+
"param": param,
|
|
86
|
+
"code": code if code is not None else _default_error_code(status_code),
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def openai_error_response(
|
|
92
|
+
detail: object,
|
|
93
|
+
status_code: int,
|
|
94
|
+
*,
|
|
95
|
+
headers: dict[str, str] | None = None,
|
|
96
|
+
error_type: str | None = None,
|
|
97
|
+
code: object | None = None,
|
|
98
|
+
param: object | None = None,
|
|
99
|
+
) -> JSONResponse:
|
|
100
|
+
return JSONResponse(
|
|
101
|
+
status_code=status_code,
|
|
102
|
+
content=openai_error_payload(detail, status_code, error_type=error_type, code=code, param=param),
|
|
103
|
+
headers=headers,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def anthropic_error_response(
|
|
108
|
+
detail: object,
|
|
109
|
+
status_code: int,
|
|
110
|
+
*,
|
|
111
|
+
headers: dict[str, str] | None = None,
|
|
112
|
+
) -> JSONResponse:
|
|
113
|
+
error_type = "api_error" if status_code >= 500 else _default_error_type(status_code)
|
|
114
|
+
return JSONResponse(
|
|
115
|
+
status_code=status_code,
|
|
116
|
+
content={
|
|
117
|
+
"type": "error",
|
|
118
|
+
"error": {
|
|
119
|
+
"type": error_type,
|
|
120
|
+
"message": error_message_from_detail(detail) or "request failed",
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
headers=headers,
|
|
124
|
+
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from services.account_service import account_service
|
|
4
|
+
from services.openai_backend_api import OpenAIBackendAPI, SEARCH_MODEL
|
|
5
|
+
|
|
6
|
+
MODEL = SEARCH_MODEL
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def handle(body: dict[str, object]) -> dict[str, object]:
|
|
10
|
+
token = account_service.get_text_access_token()
|
|
11
|
+
account = account_service.get_account(token) or {}
|
|
12
|
+
backend = OpenAIBackendAPI(token)
|
|
13
|
+
try:
|
|
14
|
+
result = backend.search(str(body["prompt"]))
|
|
15
|
+
finally:
|
|
16
|
+
backend.close()
|
|
17
|
+
account_service.mark_text_used(token)
|
|
18
|
+
result["_account_email"] = str(account.get("email") or "")
|
|
19
|
+
return result
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
import uuid
|
|
5
|
+
from typing import Any, Iterable, Iterator
|
|
6
|
+
|
|
7
|
+
from fastapi import HTTPException
|
|
8
|
+
|
|
9
|
+
from services.protocol.chat_completion_cache import cache_key, chat_completion_cache, normalize_text_messages
|
|
10
|
+
from services.protocol.conversation import (
|
|
11
|
+
ConversationRequest,
|
|
12
|
+
ImageOutput,
|
|
13
|
+
collect_image_outputs,
|
|
14
|
+
collect_text,
|
|
15
|
+
count_message_image_tokens,
|
|
16
|
+
count_message_text_tokens,
|
|
17
|
+
count_text_tokens,
|
|
18
|
+
encode_images,
|
|
19
|
+
normalize_messages,
|
|
20
|
+
stream_image_outputs_with_pool,
|
|
21
|
+
stream_text_deltas,
|
|
22
|
+
text_backend,
|
|
23
|
+
)
|
|
24
|
+
from services.protocol.web_search_tool import (
|
|
25
|
+
WEB_SEARCH_TOOL_TYPES,
|
|
26
|
+
has_unsupported_tools,
|
|
27
|
+
is_web_search_chat_request,
|
|
28
|
+
run_web_search,
|
|
29
|
+
search_query_from_messages,
|
|
30
|
+
text_with_url_citations,
|
|
31
|
+
)
|
|
32
|
+
from utils.helper import build_chat_image_markdown_content, extract_chat_image, extract_chat_prompt, is_image_chat_request, parse_image_count
|
|
33
|
+
from utils.image_tokens import (
|
|
34
|
+
chat_usage_from_image_usage,
|
|
35
|
+
count_image_inputs_tokens,
|
|
36
|
+
count_image_output_items_tokens,
|
|
37
|
+
image_usage,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
TOOL_UNAVAILABLE_SYSTEM_MESSAGE = (
|
|
41
|
+
"This compatibility backend cannot execute local tools, shell commands, non-search tools, "
|
|
42
|
+
"or file operations. Do not claim to have run tools or inspected external resources. "
|
|
43
|
+
"If a user asks you to use a tool, say that tool execution is unavailable through this backend."
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def normalize_thinking_effort(value: object) -> str:
|
|
48
|
+
normalized = str(value or "").strip().lower()
|
|
49
|
+
if normalized in {"", "none"}:
|
|
50
|
+
return ""
|
|
51
|
+
if normalized in {"low", "medium", "high"}:
|
|
52
|
+
return normalized
|
|
53
|
+
if normalized in {"xhigh", "extended"}:
|
|
54
|
+
return "extended"
|
|
55
|
+
return ""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def thinking_effort_from_body(body: dict[str, Any]) -> str:
|
|
59
|
+
if "thinking_effort" in body:
|
|
60
|
+
return normalize_thinking_effort(body.get("thinking_effort"))
|
|
61
|
+
if "reasoning_effort" in body:
|
|
62
|
+
return normalize_thinking_effort(body.get("reasoning_effort"))
|
|
63
|
+
reasoning = body.get("reasoning")
|
|
64
|
+
if isinstance(reasoning, dict):
|
|
65
|
+
return normalize_thinking_effort(reasoning.get("effort"))
|
|
66
|
+
return ""
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def completion_chunk(model: str, delta: dict[str, Any], finish_reason: str | None = None, completion_id: str = "", created: int | None = None) -> dict[str, Any]:
|
|
70
|
+
return {
|
|
71
|
+
"id": completion_id or f"chatcmpl-{uuid.uuid4().hex}",
|
|
72
|
+
"object": "chat.completion.chunk",
|
|
73
|
+
"created": created or int(time.time()),
|
|
74
|
+
"model": model,
|
|
75
|
+
"choices": [{"index": 0, "delta": delta, "finish_reason": finish_reason}],
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def completion_response(
|
|
80
|
+
model: str,
|
|
81
|
+
content: str,
|
|
82
|
+
created: int | None = None,
|
|
83
|
+
messages: list[dict[str, Any]] | None = None,
|
|
84
|
+
annotations: list[dict[str, Any]] | None = None,
|
|
85
|
+
) -> dict[str, Any]:
|
|
86
|
+
prompt_text_tokens = count_message_text_tokens(messages, model) if messages else 0
|
|
87
|
+
prompt_image_tokens = count_message_image_tokens(messages, model) if messages else 0
|
|
88
|
+
prompt_tokens = prompt_text_tokens + prompt_image_tokens
|
|
89
|
+
completion_tokens = count_text_tokens(content, model) if messages else 0
|
|
90
|
+
message = {"role": "assistant", "content": content}
|
|
91
|
+
if annotations:
|
|
92
|
+
message["annotations"] = annotations
|
|
93
|
+
return {
|
|
94
|
+
"id": f"chatcmpl-{uuid.uuid4().hex}",
|
|
95
|
+
"object": "chat.completion",
|
|
96
|
+
"created": created or int(time.time()),
|
|
97
|
+
"model": model,
|
|
98
|
+
"choices": [{
|
|
99
|
+
"index": 0,
|
|
100
|
+
"message": message,
|
|
101
|
+
"finish_reason": "stop",
|
|
102
|
+
}],
|
|
103
|
+
"usage": {
|
|
104
|
+
"prompt_tokens": prompt_tokens,
|
|
105
|
+
"completion_tokens": completion_tokens,
|
|
106
|
+
"total_tokens": prompt_tokens + completion_tokens,
|
|
107
|
+
"prompt_tokens_details": {
|
|
108
|
+
"text_tokens": prompt_text_tokens,
|
|
109
|
+
"image_tokens": prompt_image_tokens,
|
|
110
|
+
"cached_tokens": 0,
|
|
111
|
+
},
|
|
112
|
+
"completion_tokens_details": {
|
|
113
|
+
"text_tokens": completion_tokens,
|
|
114
|
+
"image_tokens": 0,
|
|
115
|
+
"reasoning_tokens": 0,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def stream_text_chat_completion(
|
|
122
|
+
backend,
|
|
123
|
+
messages: list[dict[str, Any]],
|
|
124
|
+
model: str,
|
|
125
|
+
thinking_effort: str = "",
|
|
126
|
+
) -> Iterator[dict[str, Any]]:
|
|
127
|
+
completion_id = f"chatcmpl-{uuid.uuid4().hex}"
|
|
128
|
+
created = int(time.time())
|
|
129
|
+
sent_role = False
|
|
130
|
+
request = ConversationRequest(model=model, messages=messages, thinking_effort=thinking_effort)
|
|
131
|
+
for delta_text in stream_text_deltas(backend, request):
|
|
132
|
+
if not sent_role:
|
|
133
|
+
sent_role = True
|
|
134
|
+
yield completion_chunk(model, {"role": "assistant", "content": delta_text}, None, completion_id, created)
|
|
135
|
+
else:
|
|
136
|
+
yield completion_chunk(model, {"content": delta_text}, None, completion_id, created)
|
|
137
|
+
if not sent_role:
|
|
138
|
+
yield completion_chunk(model, {"role": "assistant", "content": ""}, None, completion_id, created)
|
|
139
|
+
yield completion_chunk(model, {}, "stop", completion_id, created)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def collect_chat_content(chunks: Iterable[dict[str, Any]]) -> str:
|
|
143
|
+
parts: list[str] = []
|
|
144
|
+
for chunk in chunks:
|
|
145
|
+
choices = chunk.get("choices")
|
|
146
|
+
first = choices[0] if isinstance(choices, list) and choices and isinstance(choices[0], dict) else {}
|
|
147
|
+
delta = first.get("delta") if isinstance(first.get("delta"), dict) else {}
|
|
148
|
+
content = str(delta.get("content") or "")
|
|
149
|
+
if content:
|
|
150
|
+
parts.append(content)
|
|
151
|
+
return "".join(parts)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def chat_messages_from_body(body: dict[str, Any]) -> list[dict[str, Any]]:
|
|
155
|
+
messages = body.get("messages")
|
|
156
|
+
if isinstance(messages, list) and messages:
|
|
157
|
+
return [message for message in messages if isinstance(message, dict)]
|
|
158
|
+
prompt = str(body.get("prompt") or "").strip()
|
|
159
|
+
if prompt:
|
|
160
|
+
return [{"role": "user", "content": prompt}]
|
|
161
|
+
raise HTTPException(status_code=400, detail={"error": "messages or prompt is required"})
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def chat_image_args(body: dict[str, Any]) -> tuple[str, str, int, list[tuple[bytes, str, str]]]:
|
|
165
|
+
model = str(body.get("model") or "gpt-image-2").strip() or "gpt-image-2"
|
|
166
|
+
prompt = extract_chat_prompt(body)
|
|
167
|
+
if not prompt:
|
|
168
|
+
raise HTTPException(status_code=400, detail={"error": "prompt is required"})
|
|
169
|
+
images = [
|
|
170
|
+
(data, f"image_{idx}.png", mime)
|
|
171
|
+
for idx, (data, mime) in enumerate(extract_chat_image(body), start=1)
|
|
172
|
+
]
|
|
173
|
+
return model, prompt, parse_image_count(body.get("n")), images
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def text_chat_parts(body: dict[str, Any]) -> tuple[str, list[dict[str, Any]]]:
|
|
177
|
+
model = str(body.get("model") or "auto").strip() or "auto"
|
|
178
|
+
messages = normalize_text_messages(normalize_messages(chat_messages_from_body(body)))
|
|
179
|
+
if has_unsupported_tools(body, WEB_SEARCH_TOOL_TYPES):
|
|
180
|
+
messages.insert(0, {"role": "system", "content": TOOL_UNAVAILABLE_SYSTEM_MESSAGE})
|
|
181
|
+
return model, messages
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def chat_completion_annotations(annotations: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
185
|
+
output = []
|
|
186
|
+
for item in annotations:
|
|
187
|
+
if item.get("type") != "url_citation":
|
|
188
|
+
continue
|
|
189
|
+
output.append({
|
|
190
|
+
"type": "url_citation",
|
|
191
|
+
"url_citation": {
|
|
192
|
+
"start_index": item.get("start_index", 0),
|
|
193
|
+
"end_index": item.get("end_index", 0),
|
|
194
|
+
"url": item.get("url", ""),
|
|
195
|
+
"title": item.get("title", ""),
|
|
196
|
+
},
|
|
197
|
+
})
|
|
198
|
+
return output
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def web_search_chat_response(messages: list[dict[str, Any]], model: str) -> dict[str, Any]:
|
|
202
|
+
query = search_query_from_messages(messages)
|
|
203
|
+
if not query:
|
|
204
|
+
raise HTTPException(status_code=400, detail={"error": "messages or prompt is required for web search"})
|
|
205
|
+
text, annotations = text_with_url_citations(run_web_search(query))
|
|
206
|
+
return completion_response(
|
|
207
|
+
model,
|
|
208
|
+
text,
|
|
209
|
+
messages=messages,
|
|
210
|
+
annotations=chat_completion_annotations(annotations),
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def stream_web_search_chat_completion(messages: list[dict[str, Any]], model: str) -> Iterator[dict[str, Any]]:
|
|
215
|
+
query = search_query_from_messages(messages)
|
|
216
|
+
if not query:
|
|
217
|
+
raise HTTPException(status_code=400, detail={"error": "messages or prompt is required for web search"})
|
|
218
|
+
text, _annotations = text_with_url_citations(run_web_search(query))
|
|
219
|
+
completion_id = f"chatcmpl-{uuid.uuid4().hex}"
|
|
220
|
+
created = int(time.time())
|
|
221
|
+
yield completion_chunk(model, {"role": "assistant", "content": text}, None, completion_id, created)
|
|
222
|
+
yield completion_chunk(model, {}, "stop", completion_id, created)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def image_result_content(result: dict[str, Any]) -> str:
|
|
226
|
+
data = result.get("data")
|
|
227
|
+
if isinstance(data, list) and data:
|
|
228
|
+
return build_chat_image_markdown_content(result)
|
|
229
|
+
return str(result.get("message") or "Image generation completed.")
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def image_chat_response(body: dict[str, Any]) -> dict[str, Any]:
|
|
233
|
+
model, prompt, n, images = chat_image_args(body)
|
|
234
|
+
result = collect_image_outputs(stream_image_outputs_with_pool(ConversationRequest(
|
|
235
|
+
prompt=prompt,
|
|
236
|
+
model=model,
|
|
237
|
+
n=n,
|
|
238
|
+
response_format="b64_json",
|
|
239
|
+
images=encode_images(images) or None,
|
|
240
|
+
)))
|
|
241
|
+
response = completion_response(model, image_result_content(result), int(result.get("created") or 0) or None)
|
|
242
|
+
usage = image_usage(
|
|
243
|
+
input_text_tokens=count_text_tokens(prompt, model),
|
|
244
|
+
input_image_tokens=count_image_inputs_tokens(images, model),
|
|
245
|
+
output_tokens=count_image_output_items_tokens(result.get("data")),
|
|
246
|
+
)
|
|
247
|
+
response["usage"] = chat_usage_from_image_usage(usage)
|
|
248
|
+
return response
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def image_chat_events(body: dict[str, Any]) -> Iterator[dict[str, Any]]:
|
|
252
|
+
model, prompt, n, images = chat_image_args(body)
|
|
253
|
+
image_outputs = stream_image_outputs_with_pool(ConversationRequest(
|
|
254
|
+
prompt=prompt,
|
|
255
|
+
model=model,
|
|
256
|
+
n=n,
|
|
257
|
+
response_format="b64_json",
|
|
258
|
+
images=encode_images(images) or None,
|
|
259
|
+
))
|
|
260
|
+
yield from stream_image_chat_completion(image_outputs, model)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def stream_image_chat_completion(image_outputs: Iterable[ImageOutput], model: str) -> Iterator[dict[str, Any]]:
|
|
264
|
+
completion_id = f"chatcmpl-{uuid.uuid4().hex}"
|
|
265
|
+
created = int(time.time())
|
|
266
|
+
sent_role = False
|
|
267
|
+
sent_text = ""
|
|
268
|
+
for output in image_outputs:
|
|
269
|
+
content = ""
|
|
270
|
+
if output.kind == "progress":
|
|
271
|
+
content = output.text
|
|
272
|
+
sent_text += content
|
|
273
|
+
elif output.kind == "result":
|
|
274
|
+
content = build_chat_image_markdown_content({"data": output.data})
|
|
275
|
+
elif output.kind == "message":
|
|
276
|
+
content = output.text[len(sent_text):] if output.text.startswith(sent_text) else output.text
|
|
277
|
+
if not content:
|
|
278
|
+
continue
|
|
279
|
+
if not sent_role:
|
|
280
|
+
sent_role = True
|
|
281
|
+
yield completion_chunk(model, {"role": "assistant", "content": content}, None, completion_id, created)
|
|
282
|
+
else:
|
|
283
|
+
yield completion_chunk(model, {"content": content}, None, completion_id, created)
|
|
284
|
+
if not sent_role:
|
|
285
|
+
yield completion_chunk(model, {"role": "assistant", "content": ""}, None, completion_id, created)
|
|
286
|
+
yield completion_chunk(model, {}, "stop", completion_id, created)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def handle(body: dict[str, Any]) -> dict[str, Any] | Iterator[dict[str, Any]]:
|
|
290
|
+
if body.get("stream"):
|
|
291
|
+
if is_image_chat_request(body):
|
|
292
|
+
return image_chat_events(body)
|
|
293
|
+
model, messages = text_chat_parts(body)
|
|
294
|
+
if is_web_search_chat_request(body) and not has_unsupported_tools(body, WEB_SEARCH_TOOL_TYPES):
|
|
295
|
+
return stream_web_search_chat_completion(messages, model)
|
|
296
|
+
thinking_effort = thinking_effort_from_body(body)
|
|
297
|
+
key = cache_key(body, messages, stream=True)
|
|
298
|
+
return chat_completion_cache.get_or_compute_stream(
|
|
299
|
+
key,
|
|
300
|
+
lambda: stream_text_chat_completion(text_backend(model), messages, model, thinking_effort),
|
|
301
|
+
)
|
|
302
|
+
if is_image_chat_request(body):
|
|
303
|
+
return image_chat_response(body)
|
|
304
|
+
model, messages = text_chat_parts(body)
|
|
305
|
+
if is_web_search_chat_request(body) and not has_unsupported_tools(body, WEB_SEARCH_TOOL_TYPES):
|
|
306
|
+
return web_search_chat_response(messages, model)
|
|
307
|
+
thinking_effort = thinking_effort_from_body(body)
|
|
308
|
+
key = cache_key(body, messages, stream=False)
|
|
309
|
+
return chat_completion_cache.get_or_compute_response(
|
|
310
|
+
key,
|
|
311
|
+
lambda: completion_response(
|
|
312
|
+
model,
|
|
313
|
+
collect_text(text_backend(model), ConversationRequest(model=model, messages=messages, thinking_effort=thinking_effort)),
|
|
314
|
+
messages=messages,
|
|
315
|
+
),
|
|
316
|
+
)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from io import BytesIO
|
|
4
|
+
from typing import Any, Iterator
|
|
5
|
+
|
|
6
|
+
from PIL import Image
|
|
7
|
+
|
|
8
|
+
from services.protocol.conversation import (
|
|
9
|
+
ConversationRequest,
|
|
10
|
+
ImageGenerationError,
|
|
11
|
+
collect_image_outputs,
|
|
12
|
+
count_text_tokens,
|
|
13
|
+
encode_images,
|
|
14
|
+
stream_image_chunks,
|
|
15
|
+
stream_image_outputs_with_pool,
|
|
16
|
+
)
|
|
17
|
+
from utils.image_tokens import count_image_inputs_tokens, count_image_output_items_tokens, image_usage
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _composite_mask(
|
|
21
|
+
images: list[tuple[bytes, str, str]],
|
|
22
|
+
masks: list[tuple[bytes, str, str]],
|
|
23
|
+
) -> list[tuple[bytes, str, str]]:
|
|
24
|
+
"""将 mask 的 alpha 通道合成到图片中,标识需要编辑的区域。
|
|
25
|
+
|
|
26
|
+
mask 的透明区域(低 alpha)= 需要编辑的区域,
|
|
27
|
+
mask 的不透明区域(高 alpha)= 保留的区域。
|
|
28
|
+
如果无 mask 则返回原图。
|
|
29
|
+
"""
|
|
30
|
+
if not masks:
|
|
31
|
+
return images
|
|
32
|
+
result: list[tuple[bytes, str, str]] = []
|
|
33
|
+
for i, (data, filename, mime_type) in enumerate(images):
|
|
34
|
+
mask_data = masks[i][0] if i < len(masks) else masks[-1][0]
|
|
35
|
+
img = Image.open(BytesIO(data)).convert("RGBA")
|
|
36
|
+
mask_img = Image.open(BytesIO(mask_data))
|
|
37
|
+
if mask_img.mode == "RGBA":
|
|
38
|
+
alpha = mask_img.split()[3]
|
|
39
|
+
elif mask_img.mode == "L":
|
|
40
|
+
alpha = mask_img
|
|
41
|
+
else:
|
|
42
|
+
alpha = mask_img.convert("L")
|
|
43
|
+
alpha = alpha.resize(img.size, Image.LANCZOS)
|
|
44
|
+
img.putalpha(alpha)
|
|
45
|
+
buf = BytesIO()
|
|
46
|
+
img.save(buf, format="PNG")
|
|
47
|
+
result.append((buf.getvalue(), filename, "image/png"))
|
|
48
|
+
return result
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def handle(body: dict[str, Any]) -> dict[str, Any] | Iterator[dict[str, Any]]:
|
|
52
|
+
prompt = str(body.get("prompt") or "")
|
|
53
|
+
images = body.get("images") or []
|
|
54
|
+
masks = body.get("mask") or []
|
|
55
|
+
images = _composite_mask(images, masks)
|
|
56
|
+
model = str(body.get("model") or "gpt-image-2")
|
|
57
|
+
n = int(body.get("n") or 1)
|
|
58
|
+
size = body.get("size")
|
|
59
|
+
quality = str(body.get("quality") or "auto")
|
|
60
|
+
response_format = str(body.get("response_format") or "b64_json")
|
|
61
|
+
base_url = str(body.get("base_url") or "") or None
|
|
62
|
+
progress_callback = body.get("progress_callback")
|
|
63
|
+
encoded_images = encode_images(images)
|
|
64
|
+
if not encoded_images:
|
|
65
|
+
raise ImageGenerationError("image is required")
|
|
66
|
+
outputs = stream_image_outputs_with_pool(ConversationRequest(
|
|
67
|
+
prompt=prompt,
|
|
68
|
+
model=model,
|
|
69
|
+
n=n,
|
|
70
|
+
size=size,
|
|
71
|
+
quality=quality,
|
|
72
|
+
response_format=response_format,
|
|
73
|
+
base_url=base_url,
|
|
74
|
+
images=encoded_images,
|
|
75
|
+
message_as_error=True,
|
|
76
|
+
progress_callback=progress_callback,
|
|
77
|
+
))
|
|
78
|
+
if body.get("stream"):
|
|
79
|
+
return stream_image_chunks(outputs)
|
|
80
|
+
result = collect_image_outputs(outputs)
|
|
81
|
+
result["usage"] = image_usage(
|
|
82
|
+
input_text_tokens=count_text_tokens(prompt, model),
|
|
83
|
+
input_image_tokens=count_image_inputs_tokens(images, model),
|
|
84
|
+
output_tokens=count_image_output_items_tokens(result.get("data"), size, quality),
|
|
85
|
+
)
|
|
86
|
+
return result
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Iterator
|
|
4
|
+
|
|
5
|
+
from services.protocol.conversation import (
|
|
6
|
+
ConversationRequest,
|
|
7
|
+
collect_image_outputs,
|
|
8
|
+
count_text_tokens,
|
|
9
|
+
stream_image_chunks,
|
|
10
|
+
stream_image_outputs_with_pool,
|
|
11
|
+
)
|
|
12
|
+
from utils.image_tokens import count_image_output_items_tokens, image_usage
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def handle(body: dict[str, Any]) -> dict[str, Any] | Iterator[dict[str, Any]]:
|
|
16
|
+
prompt = str(body.get("prompt") or "")
|
|
17
|
+
model = str(body.get("model") or "gpt-image-2")
|
|
18
|
+
n = int(body.get("n") or 1)
|
|
19
|
+
size = body.get("size")
|
|
20
|
+
quality = str(body.get("quality") or "auto")
|
|
21
|
+
response_format = str(body.get("response_format") or "b64_json")
|
|
22
|
+
base_url = str(body.get("base_url") or "") or None
|
|
23
|
+
progress_callback = body.get("progress_callback")
|
|
24
|
+
outputs = stream_image_outputs_with_pool(ConversationRequest(
|
|
25
|
+
prompt=prompt,
|
|
26
|
+
model=model,
|
|
27
|
+
n=n,
|
|
28
|
+
size=size,
|
|
29
|
+
quality=quality,
|
|
30
|
+
response_format=response_format,
|
|
31
|
+
base_url=base_url,
|
|
32
|
+
message_as_error=True,
|
|
33
|
+
progress_callback=progress_callback,
|
|
34
|
+
))
|
|
35
|
+
if body.get("stream"):
|
|
36
|
+
return stream_image_chunks(outputs)
|
|
37
|
+
result = collect_image_outputs(outputs)
|
|
38
|
+
result["usage"] = image_usage(
|
|
39
|
+
input_text_tokens=count_text_tokens(prompt, model),
|
|
40
|
+
output_tokens=count_image_output_items_tokens(result.get("data"), size, quality),
|
|
41
|
+
)
|
|
42
|
+
return result
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from services.account_service import account_service
|
|
6
|
+
from services.model_service import model_catalog_service
|
|
7
|
+
from utils.helper import CODEX_IMAGE_MODEL
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def list_models() -> dict[str, Any]:
|
|
11
|
+
result = model_catalog_service.list_models()
|
|
12
|
+
data = result.get("data")
|
|
13
|
+
if not isinstance(data, list):
|
|
14
|
+
return result
|
|
15
|
+
seen = {str(item.get("id") or "").strip() for item in data if isinstance(item, dict)}
|
|
16
|
+
dynamic_models: set[str] = set()
|
|
17
|
+
accounts = account_service.list_accounts()
|
|
18
|
+
web_image_accounts = [
|
|
19
|
+
account
|
|
20
|
+
for account in accounts
|
|
21
|
+
if isinstance(account, dict)
|
|
22
|
+
]
|
|
23
|
+
codex_types = {
|
|
24
|
+
normalized
|
|
25
|
+
for account in accounts
|
|
26
|
+
if isinstance(account, dict)
|
|
27
|
+
and account_service._normalize_source_type(account.get("source_type")) == "codex"
|
|
28
|
+
and (normalized := account_service._normalize_account_type(account.get("type")))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if web_image_accounts:
|
|
32
|
+
dynamic_models.add("gpt-image-2")
|
|
33
|
+
if codex_types & {"Plus", "Team", "Pro"}:
|
|
34
|
+
dynamic_models.add(CODEX_IMAGE_MODEL)
|
|
35
|
+
if "Plus" in codex_types:
|
|
36
|
+
dynamic_models.add(f"plus-{CODEX_IMAGE_MODEL}")
|
|
37
|
+
if "Team" in codex_types:
|
|
38
|
+
dynamic_models.add(f"team-{CODEX_IMAGE_MODEL}")
|
|
39
|
+
if "Pro" in codex_types:
|
|
40
|
+
dynamic_models.add(f"pro-{CODEX_IMAGE_MODEL}")
|
|
41
|
+
|
|
42
|
+
for model in sorted(dynamic_models):
|
|
43
|
+
if model not in seen:
|
|
44
|
+
data.append({
|
|
45
|
+
"id": model,
|
|
46
|
+
"object": "model",
|
|
47
|
+
"created": 0,
|
|
48
|
+
"owned_by": "chatgpt2api",
|
|
49
|
+
"permission": [],
|
|
50
|
+
"root": model,
|
|
51
|
+
"parent": None,
|
|
52
|
+
})
|
|
53
|
+
return result
|