返回提交历史
Modified
docs/pydantic_ai.md
+7
-4
Added
g4f/Provider/ARTA.py
+189
-0
Modified
g4f/Provider/PollinationsAI.py
+46
-16
Modified
g4f/Provider/__init__.py
+1
-0
Modified
g4f/Provider/needs_auth/OpenaiChat.py
+3
-2
Modified
g4f/client/__init__.py
+4
-4
Modified
g4f/gui/client/static/css/style.css
+3
-21
Modified
g4f/gui/client/static/js/chat.v1.js
+65
-10
Modified
g4f/gui/server/api.py
+3
-17
Modified
g4f/gui/server/backend_api.py
+6
-9
Modified
g4f/gui/server/js_api.py
+3
-3
Modified
g4f/providers/base_provider.py
+1
-1
Modified
g4f/providers/tool_support.py
+12
-5
Modified
g4f/tools/run_tools.py
+10
-11
XFEstudio/gpt4free
Add ARTA image provider Add ToolSupport in PollinationsAI provider Add default value for model in chat completions Add Streaming Support for PollinationsAI provider
3e7af909
代码差异
14 个文件
+353
-103
@@ -109,15 +109,18 @@ This example shows how to initialize an agent with a specific model (`gpt-4o`) a
109
109
from pydantic import BaseModel
110
110
from pydantic_ai import Agent
111
111
from pydantic_ai.models import ModelSettings
112
from g4f.integration.pydantic_ai import patch_infer_model
112
from g4f.integration.pydantic_ai import AIModel
113
from g4f.Provider import PollinationsAI
113
114
114
patch_infer_model("your_api_key")
115
115
116
116
class MyModel(BaseModel):
117
117
city: str
118
118
country: str
119
119
120
agent = Agent('g4f:Groq:llama3-70b-8192', result_type=MyModel, model_settings=ModelSettings(temperature=0))
120
nt = Agent(AIModel(
121
"gpt-4o", # Specify the provider and model
122
PollinationsAI # Use a supported provider to handle tool-based response formatting
123
), result_type=MyModel, model_settings=ModelSettings(temperature=0))
121
124
122
125
if __name__ == '__main__':
123
126
result = agent.run_sync('The windy city in the US of A.')
@@ -152,7 +155,7 @@ class MyModel(BaseModel):
152
155
153
156
# Create the agent for a model with tool support (using one tool)
154
157
agent = Agent(AIModel(
155
"PollinationsAI:openai", # Specify the provider and model
158
"OpenaiChat:gpt-4o", # Specify the provider and model
156
159
ToolSupportProvider # Use ToolSupportProvider to handle tool-based response formatting
157
160
), result_type=MyModel, model_settings=ModelSettings(temperature=0))
158
161
@@ -0,0 +1,189 @@
1
from __future__ import annotations
2
3
import os
4
import time
5
import json
6
from pathlib import Path
7
from aiohttp import ClientSession
8
import asyncio
9
10
from ..typing import AsyncResult, Messages
11
from ..providers.response import ImageResponse, Reasoning
12
from ..errors import ResponseError
13
from ..cookies import get_cookies_dir
14
from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
15
from .helper import format_image_prompt
16
17
class ARTA(AsyncGeneratorProvider, ProviderModelMixin):
18
url = "https://img-gen-prod.ai-arta.com"
19
auth_url = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser?key=AIzaSyB3-71wG0fIt0shj0ee4fvx1shcjJHGrrQ"
20
token_refresh_url = "https://securetoken.googleapis.com/v1/token?key=AIzaSyB3-71wG0fIt0shj0ee4fvx1shcjJHGrrQ"
21
image_generation_url = "https://img-gen-prod.ai-arta.com/api/v1/text2image"
22
status_check_url = "https://img-gen-prod.ai-arta.com/api/v1/text2image/{record_id}/status"
23
24
working = True
25
26
default_model = "Flux"
27
default_image_model = default_model
28
model_aliases = {
29
"flux": "Flux",
30
"medieval": "Medieval",
31
"vincent_van_gogh": "Vincent Van Gogh",
32
"f_dev": "F Dev",
33
"low_poly": "Low Poly",
34
"dreamshaper_xl": "Dreamshaper-xl",
35
"anima_pencil_xl": "Anima-pencil-xl",
36
"biomech": "Biomech",
37
"trash_polka": "Trash Polka",
38
"no_style": "No Style",
39
"cheyenne_xl": "Cheyenne-xl",
40
"chicano": "Chicano",
41
"embroidery_tattoo": "Embroidery tattoo",
42
"red_and_black": "Red and Black",
43
"fantasy_art": "Fantasy Art",
44
"watercolor": "Watercolor",
45
"dotwork": "Dotwork",
46
"old_school_colored": "Old school colored",
47
"realistic_tattoo": "Realistic tattoo",
48
"japanese_2": "Japanese_2",
49
"realistic_stock_xl": "Realistic-stock-xl",
50
"f_pro": "F Pro",
51
"revanimated": "RevAnimated",
52
"katayama_mix_xl": "Katayama-mix-xl",
53
"sdxl_l": "SDXL L",
54
"cor_epica_xl": "Cor-epica-xl",
55
"anime_tattoo": "Anime tattoo",
56
"new_school": "New School",
57
"death_metal": "Death metal",
58
"old_school": "Old School",
59
"juggernaut_xl": "Juggernaut-xl",
60
"photographic": "Photographic",
61
"sdxl_1_0": "SDXL 1.0",
62
"graffiti": "Graffiti",
63
"mini_tattoo": "Mini tattoo",
64
"surrealism": "Surrealism",
65
"neo_traditional": "Neo-traditional",
66
"on_limbs_black": "On limbs black",
67
"yamers_realistic_xl": "Yamers-realistic-xl",
68
"pony_xl": "Pony-xl",
69
"playground_xl": "Playground-xl",
70
"anything_xl": "Anything-xl",
71
"flame_design": "Flame design",
72
"kawaii": "Kawaii",
73
"cinematic_art": "Cinematic Art",
74
"professional": "Professional",
75
"flux_black_ink": "Flux Black Ink"
76
}
77
image_models = [*model_aliases.keys()]
78
models = image_models
79
80
@classmethod
81
def get_auth_file(cls):
82
path = Path(get_cookies_dir())
83
path.mkdir(exist_ok=True)
84
filename = f"auth_{cls.__name__}.json"
85
return path / filename
86
87
@classmethod
88
async def create_token(cls, path: Path, proxy: str | None = None):
89
async with ClientSession() as session:
90
# Step 1: Generate Authentication Token
91
auth_payload = {"clientType": "CLIENT_TYPE_ANDROID"}
92
async with session.post(cls.auth_url, json=auth_payload, proxy=proxy) as auth_response:
93
auth_data = await auth_response.json()
94
auth_token = auth_data.get("idToken")
95
#refresh_token = auth_data.get("refreshToken")
96
if not auth_token:
97
raise ResponseError("Failed to obtain authentication token.")
98
json.dump(auth_data, path.open("w"))
99
return auth_data
100
101
@classmethod
102
async def refresh_token(cls, refresh_token: str, proxy: str = None) -> tuple[str, str]:
103
async with ClientSession() as session:
104
payload = {
105
"grant_type": "refresh_token",
106
"refresh_token": refresh_token,
107
}
108
async with session.post(cls.token_refresh_url, data=payload, proxy=proxy) as response:
109
response_data = await response.json()
110
return response_data.get("id_token"), response_data.get("refresh_token")
111
112
@classmethod
113
async def read_and_refresh_token(cls, proxy: str | None = None) -> str:
114
path = cls.get_auth_file()
115
if path.is_file():
116
auth_data = json.load(path.open("rb"))
117
diff = time.time() - os.path.getmtime(path)
118
expiresIn = int(auth_data.get("expiresIn"))
119
if diff < expiresIn:
120
if diff > expiresIn / 2:
121
auth_data["idToken"], auth_data["refreshToken"] = await cls.refresh_token(auth_data.get("refreshToken"), proxy)
122
json.dump(auth_data, path.open("w"))
123
return auth_data
124
return await cls.create_token(path, proxy)
125
126
@classmethod
127
async def create_async_generator(
128
cls,
129
model: str,
130
messages: Messages,
131
proxy: str = None,
132
prompt: str = None,
133
negative_prompt: str = "blurry, deformed hands, ugly",
134
images_num: int = 1,
135
guidance_scale: int = 7,
136
num_inference_steps: int = 30,
137
aspect_ratio: str = "1:1",
138
**kwargs
139
) -> AsyncResult:
140
model = cls.get_model(model)
141
prompt = format_image_prompt(messages, prompt)
142
143
# Step 1: Get Authentication Token
144
auth_data = await cls.read_and_refresh_token(proxy)
145
146
async with ClientSession() as session:
147
# Step 2: Generate Images
148
image_payload = {
149
"prompt": prompt,
150
"negative_prompt": negative_prompt,
151
"style": model,
152
"images_num": str(images_num),
153
"cfg_scale": str(guidance_scale),
154
"steps": str(num_inference_steps),
155
"aspect_ratio": aspect_ratio,
156
}
157
158
headers = {
159
"Authorization": auth_data.get("idToken"),
160
}
161
162
async with session.post(cls.image_generation_url, data=image_payload, headers=headers, proxy=proxy) as image_response:
163
image_data = await image_response.json()
164
record_id = image_data.get("record_id")
165
166
if not record_id:
167
raise ResponseError(f"Failed to initiate image generation: {image_data}")
168
169
# Step 3: Check Generation Status
170
status_url = cls.status_check_url.format(record_id=record_id)
171
counter = 0
172
while True:
173
async with session.get(status_url, headers=headers, proxy=proxy) as status_response:
174
status_data = await status_response.json()
175
status = status_data.get("status")
176
177
if status == "DONE":
178
image_urls = [image["url"] for image in status_data.get("response", [])]
179
yield Reasoning(status="Finished")
180
yield ImageResponse(images=image_urls, alt=prompt)
181
return
182
elif status in ("IN_QUEUE", "IN_PROGRESS"):
183
yield Reasoning(status=("Waiting" if status == "IN_QUEUE" else "Generating") + "." * counter)
184
await asyncio.sleep(5) # Poll every 5 seconds
185
counter += 1
186
if counter > 3:
187
counter = 0
188
else:
189
raise ResponseError(f"Image generation failed with status: {status}")
@@ -1,5 +1,6 @@
1
1
from __future__ import annotations
2
2
3
import json
3
4
import random
4
5
import requests
5
6
from urllib.parse import quote_plus
@@ -13,7 +14,7 @@ from ..image import to_data_uri
13
14
from ..errors import ModelNotFoundError
14
15
from ..requests.raise_for_status import raise_for_status
15
16
from ..requests.aiohttp import get_connector
16
from ..providers.response import ImageResponse, ImagePreview, FinishReason, Usage, Audio
17
from ..providers.response import ImageResponse, ImagePreview, FinishReason, Usage, Audio, ToolCalls
17
18
from .. import debug
18
19
19
20
DEFAULT_HEADERS = {
@@ -52,7 +53,7 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
52
53
text_models = [default_model]
53
54
image_models = [default_image_model]
54
55
extra_image_models = ["flux-pro", "flux-dev", "flux-schnell", "midjourney", "dall-e-3"]
55
vision_models = [default_vision_model, "gpt-4o-mini", "o1-mini"]
56
vision_models = [default_vision_model, "gpt-4o-mini", "o1-mini", "openai", "openai-large"]
56
57
extra_text_models = vision_models
57
58
_models_loaded = False
58
59
model_aliases = {
@@ -138,6 +139,7 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
138
139
cls,
139
140
model: str,
140
141
messages: Messages,
142
stream: bool = False,
141
143
proxy: str = None,
142
144
prompt: str = None,
143
145
width: int = 1024,
@@ -154,6 +156,7 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
154
156
frequency_penalty: float = None,
155
157
response_format: Optional[dict] = None,
156
158
cache: bool = False,
159
extra_parameters: list[str] = ["tools", "parallel_tool_calls", "tool_choice", "reasoning_effort", "logit_bias"],
157
160
**kwargs
158
161
) -> AsyncResult:
159
162
cls.get_models()
@@ -193,6 +196,9 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
193
196
response_format=response_format,
194
197
seed=seed,
195
198
cache=cache,
199
stream=stream,
200
extra_parameters=extra_parameters,
201
**kwargs
196
202
):
197
203
yield result
198
204
@@ -246,7 +252,10 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
246
252
frequency_penalty: float,
247
253
response_format: Optional[dict],
248
254
seed: Optional[int],
249
cache: bool
255
cache: bool,
256
stream: bool,
257
extra_parameters: list[str],
258
**kwargs
250
259
) -> AsyncResult:
251
260
if not cache and seed is None:
252
261
seed = random.randint(9999, 99999999)
@@ -267,6 +276,13 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
267
276
messages[-1] = last_message
268
277
269
278
async with ClientSession(headers=DEFAULT_HEADERS, connector=get_connector(proxy=proxy)) as session:
279
if model in cls.audio_models or stream:
280
#data["voice"] = random.choice(cls.audio_models[model])
281
url = cls.text_api_endpoint
282
stream = False
283
else:
284
url = cls.openai_endpoint
285
extra_parameters = {param: kwargs[param] for param in extra_parameters if param in kwargs}
270
286
data = filter_none(**{
271
287
"messages": messages,
272
288
"model": model,
@@ -275,17 +291,11 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
275
291
"top_p": top_p,
276
292
"frequency_penalty": frequency_penalty,
277
293
"jsonMode": json_mode,
278
"stream": False,
294
"stream": stream,
279
295
"seed": seed,
280
"cache": cache
296
"cache": cache,
297
**extra_parameters
281
298
})
282
if "gemini" in model:
283
data.pop("seed")
284
if model in cls.audio_models:
285
#data["voice"] = random.choice(cls.audio_models[model])
286
url = cls.text_api_endpoint
287
else:
288
url = cls.openai_endpoint
289
299
async with session.post(url, json=data) as response:
290
300
await raise_for_status(response)
291
301
if response.headers["content-type"] == "audio/mpeg":
@@ -294,16 +304,36 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
294
304
elif response.headers["content-type"].startswith("text/plain"):
295
305
yield await response.text()
296
306
return
307
elif response.headers["content-type"].startswith("text/event-stream"):
308
async for line in response.content:
309
if line.startswith(b"data: "):
310
if line[6:].startswith(b"[DONE]"):
311
break
312
result = json.loads(line[6:])
313
choice = result.get("choices", [{}])[0]
314
content = choice.get("delta", {}).get("content")
315
if content:
316
yield content
317
if "usage" in result:
318
yield Usage(**result["usage"])
319
finish_reason = choice.get("finish_reason")
320
if finish_reason:
321
yield FinishReason(finish_reason)
322
return
297
323
result = await response.json()
298
324
choice = result["choices"][0]
299
325
message = choice.get("message", {})
300
326
content = message.get("content", "")
301
327
302
if "</think>" in content and "<think>" not in content:
303
yield "<think>"
328
if "tool_calls" in message:
329
yield ToolCalls(message["tool_calls"])
330
331
if content is not None:
332
if "</think>" in content and "<think>" not in content:
333
yield "<think>"
304
334
305
if content:
306
yield content.replace("\\(", "(").replace("\\)", ")")
335
if content:
336
yield content.replace("\\(", "(").replace("\\)", ")")
307
337
308
338
if "usage" in result:
309
339
yield Usage(**result["usage"])
@@ -15,6 +15,7 @@ from .mini_max import HailuoAI, MiniMax
15
15
from .template import OpenaiTemplate, BackendApi
16
16
17
17
from .AllenAI import AllenAI
18
from .ARTA import ARTA
18
19
from .Blackbox import Blackbox
19
20
from .ChatGLM import ChatGLM
20
21
from .ChatGpt import ChatGpt
@@ -623,8 +623,9 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
623
623
page.add_handler(nodriver.cdp.network.RequestWillBeSent, on_request)
624
624
page = await browser.get(cls.url)
625
625
user_agent = await page.evaluate("window.navigator.userAgent")
626
await page.select("textarea.text-token-text-primary", 240)
627
await page.evaluate("document.querySelector('textarea.text-token-text-primary').value = 'Hello'")
626
await page.select("#prompt-textarea", 240)
627
await page.evaluate("document.getElementById('prompt-textarea').innerText = 'Hello'")
628
await page.select("[data-testid=\"send-button\"]", 30)
628
629
await page.evaluate("document.querySelector('[data-testid=\"send-button\"]').click()")
629
630
while True:
630
631
body = await page.evaluate("JSON.stringify(window.__remixContext)")
@@ -276,7 +276,7 @@ class Completions:
276
276
def create(
277
277
self,
278
278
messages: Messages,
279
model: str,
279
model: str = "",
280
280
provider: Optional[ProviderType] = None,
281
281
stream: Optional[bool] = False,
282
282
proxy: Optional[str] = None,
@@ -330,7 +330,7 @@ class Completions:
330
330
def stream(
331
331
self,
332
332
messages: Messages,
333
model: str,
333
model: str = "",
334
334
**kwargs
335
335
) -> IterResponse:
336
336
return self.create(messages, model, stream=True, **kwargs)
@@ -564,7 +564,7 @@ class AsyncCompletions:
564
564
def create(
565
565
self,
566
566
messages: Messages,
567
model: str,
567
model: str = "",
568
568
provider: Optional[ProviderType] = None,
569
569
stream: Optional[bool] = False,
570
570
proxy: Optional[str] = None,
@@ -619,7 +619,7 @@ class AsyncCompletions:
619
619
def stream(
620
620
self,
621
621
messages: Messages,
622
model: str,
622
model: str = "",
623
623
**kwargs
624
624
) -> AsyncIterator[ChatCompletionChunk]:
625
625
return self.create(messages, model, stream=True, **kwargs)
@@ -112,7 +112,7 @@ body:not(.white) a:visited{
112
112
113
113
.new_version {
114
114
position: absolute;
115
right: 0;
115
left: 0;
116
116
top: 0;
117
117
padding: 10px;
118
118
font-weight: 500;
@@ -143,6 +143,7 @@ body:not(.white) a:visited{
143
143
144
144
.conversation {
145
145
width: 100%;
146
height: 100%;
146
147
display: flex;
147
148
flex-direction: column;
148
149
gap: 5px;
@@ -238,8 +239,7 @@ body:not(.white) a:visited{
238
239
239
240
#close_provider_forms {
240
241
max-width: 210px;
241
margin-left: auto;
242
margin-right: 8px;
242
margin-left: 12px;
243
243
margin-top: 12px;
244
244
}
245
245
@@ -1584,19 +1584,6 @@ form .field.saved .fa-xmark {
1584
1584
}
1585
1585
}
1586
1586
1587
1588
/* Basic adaptation */
1589
.row {
1590
flex-wrap: wrap;
1591
gap: 10px;
1592
}
1593
1594
.conversations, .settings, .conversation {
1595
flex: 1 1 300px;
1596
min-width: 0;
1597
height: 100%;
1598
}
1599
1600
1587
/* Media queries for mobile devices */
1601
1588
@media (max-width: 768px) {
1602
1589
.row {
@@ -1608,11 +1595,6 @@ form .field.saved .fa-xmark {
1608
1595
max-width: 100%;
1609
1596
margin: 0;
1610
1597
}
1611
1612
.conversation {
1613
order: -1;
1614
min-height: 80vh;
1615
}
1616
1598
}
1617
1599
1618
1600
@media (max-width: 480px) {
@@ -259,6 +259,10 @@ function register_message_images() {
259
259
260
260
const register_message_buttons = async () => {
261
261
message_box.querySelectorAll(".message .content .provider").forEach(async (el) => {
262
if (el.dataset.click) {
263
return
264
}
265
el.dataset.click = true;
262
266
const provider_forms = document.querySelector(".provider_forms");
263
267
const provider_form = provider_forms.querySelector(`#${el.dataset.provider}-form`);
264
268
const provider_link = el.querySelector("a");
@@ -279,6 +283,10 @@ const register_message_buttons = async () => {
279
283
});
280
284
281
285
message_box.querySelectorAll(".message .fa-xmark").forEach(async (el) => el.addEventListener("click", async () => {
286
if (el.dataset.click) {
287
return
288
}
289
el.dataset.click = true;
282
290
const message_el = get_message_el(el);
283
291
await remove_message(window.conversation_id, message_el.dataset.index);
284
292
message_el.remove();
@@ -286,6 +294,10 @@ const register_message_buttons = async () => {
286
294
}));
287
295
288
296
message_box.querySelectorAll(".message .fa-clipboard").forEach(async (el) => el.addEventListener("click", async () => {
297
if (el.dataset.click) {
298
return
299
}
300
el.dataset.click = true;
289
301
let message_el = get_message_el(el);
290
302
let response = await fetch(message_el.dataset.object_url);
291
303
let copyText = await response.text();
@@ -304,6 +316,10 @@ const register_message_buttons = async () => {
304
316
}))
305
317
306
318
message_box.querySelectorAll(".message .fa-file-export").forEach(async (el) => el.addEventListener("click", async () => {
319
if (el.dataset.click) {
320
return
321
}
322
el.dataset.click = true;
307
323
const elem = window.document.createElement('a');
308
324
let filename = `chat ${new Date().toLocaleString()}.txt`.replaceAll(":", "-");
309
325
const conversation = await get_conversation(window.conversation_id);
@@ -323,6 +339,10 @@ const register_message_buttons = async () => {
323
339
}))
324
340
325
341
message_box.querySelectorAll(".message .fa-volume-high").forEach(async (el) => el.addEventListener("click", async () => {
342
if (el.dataset.click) {
343
return
344
}
345
el.dataset.click = true;
326
346
const message_el = get_message_el(el);
327
347
let audio;
328
348
if (message_el.dataset.synthesize_url) {
@@ -344,6 +364,10 @@ const register_message_buttons = async () => {
344
364
}));
345
365
346
366
message_box.querySelectorAll(".message .regenerate_button").forEach(async (el) => el.addEventListener("click", async () => {
367
if (el.dataset.click) {
368
return
369
}
370
el.dataset.click = true;
347
371
const message_el = get_message_el(el);
348
372
el.classList.add("clicked");
349
373
setTimeout(() => el.classList.remove("clicked"), 1000);
@@ -351,6 +375,10 @@ const register_message_buttons = async () => {
351
375
}));
352
376
353
377
message_box.querySelectorAll(".message .continue_button").forEach(async (el) => el.addEventListener("click", async () => {
378
if (el.dataset.click) {
379
return
380
}
381
el.dataset.click = true;
354
382
if (!el.disabled) {
355
383
el.disabled = true;
356
384
const message_el = get_message_el(el);
@@ -361,11 +389,19 @@ const register_message_buttons = async () => {
361
389
));
362
390
363
391
message_box.querySelectorAll(".message .fa-whatsapp").forEach(async (el) => el.addEventListener("click", async () => {
392
if (el.dataset.click) {
393
return
394
}
395
el.dataset.click = true;
364
396
const text = get_message_el(el).innerText;
365
397
window.open(`https://wa.me/?text=${encodeURIComponent(text)}`, '_blank');
366
398
}));
367
399
368
400
message_box.querySelectorAll(".message .fa-print").forEach(async (el) => el.addEventListener("click", async () => {
401
if (el.dataset.click) {
402
return
403
}
404
el.dataset.click = true;
369
405
const message_el = get_message_el(el);
370
406
el.classList.add("clicked");
371
407
message_box.scrollTop = 0;
@@ -378,6 +414,10 @@ const register_message_buttons = async () => {
378
414
}));
379
415
380
416
message_box.querySelectorAll(".message .reasoning_title").forEach(async (el) => el.addEventListener("click", async () => {
417
if (el.dataset.click) {
418
return
419
}
420
el.dataset.click = true;
381
421
let text_el = el.parentElement.querySelector(".reasoning_text");
382
422
if (text_el) {
383
423
text_el.classList[text_el.classList.contains("hidden") ? "remove" : "add"]("hidden");
@@ -569,9 +609,9 @@ const prepare_messages = (messages, message_index = -1, do_continue = false, do_
569
609
}
570
610
571
611
// Remove history, only add new user messages
572
let filtered_messages = [];
573
612
// The message_index is null on count total tokens
574
if (document.getElementById('history')?.checked && do_filter && message_index != null) {
613
if (!do_continue && document.getElementById('history')?.checked && do_filter && message_index != null) {
614
let filtered_messages = [];
575
615
while (last_message = messages.pop()) {
576
616
if (last_message["role"] == "user") {
577
617
filtered_messages.push(last_message);
@@ -630,9 +670,9 @@ async function load_provider_parameters(provider) {
630
670
form_el.id = form_id;
631
671
form_el.classList.add("hidden");
632
672
appStorage.setItem(form_el.id, JSON.stringify(parameters_storage[provider]));
633
let old_form = message_box.querySelector(`#${provider}-form`);
673
let old_form = document.getElementById(form_id);
634
674
if (old_form) {
635
provider_forms.removeChild(old_form);
675
old_form.remove();
636
676
}
637
677
Object.entries(parameters_storage[provider]).forEach(([key, value]) => {
638
678
let el_id = `${provider}-${key}`;
@@ -649,7 +689,7 @@ async function load_provider_parameters(provider) {
649
689
saved_value = value;
650
690
}
651
691
field_el.innerHTML = `<span class="label">${key}:</span>
652
<input type="checkbox" id="${el_id}" name="${provider}[${key}]">
692
<input type="checkbox" id="${el_id}" name="${key}">
653
693
<label for="${el_id}" class="toogle" title=""></label>
654
694
<i class="fa-solid fa-xmark"></i>`;
655
695
form_el.appendChild(field_el);
@@ -679,15 +719,15 @@ async function load_provider_parameters(provider) {
679
719
placeholder = value == null ? "null" : value;
680
720
}
681
721
field_el.innerHTML = `<label for="${el_id}" title="">${key}:</label>`;
682
if (Number.isInteger(value) && value != 1) {
683
max = value >= 4096 ? 8192 : 4096;
684
field_el.innerHTML += `<input type="range" id="${el_id}" name="${provider}[${key}]" value="${escapeHtml(value)}" class="slider" min="0" max="${max}" step="1"/><output>${escapeHtml(value)}</output>`;
722
if (Number.isInteger(value)) {
723
max = value == 42 || value >= 4096 ? 8192 : value >= 100 ? 4096 : value == 1 ? 10 : 100;
724
field_el.innerHTML += `<input type="range" id="${el_id}" name="${key}" value="${escapeHtml(value)}" class="slider" min="0" max="${max}" step="1"/><output>${escapeHtml(value)}</output>`;
685
725
field_el.innerHTML += `<i class="fa-solid fa-xmark"></i>`;
686
726
} else if (typeof value == "number") {
687
field_el.innerHTML += `<input type="range" id="${el_id}" name="${provider}[${key}]" value="${escapeHtml(value)}" class="slider" min="0" max="2" step="0.1"/><output>${escapeHtml(value)}</output>`;
727
field_el.innerHTML += `<input type="range" id="${el_id}" name="${key}" value="${escapeHtml(value)}" class="slider" min="0" max="2" step="0.1"/><output>${escapeHtml(value)}</output>`;
688
728
field_el.innerHTML += `<i class="fa-solid fa-xmark"></i>`;
689
729
} else {
690
field_el.innerHTML += `<textarea id="${el_id}" name="${provider}[${key}]"></textarea>`;
730
field_el.innerHTML += `<textarea id="${el_id}" name="${key}"></textarea>`;
691
731
field_el.innerHTML += `<i class="fa-solid fa-xmark"></i>`;
692
732
input_el = field_el.querySelector("textarea");
693
733
if (value != null) {
@@ -723,6 +763,7 @@ async function load_provider_parameters(provider) {
723
763
input_el = field_el.querySelector("input");
724
764
input_el.dataset.value = value;
725
765
input_el.value = saved_value;
766
input_el.nextElementSibling.value = input_el.value;
726
767
input_el.oninput = () => {
727
768
input_el.nextElementSibling.value = input_el.value;
728
769
field_el.classList.add("saved");
@@ -1008,6 +1049,7 @@ const ask_gpt = async (message_id, message_index = -1, regenerate = false, provi
1008
1049
}
1009
1050
await safe_remove_cancel_button();
1010
1051
await register_message_images();
1052
await register_message_buttons();
1011
1053
await load_conversations();
1012
1054
regenerate_button.classList.remove("regenerate-hidden");
1013
1055
}
@@ -1035,6 +1077,18 @@ const ask_gpt = async (message_id, message_index = -1, regenerate = false, provi
1035
1077
}
1036
1078
}
1037
1079
const ignored = Array.from(settings.querySelectorAll("input.provider:not(:checked)")).map((el)=>el.value);
1080
let extra_parameters = {};
1081
document.getElementById(`${provider}-form`)?.querySelectorAll(".saved input, .saved textarea").forEach(async (el) => {
1082
let value = el.type == "checkbox" ? el.checked : el.value;
1083
extra_parameters[el.name] = value;
1084
if (el.type == "textarea") {
1085
try {
1086
extra_parameters[el.name] = await JSON.parse(value);
1087
} catch (e) {
1088
}
1089
}
1090
});
1091
console.log(extra_parameters);
1038
1092
await api("conversation", {
1039
1093
id: message_id,
1040
1094
conversation_id: window.conversation_id,
@@ -1048,6 +1102,7 @@ const ask_gpt = async (message_id, message_index = -1, regenerate = false, provi
1048
1102
api_key: api_key,
1049
1103
api_base: api_base,
1050
1104
ignored: ignored,
1105
...extra_parameters
1051
1106
}, Object.values(image_storage), message_id, scroll, finish_message);
1052
1107
} catch (e) {
1053
1108
console.error(e);
@@ -89,25 +89,17 @@ class Api:
89
89
ensure_images_dir()
90
90
return send_from_directory(os.path.abspath(images_dir), name)
91
91
92
def _prepare_conversation_kwargs(self, json_data: dict, kwargs: dict):
92
def _prepare_conversation_kwargs(self, json_data: dict):
93
kwargs = {**json_data}
93
94
model = json_data.get('model')
94
95
provider = json_data.get('provider')
95
96
messages = json_data.get('messages')
96
api_key = json_data.get("api_key")
97
if api_key:
98
kwargs["api_key"] = api_key
99
api_base = json_data.get("api_base")
100
if api_base:
101
kwargs["api_base"] = api_base
102
97
kwargs["tool_calls"] = [{
103
98
"function": {
104
99
"name": "bucket_tool"
105
100
},
106
101
"type": "function"
107
102
}]
108
web_search = json_data.get('web_search')
109
if web_search:
110
kwargs["web_search"] = web_search
111
103
action = json_data.get('action')
112
104
if action == "continue":
113
105
kwargs["tool_calls"].append({
@@ -117,19 +109,13 @@ class Api:
117
109
"type": "function"
118
110
})
119
111
conversation = json_data.get("conversation")
120
if conversation is not None:
112
if isinstance(conversation, dict):
121
113
kwargs["conversation"] = JsonConversation(**conversation)
122
114
else:
123
115
conversation_id = json_data.get("conversation_id")
124
116
if conversation_id and provider:
125
117
if provider in conversations and conversation_id in conversations[provider]:
126
118
kwargs["conversation"] = conversations[provider][conversation_id]
127
128
if json_data.get("ignored"):
129
kwargs["ignored"] = json_data["ignored"]
130
if json_data.get("action"):
131
kwargs["action"] = json_data["action"]
132
133
119
return {
134
120
"model": model,
135
121
"provider": provider,
@@ -106,17 +106,16 @@ class Backend_Api(Api):
106
106
Returns:
107
107
Response: A Flask response object for streaming.
108
108
"""
109
kwargs = {}
109
if "json" in request.form:
110
json_data = json.loads(request.form['json'])
111
else:
112
json_data = request.json
110
113
if "files" in request.files:
111
114
images = []
112
115
for file in request.files.getlist('files'):
113
116
if file.filename != '' and is_allowed_extension(file.filename):
114
117
images.append((to_image(file.stream, file.filename.endswith('.svg')), file.filename))
115
kwargs['images'] = images
116
if "json" in request.form:
117
json_data = json.loads(request.form['json'])
118
else:
119
json_data = request.json
118
json_data['images'] = images
120
119
121
120
if app.demo and not json_data.get("provider"):
122
121
model = json_data.get("model")
@@ -126,9 +125,7 @@ class Backend_Api(Api):
126
125
if not model or model == "default":
127
126
json_data["model"] = models.demo_models["default"][0].name
128
127
json_data["provider"] = random.choice(models.demo_models["default"][1])
129
if "images" in json_data:
130
kwargs["images"] = json_data["images"]
131
kwargs = self._prepare_conversation_kwargs(json_data, kwargs)
128
kwargs = self._prepare_conversation_kwargs(json_data)
132
129
return self.app.response_class(
133
130
self._create_response_stream(
134
131
kwargs,
@@ -21,12 +21,12 @@ from .api import Api
21
21
22
22
class JsApi(Api):
23
23
24
def get_conversation(self, options: dict, message_id: str = None, scroll: bool = None, **kwargs) -> Iterator:
24
def get_conversation(self, options: dict, message_id: str = None, scroll: bool = None) -> Iterator:
25
25
window = webview.windows[0]
26
26
if hasattr(self, "image") and self.image is not None:
27
kwargs["image"] = open(self.image, "rb")
27
options["image"] = open(self.image, "rb")
28
28
for message in self._create_response_stream(
29
self._prepare_conversation_kwargs(options, kwargs),
29
self._prepare_conversation_kwargs(options),
30
30
options.get("conversation_id"),
31
31
options.get('provider')
32
32
):
@@ -34,7 +34,7 @@ SAFE_PARAMETERS = [
34
34
"api_key", "api_base", "seed", "width", "height",
35
35
"proof_token", "max_retries", "web_search",
36
36
"guidance_scale", "num_inference_steps", "randomize_seed",
37
"safe", "enhance", "private",
37
"safe", "enhance", "private", "aspect_ratio", "images_num",
38
38
]
39
39
40
40
BASIC_PARAMETERS = {