返回提交历史
Added
etc/examples/video.py
+19
-0
Modified
g4f/Provider/hf/HuggingFaceMedia.py
+8
-4
Modified
g4f/Provider/needs_auth/Grok.py
+2
-1
Modified
g4f/Provider/template/OpenaiTemplate.py
+2
-2
Modified
g4f/api/__init__.py
+5
-0
Modified
g4f/client/__init__.py
+15
-9
Modified
g4f/client/image_models.py
+33
-5
Modified
g4f/gui/client/index.html
+6
-3
Modified
g4f/gui/client/qrcode.html
+2
-2
Modified
g4f/gui/client/static/css/style.css
+8
-8
Modified
g4f/gui/client/static/js/chat.v1.js
+30
-9
Modified
g4f/gui/server/api.py
+5
-2
Modified
g4f/gui/server/backend_api.py
+32
-8
Modified
g4f/image/__init__.py
+5
-13
Modified
g4f/image/copy_images.py
+4
-2
Modified
g4f/models.py
+9
-30
Modified
g4f/providers/base_provider.py
+5
-1
Modified
g4f/requests/raise_for_status.py
+16
-12
XFEstudio/gpt4free
Set default model in HuggingFaceMedia Improve handling of shared chats Show api_key input if required
ce500f0d
代码差异
18 个文件
+206
-111
@@ -0,0 +1,19 @@
1
import g4f.Provider
2
from g4f.client import Client
3
4
client = Client(
5
provider=g4f.Provider.HuggingFaceMedia,
6
api_key="hf_***" # Your API key here
7
)
8
9
video_models = client.models.get_video()
10
11
print(video_models)
12
13
result = client.media.generate(
14
model=video_models[0],
15
prompt="G4F AI technology is the best in the world.",
16
response_format="url"
17
)
18
19
print(result.data[0].url)
@@ -66,6 +66,8 @@ class HuggingFaceMedia(AsyncGeneratorProvider, ProviderModelMixin):
66
66
for provider_data in provider_keys:
67
67
prepend_models.append(f"{model}:{provider_data.get('provider')}")
68
68
cls.models = prepend_models + [model for model in new_models if model not in prepend_models]
69
cls.image_models = [model for model, task in cls.task_mapping.items() if task == "text-to-image"]
70
cls.video_models = [model for model, task in cls.task_mapping.items() if task == "text-to-video"]
69
71
else:
70
72
cls.models = []
71
73
return cls.models
@@ -99,12 +101,14 @@ class HuggingFaceMedia(AsyncGeneratorProvider, ProviderModelMixin):
99
101
prompt: str = None,
100
102
proxy: str = None,
101
103
timeout: int = 0,
102
aspect_ratio: str = "1:1",
104
aspect_ratio: str = None,
103
105
**kwargs
104
106
):
105
107
selected_provider = None
106
if ":" in model:
108
if model and ":" in model:
107
109
model, selected_provider = model.split(":", 1)
110
elif not model:
111
model = cls.get_models()[0]
108
112
provider_mapping = await cls.get_mapping(model, api_key)
109
113
headers = {
110
114
'Accept-Encoding': 'gzip, deflate',
@@ -133,11 +137,11 @@ class HuggingFaceMedia(AsyncGeneratorProvider, ProviderModelMixin):
133
137
extra_data = {
134
138
"num_inference_steps": 20,
135
139
"resolution": "480p",
136
"aspect_ratio": aspect_ratio,
140
"aspect_ratio": "16:9" if aspect_ratio is None else aspect_ratio,
137
141
**extra_data
138
142
}
139
143
else:
140
extra_data = use_aspect_ratio(extra_data, aspect_ratio)
144
extra_data = use_aspect_ratio(extra_data, "1:1" if aspect_ratio is None else aspect_ratio)
141
145
if provider_key == "fal-ai":
142
146
url = f"{api_base}/{provider_id}"
143
147
data = {
@@ -30,6 +30,7 @@ class Grok(AsyncAuthedProvider, ProviderModelMixin):
30
30
31
31
default_model = "grok-3"
32
32
models = [default_model, "grok-3-thinking", "grok-2"]
33
model_aliases = {"grok-3-r1": "grok-3-thinking"}
33
34
34
35
@classmethod
35
36
async def on_auth_async(cls, cookies: Cookies = None, proxy: str = None, **kwargs) -> AsyncIterator:
@@ -73,7 +74,7 @@ class Grok(AsyncAuthedProvider, ProviderModelMixin):
73
74
"sendFinalMetadata": True,
74
75
"customInstructions": "",
75
76
"deepsearchPreset": "",
76
"isReasoning": model.endswith("-thinking"),
77
"isReasoning": model.endswith("-thinking") or model.endswith("-r1"),
77
78
}
78
79
79
80
@classmethod
@@ -92,7 +92,7 @@ class OpenaiTemplate(AsyncGeneratorProvider, ProviderModelMixin, RaiseErrorMixin
92
92
}
93
93
async with session.post(f"{api_base.rstrip('/')}/images/generations", json=data, ssl=cls.ssl) as response:
94
94
data = await response.json()
95
cls.raise_error(data)
95
cls.raise_error(data, response.status)
96
96
await raise_for_status(response)
97
97
yield ImageResponse([image["url"] for image in data["data"]], prompt)
98
98
return
@@ -135,7 +135,7 @@ class OpenaiTemplate(AsyncGeneratorProvider, ProviderModelMixin, RaiseErrorMixin
135
135
content_type = response.headers.get("content-type", "text/event-stream" if stream else "application/json")
136
136
if content_type.startswith("application/json"):
137
137
data = await response.json()
138
cls.raise_error(data)
138
cls.raise_error(data, response.status)
139
139
await raise_for_status(response)
140
140
choice = data["choices"][0]
141
141
if "content" in choice["message"] and choice["message"]["content"]:
@@ -10,6 +10,7 @@ from email.utils import formatdate
10
10
import os.path
11
11
import hashlib
12
12
import asyncio
13
from urllib.parse import quote_plus
13
14
from fastapi import FastAPI, Response, Request, UploadFile, Depends
14
15
from fastapi.middleware.wsgi import WSGIMiddleware
15
16
from fastapi.responses import StreamingResponse, RedirectResponse, HTMLResponse, JSONResponse
@@ -562,6 +563,10 @@ class Api:
562
563
})
563
564
async def get_media(filename, request: Request):
564
565
target = os.path.join(images_dir, os.path.basename(filename))
566
if not os.path.isfile(target):
567
other_name = os.path.join(images_dir, os.path.basename(quote_plus(filename)))
568
if os.path.isfile(other_name):
569
target = other_name
565
570
ext = os.path.splitext(filename)[1][1:]
566
571
mime_type = EXTENSIONS_MAP.get(ext)
567
572
stat_result = SimpleNamespace()
@@ -19,7 +19,7 @@ from ..providers.asyncio import to_sync_generator
19
19
from ..Provider.needs_auth import BingCreateImages, OpenaiAccount
20
20
from ..tools.run_tools import async_iter_run_tools, iter_run_tools
21
21
from .stubs import ChatCompletion, ChatCompletionChunk, Image, ImagesResponse, UsageModel, ToolCallModel
22
from .image_models import ImageModels
22
from .image_models import MediaModels
23
23
from .types import IterResponse, ImageProvider, Client as BaseClient
24
24
from .service import get_model_and_provider, convert_to_provider
25
25
from .helper import find_stop, filter_json, filter_none, safe_aclose
@@ -267,8 +267,11 @@ class Client(BaseClient):
267
267
) -> None:
268
268
super().__init__(**kwargs)
269
269
self.chat: Chat = Chat(self, provider)
270
if image_provider is None:
271
image_provider = provider
272
self.models: MediaModels = MediaModels(self, image_provider)
270
273
self.images: Images = Images(self, image_provider)
271
self.media: Images = Images(self, image_provider)
274
self.media: Images = self.images
272
275
273
276
class Completions:
274
277
def __init__(self, client: Client, provider: Optional[ProviderType] = None):
@@ -349,7 +352,6 @@ class Images:
349
352
def __init__(self, client: Client, provider: Optional[ProviderType] = None):
350
353
self.client: Client = client
351
354
self.provider: Optional[ProviderType] = provider
352
self.models: ImageModels = ImageModels(client)
353
355
354
356
def generate(
355
357
self,
@@ -369,7 +371,7 @@ class Images:
369
371
if provider is None:
370
372
provider_handler = self.provider
371
373
if provider_handler is None:
372
provider_handler = self.models.get(model, default)
374
provider_handler = self.client.models.get(model, default)
373
375
elif isinstance(provider, str):
374
376
provider_handler = convert_to_provider(provider)
375
377
else:
@@ -385,19 +387,21 @@ class Images:
385
387
provider: Optional[ProviderType] = None,
386
388
response_format: Optional[str] = None,
387
389
proxy: Optional[str] = None,
390
api_key: Optional[str] = None,
388
391
**kwargs
389
392
) -> ImagesResponse:
390
393
provider_handler = await self.get_provider_handler(model, provider, BingCreateImages)
391
394
provider_name = provider_handler.__name__ if hasattr(provider_handler, "__name__") else type(provider_handler).__name__
392
395
if proxy is None:
393
396
proxy = self.client.proxy
394
397
if api_key is None:
398
api_key = self.client.api_key
395
399
error = None
396
400
response = None
397
401
if isinstance(provider_handler, IterListProvider):
398
402
for provider in provider_handler.providers:
399
403
try:
400
response = await self._generate_image_response(provider, provider.__name__, model, prompt, **kwargs)
404
response = await self._generate_image_response(provider, provider.__name__, model, prompt, proxy=proxy, **kwargs)
401
405
if response is not None:
402
406
provider_name = provider.__name__
403
407
break
@@ -405,7 +409,7 @@ class Images:
405
409
error = e
406
410
debug.error(f"{provider.__name__} {type(e).__name__}: {e}")
407
411
else:
408
response = await self._generate_image_response(provider_handler, provider_name, model, prompt, **kwargs)
412
response = await self._generate_image_response(provider_handler, provider_name, model, prompt, proxy=proxy, api_key=api_key, **kwargs)
409
413
410
414
if isinstance(response, MediaResponse):
411
415
return await self._process_image_response(
@@ -534,7 +538,7 @@ class Images:
534
538
else:
535
539
# Save locally for None (default) case
536
540
images = await copy_media(response.get_list(), response.get("cookies"), proxy)
537
images = [Image.model_construct(url=f"/media/{os.path.basename(image)}", revised_prompt=response.alt) for image in images]
541
images = [Image.model_construct(url=image, revised_prompt=response.alt) for image in images]
538
542
539
543
return ImagesResponse.model_construct(
540
544
created=int(time.time()),
@@ -552,6 +556,9 @@ class AsyncClient(BaseClient):
552
556
) -> None:
553
557
super().__init__(**kwargs)
554
558
self.chat: AsyncChat = AsyncChat(self, provider)
559
if image_provider is None:
560
image_provider = provider
561
self.models: MediaModels = MediaModels(self, image_provider)
555
562
self.images: AsyncImages = AsyncImages(self, image_provider)
556
563
self.media: AsyncImages = self.images
557
564
@@ -635,7 +642,6 @@ class AsyncImages(Images):
635
642
def __init__(self, client: AsyncClient, provider: Optional[ProviderType] = None):
636
643
self.client: AsyncClient = client
637
644
self.provider: Optional[ProviderType] = provider
638
self.models: ImageModels = ImageModels(client)
639
645
640
646
async def generate(
641
647
self,
@@ -1,15 +1,43 @@
1
1
from __future__ import annotations
2
2
3
from ..models import ModelUtils
3
from ..models import ModelUtils, ImageModel
4
4
from ..Provider import ProviderUtils
5
from ..providers.types import ProviderType
5
6
6
class ImageModels():
7
def __init__(self, client):
7
class MediaModels():
8
def __init__(self, client, provider: ProviderType = None):
8
9
self.client = client
10
self.provider = provider
9
11
10
def get(self, name, default=None):
12
def get(self, name, default=None) -> ProviderType:
11
13
if name in ModelUtils.convert:
12
14
return ModelUtils.convert[name].best_provider
13
15
if name in ProviderUtils.convert:
14
16
return ProviderUtils.convert[name]
15
return default
17
return default
18
19
def get_all(self, api_key: str = None, **kwargs) -> list[str]:
20
if self.provider is None:
21
return []
22
if api_key is None:
23
api_key = self.client.api_key
24
return self.provider.get_models(
25
**kwargs,
26
**{} if api_key is None else {"api_key": api_key}
27
)
28
29
def get_image(self, **kwargs) -> list[str]:
30
if self.provider is None:
31
return [model_id for model_id, model in ModelUtils.convert.items() if isinstance(model, ImageModel)]
32
self.get_all(**kwargs)
33
if hasattr(self.provider, "image_models"):
34
return self.provider.image_models
35
return []
36
37
def get_video(self, **kwargs) -> list[str]:
38
if self.provider is None:
39
return []
40
self.get_all(**kwargs)
41
if hasattr(self.provider, "video_models"):
42
return self.provider.video_models
43
return []
@@ -61,9 +61,12 @@
61
61
const gpt_image = '<img src="/static/img/gpt.png" alt="your avatar">';
62
62
</script>
63
63
<script src="/static/js/highlight.min.js" async></script>
64
65
<script>window.conversation_id = "{{conversation_id}}"</script>
66
<script>window.chat_id = "{{chat_id}}"; window.share_url = "{{share_url}}";</script>
64
<script>
65
window.conversation_id = "{{conversation_id}}";
66
window.chat_id = "{{chat_id}}";
67
window.share_url = "{{share_url}}";
68
window.start_id = "{{conversation_id}}";
69
</script>
67
70
<title>G4F Chat</title>
68
71
</head>
69
72
<body>
@@ -7,9 +7,9 @@
7
7
<script src="https://cdn.jsdelivr.net/npm/qrcodejs/qrcode.min.js"></script>
8
8
<style>
9
9
body { font-family: Arial, sans-serif; text-align: center; margin: 20px; }
10
video { width: 400px; height: 400px; border: 1px solid black; display: block; margin: auto; object-fit: cover;}
10
video { width: 400px; height: 400px; border: 1px solid black; display: block; margin: auto; object-fit: cover; max-width: 100%;}
11
11
#qrcode { margin-top: 20px; }
12
#qrcode img, #qrcode canvas { margin: 0 auto; width: 400px; height: 400px; }
12
#qrcode img, #qrcode canvas { margin: 0 auto; width: 400px; height: 400px; max-width: 100%;}
13
13
button { margin: 5px; padding: 10px; }
14
14
</style>
15
15
</head>
@@ -881,7 +881,7 @@ input.model:hover
881
881
padding: var(--inner-gap) 28px;
882
882
}
883
883
884
#systemPrompt, #chatPrompt, .settings textarea, form textarea {
884
#systemPrompt, #chatPrompt, .settings textarea, form textarea, .chat-body textarea {
885
885
font-size: 15px;
886
886
color: var(--colour-3);
887
887
outline: none;
@@ -1305,7 +1305,7 @@ form textarea {
1305
1305
padding: 0;
1306
1306
}
1307
1307
1308
.settings textarea {
1308
.settings textarea, .chat-body textarea {
1309
1309
height: 30px;
1310
1310
min-height: 30px;
1311
1311
padding: 6px;
@@ -1315,7 +1315,7 @@ form textarea {
1315
1315
text-wrap: nowrap;
1316
1316
}
1317
1317
1318
form .field .fa-xmark {
1318
.field .fa-xmark {
1319
1319
line-height: 20px;
1320
1320
cursor: pointer;
1321
1321
margin-left: auto;
@@ -1323,11 +1323,11 @@ form .field .fa-xmark {
1323
1323
margin-top: 0;
1324
1324
}
1325
1325
1326
form .field.saved .fa-xmark {
1326
.field.saved .fa-xmark {
1327
1327
color: var(--accent)
1328
1328
}
1329
1329
1330
.settings .field, form .field {
1330
.settings .field, form .field, .chat-body .field {
1331
1331
padding: var(--inner-gap) var(--inner-gap) var(--inner-gap) 0;
1332
1332
}
1333
1333
@@ -1359,7 +1359,7 @@ form .field.saved .fa-xmark {
1359
1359
border: none;
1360
1360
}
1361
1361
1362
.settings input, form input {
1362
.settings input, form input, .chat-body input {
1363
1363
background-color: transparent;
1364
1364
padding: 2px;
1365
1365
border: none;
@@ -1368,11 +1368,11 @@ form .field.saved .fa-xmark {
1368
1368
color: var(--colour-3);
1369
1369
}
1370
1370
1371
.settings input:focus, form input:focus {
1371
.settings input:focus, form input:focus, .chat-body input:focus {
1372
1372
outline: none;
1373
1373
}
1374
1374
1375
.settings .label, form .label, .settings label, form label {
1375
.settings .label, form .label, .settings label, form label, .chat-body label {
1376
1376
font-size: 15px;
1377
1377
margin-left: var(--inner-gap);
1378
1378
}
@@ -28,7 +28,7 @@ const switchInput = document.getElementById("switch");
28
28
const searchButton = document.getElementById("search");
29
29
const paperclip = document.querySelector(".user-input .fa-paperclip");
30
30
31
const optionElementsSelector = ".settings input, .settings textarea, #model, #model2, #provider";
31
const optionElementsSelector = ".settings input, .settings textarea, .chat-body input, #model, #model2, #provider";
32
32
33
33
let provider_storage = {};
34
34
let message_storage = {};
@@ -153,7 +153,7 @@ const iframe_close = Object.assign(document.createElement("button"), {
153
153
});
154
154
iframe_close.onclick = () => iframe_container.classList.add("hidden");
155
155
iframe_container.appendChild(iframe_close);
156
chat.appendChild(iframe_container);
156
document.body.appendChild(iframe_container);
157
157
158
158
class HtmlRenderPlugin {
159
159
constructor(options = {}) {
@@ -843,6 +843,16 @@ async function add_message_chunk(message, message_id, provider, scroll, finish_m
843
843
conversation.data[key] = value;
844
844
}
845
845
await save_conversation(conversation_id, conversation);
846
} else if (message.type == "auth") {
847
error_storage[message_id] = message.message
848
content_map.inner.innerHTML += markdown_render(`**An error occured:** ${message.message}`);
849
let provider = provider_storage[message_id]?.name;
850
let configEl = document.querySelector(`.settings .${provider}-api_key`);
851
if (configEl) {
852
configEl = configEl.parentElement.cloneNode(true);
853
content_map.content.appendChild(configEl);
854
await register_settings_storage();
855
}
846
856
} else if (message.type == "provider") {
847
857
provider_storage[message_id] = message.provider;
848
858
let provider_el = content_map.content.querySelector('.provider');
@@ -1122,10 +1132,6 @@ const ask_gpt = async (message_id, message_index = -1, regenerate = false, provi
1122
1132
let api_key;
1123
1133
if (is_demo && !provider) {
1124
1134
api_key = localStorage.getItem("HuggingFace-api_key");
1125
if (!api_key) {
1126
location.href = "/";
1127
return;
1128
}
1129
1135
} else {
1130
1136
api_key = get_api_key_by_provider(provider);
1131
1137
}
@@ -1221,6 +1227,7 @@ function sanitize(input, replacement) {
1221
1227
}
1222
1228
1223
1229
async function set_conversation_title(conversation_id, title) {
1230
window.chat_id = null;
1224
1231
conversation = await get_conversation(conversation_id)
1225
1232
conversation.new_title = title;
1226
1233
const new_id = sanitize(title, " ");
@@ -1742,12 +1749,22 @@ const load_conversations = async () => {
1742
1749
1743
1750
let html = [];
1744
1751
conversations.forEach((conversation) => {
1752
// const length = conversation.items.map((item) => (
1753
// !item.content.toLowerCase().includes("hello") &&
1754
// !item.content.toLowerCase().includes("hi") &&
1755
// item.content
1756
// ) ? 1 : 0).reduce((a,b)=>a+b, 0);
1757
// if (!length) {
1758
// appStorage.removeItem(`conversation:${conversation.id}`);
1759
// return;
1760
// }
1761
const shareIcon = (conversation.id == window.start_id && window.chat_id) ? '<i class="fa-solid fa-qrcode"></i>': '';
1745
1762
html.push(`
1746
1763
<div class="convo" id="convo-${conversation.id}">
1747
1764
<div class="left" onclick="set_conversation('${conversation.id}')">
1748
1765
<i class="fa-regular fa-comments"></i>
1749
1766
<span class="datetime">${conversation.updated ? toLocaleDateString(conversation.updated) : ""}</span>
1750
<span class="convo-title">${escapeHtml(conversation.new_title ? conversation.new_title : conversation.title)}</span>
1767
<span class="convo-title">${shareIcon} ${escapeHtml(conversation.new_title ? conversation.new_title : conversation.title)}</span>
1751
1768
</div>
1752
1769
<i onclick="show_option('${conversation.id}')" class="fa-solid fa-ellipsis-vertical" id="conv-${conversation.id}"></i>
1753
1770
<div id="cho-${conversation.id}" class="choise" style="display:none;">
@@ -2060,7 +2077,6 @@ window.addEventListener('load', async function() {
2060
2077
if (!window.conversation_id) {
2061
2078
window.conversation_id = window.chat_id;
2062
2079
}
2063
window.start_id = window.conversation_id
2064
2080
const response = await fetch(`${window.share_url}/backend-api/v2/chat/${window.chat_id ? window.chat_id : window.conversation_id}`, {
2065
2081
headers: {'accept': 'application/json'},
2066
2082
});
@@ -2075,6 +2091,7 @@ window.addEventListener('load', async function() {
2075
2091
`conversation:${conversation.id}`,
2076
2092
JSON.stringify(conversation)
2077
2093
);
2094
await load_conversations();
2078
2095
let refreshOnHide = true;
2079
2096
document.addEventListener("visibilitychange", () => {
2080
2097
if (document.hidden) {
@@ -2091,6 +2108,9 @@ window.addEventListener('load', async function() {
2091
2108
if (!refreshOnHide) {
2092
2109
return;
2093
2110
}
2111
if (window.conversation_id != window.start_id) {
2112
return;
2113
}
2094
2114
const response = await fetch(`${window.share_url}/backend-api/v2/chat/${window.chat_id}`, {
2095
2115
headers: {'accept': 'application/json', 'if-none-match': conversation.updated},
2096
2116
});
@@ -2102,6 +2122,7 @@ window.addEventListener('load', async function() {
2102
2122
`conversation:${conversation.id}`,
2103
2123
JSON.stringify(conversation)
2104
2124
);
2125
await load_conversations();
2105
2126
await load_conversation(conversation);
2106
2127
}
2107
2128
}
@@ -2284,7 +2305,7 @@ async function on_api() {
2284
2305
}
2285
2306
} else if (provider.login_url) {
2286
2307
if (!login_urls[provider.name]) {
2287
login_urls[provider.name] = [provider.label, provider.login_url, [], provider.auth];
2308
login_urls[provider.name] = [provider.label, provider.login_url, [provider.name], provider.auth];
2288
2309
} else {
2289
2310
login_urls[provider.name][0] = provider.label;
2290
2311
login_urls[provider.name][1] = provider.login_url;
@@ -7,7 +7,7 @@ from typing import Iterator
7
7
from flask import send_from_directory
8
8
from inspect import signature
9
9
10
from ...errors import VersionNotFoundError
10
from ...errors import VersionNotFoundError, MissingAuthError
11
11
from ...image.copy_images import copy_media, ensure_images_dir, images_dir
12
12
from ...tools.run_tools import iter_run_tools
13
13
from ...Provider import ProviderUtils, __providers__
@@ -187,7 +187,8 @@ class Api:
187
187
media = chunk
188
188
if download_media or chunk.get("cookies"):
189
189
chunk.alt = format_image_prompt(kwargs.get("messages"), chunk.alt)
190
media = asyncio.run(copy_media(chunk.get_list(), chunk.get("cookies"), chunk.get("headers"), proxy=proxy, alt=chunk.alt))
190
tags = [tag for tag in [model, kwargs.get("aspect_ratio")] if tag]
191
media = asyncio.run(copy_media(chunk.get_list(), chunk.get("cookies"), chunk.get("headers"), proxy=proxy, alt=chunk.alt, tags=tags))
191
192
media = ImageResponse(media, chunk.alt) if isinstance(chunk, ImageResponse) else VideoResponse(media, chunk.alt)
192
193
yield self._format_json("content", str(media), images=chunk.get_list(), alt=chunk.alt)
193
194
elif isinstance(chunk, SynthesizeData):
@@ -214,6 +215,8 @@ class Api:
214
215
yield self._format_json(chunk.type, **chunk.get_dict())
215
216
else:
216
217
yield self._format_json("content", str(chunk))
218
except MissingAuthError as e:
219
yield self._format_json('auth', type(e).__name__, message=get_error_message(e))
217
220
except Exception as e:
218
221
logger.exception(e)
219
222
debug.error(e)