返回提交历史
Modified
etc/tool/create_provider.py
+1
-1
Modified
etc/unittest/__main__.py
+1
-0
Modified
etc/unittest/model.py
+4
-4
Added
etc/unittest/models.py
+23
-0
Modified
g4f/Provider/Airforce.py
+1
-1
Modified
g4f/Provider/Blackbox.py
+6
-15
Modified
g4f/Provider/Flux.py
+1
-1
Modified
g4f/Provider/RobocodersAPI.py
+2
-1
Modified
g4f/Provider/needs_auth/CopilotAccount.py
+4
-1
Modified
g4f/Provider/needs_auth/MetaAI.py
+1
-1
Modified
g4f/gui/client/index.html
+8
-18
Modified
g4f/gui/client/static/js/chat.v1.js
+5
-44
Modified
g4f/gui/client/static/js/highlightjs-copy.min.js
+0
-4
Deleted
g4f/gui/server/android_gallery.py
+0
-67
Deleted
g4f/gui/server/js_api.py
+0
-94
Modified
g4f/gui/webview.py
+0
-2
Modified
g4f/image.py
+1
-2
Modified
g4f/models.py
+5
-37
XFEstudio/gpt4free
Remove webview js api, Add unittest for provider has model, Use cooki… (#2470)
* Remove webview js api, Add unittest for provider has model, Use cookies dir for cache
76c36834
代码差异
18 个文件
+63
-293
@@ -113,7 +113,7 @@ And replace "gpt-3.5-turbo" with `model`.
113
113
print("Create code...")
114
114
response = []
115
115
for chunk in g4f.ChatCompletion.create(
116
model=g4f.models.default,
116
model=g4f.models.gpt_4o,
117
117
messages=[{"role": "user", "content": prompt}],
118
118
timeout=300,
119
119
stream=True,
@@ -8,5 +8,6 @@ from .client import *
8
8
from .image_client import *
9
9
from .include import *
10
10
from .retry_provider import *
11
from .models import *
11
12
12
13
unittest.main()
@@ -4,24 +4,24 @@ from g4f import ChatCompletion
4
4
from .mocks import ModelProviderMock
5
5
6
6
DEFAULT_MESSAGES = [{'role': 'user', 'content': 'Hello'}]
7
7
8
8
test_model = g4f.models.Model(
9
9
name = "test/test_model",
10
10
base_provider = "",
11
11
best_provider = ModelProviderMock
12
12
)
13
13
g4f.models.ModelUtils.convert["test_model"] = test_model
14
14
15
15
class TestPassModel(unittest.TestCase):
16
16
17
17
def test_model_instance(self):
18
18
response = ChatCompletion.create(test_model, DEFAULT_MESSAGES)
19
19
self.assertEqual(test_model.name, response)
20
20
21
21
def test_model_name(self):
22
22
response = ChatCompletion.create("test_model", DEFAULT_MESSAGES)
23
23
self.assertEqual(test_model.name, response)
24
24
25
25
def test_model_pass(self):
26
26
response = ChatCompletion.create("test/test_model", DEFAULT_MESSAGES, ModelProviderMock)
27
27
self.assertEqual(test_model.name, response)
@@ -0,0 +1,23 @@
1
import unittest
2
from typing import Type
3
import asyncio
4
5
from g4f.models import __models__
6
from g4f.providers.base_provider import BaseProvider, ProviderModelMixin
7
from g4f.models import Model
8
9
class TestProviderHasModel(unittest.IsolatedAsyncioTestCase):
10
cache: dict = {}
11
12
async def test_provider_has_model(self):
13
for model, providers in __models__.values():
14
for provider in providers:
15
if issubclass(provider, ProviderModelMixin):
16
if model.name not in provider.model_aliases:
17
await asyncio.wait_for(self.provider_has_model(provider, model), 10)
18
19
async def provider_has_model(self, provider: Type[BaseProvider], model: Model):
20
if provider.__name__ not in self.cache:
21
self.cache[provider.__name__] = provider.get_models()
22
if self.cache[provider.__name__]:
23
self.assertIn(model.name, self.cache[provider.__name__], provider.__name__)
@@ -60,6 +60,7 @@ class Airforce(AsyncGeneratorProvider, ProviderModelMixin):
60
60
"evil": "any-uncensored",
61
61
"sdxl": "stable-diffusion-xl-base",
62
62
"flux-pro": "flux-1.1-pro",
63
"llama-3.1-8b": "llama-3.1-8b-chat"
63
64
}
64
65
65
66
@classmethod
@@ -85,7 +86,6 @@ class Airforce(AsyncGeneratorProvider, ProviderModelMixin):
85
86
cls.models = [model for model in cls.models if model not in cls.hidden_models]
86
87
except Exception as e:
87
88
debug.log(f"Error fetching text models: {e}")
88
cls.models = [cls.default_model]
89
89
90
90
return cls.models
91
91
@@ -7,21 +7,20 @@ import json
7
7
import re
8
8
import aiohttp
9
9
10
import os
11
10
import json
12
11
from pathlib import Path
13
12
14
13
from ..typing import AsyncResult, Messages, ImageType
15
14
from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
16
15
from ..image import ImageResponse, to_data_uri
17
16
from ..cookies import get_cookies_dir
18
17
from .helper import format_prompt
19
18
20
19
class Blackbox(AsyncGeneratorProvider, ProviderModelMixin):
21
20
label = "Blackbox AI"
22
21
url = "https://www.blackbox.ai"
23
22
api_endpoint = "https://www.blackbox.ai/api/chat"
24
23
25
24
working = True
26
25
supports_stream = True
27
26
supports_system_message = True
@@ -38,7 +37,7 @@ class Blackbox(AsyncGeneratorProvider, ProviderModelMixin):
38
37
agentMode = {
39
38
'ImageGeneration': {'mode': True, 'id': "ImageGenerationLV45LJp", 'name': "Image Generation"}
40
39
}
41
40
42
41
trendingAgentMode = {
43
42
"gemini-1.5-flash": {'mode': True, 'id': 'Gemini'},
44
43
"llama-3.1-8b": {'mode': True, 'id': "llama-3.1-8b"},
@@ -108,19 +107,11 @@ class Blackbox(AsyncGeneratorProvider, ProviderModelMixin):
108
107
"flux": "ImageGeneration",
109
108
}
110
109
111
@classmethod
112
def _get_cache_dir(cls) -> Path:
113
# Get the path to the current file
114
current_file = Path(__file__)
115
# Create the path to the .cache directory
116
cache_dir = current_file.parent / '.cache'
117
# Create a directory if it does not exist
118
cache_dir.mkdir(exist_ok=True)
119
return cache_dir
120
121
110
@classmethod
122
111
def _get_cache_file(cls) -> Path:
123
return cls._get_cache_dir() / 'blackbox.json'
112
dir = Path(get_cookies_dir())
113
dir.mkdir(exist_ok=True)
114
return dir / 'blackbox.json'
124
115
125
116
@classmethod
126
117
def _load_cached_value(cls) -> str | None:
@@ -12,7 +12,7 @@ class Flux(AsyncGeneratorProvider, ProviderModelMixin):
12
12
url = "https://black-forest-labs-flux-1-dev.hf.space"
13
13
api_endpoint = "/gradio_api/call/infer"
14
14
working = True
15
default_model = 'flux-1-dev'
15
default_model = 'flux-dev'
16
16
models = [default_model]
17
17
image_models = [default_model]
18
18
@@ -14,6 +14,7 @@ except ImportError:
14
14
from aiohttp import ClientTimeout
15
15
from ..errors import MissingRequirementsError
16
16
from ..typing import AsyncResult, Messages
17
from ..cookies import get_cookies_dir
17
18
from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
18
19
from .helper import format_prompt
19
20
@@ -30,7 +31,7 @@ class RobocodersAPI(AsyncGeneratorProvider, ProviderModelMixin):
30
31
agent = [default_model, "RepoAgent", "FrontEndAgent"]
31
32
models = [*agent]
32
33
33
CACHE_DIR = Path(__file__).parent / ".cache"
34
CACHE_DIR = Path(get_cookies_dir())
34
35
CACHE_FILE = CACHE_DIR / "robocoders.json"
35
36
36
37
@classmethod
@@ -9,4 +9,7 @@ class CopilotAccount(Copilot, ProviderModelMixin):
9
9
default_model = "Copilot"
10
10
default_vision_model = default_model
11
11
models = [default_model]
12
image_models = models
12
image_models = models
13
model_aliases = {
14
"dall-e-3": default_model
15
}
@@ -29,7 +29,7 @@ class MetaAI(AsyncGeneratorProvider, ProviderModelMixin):
29
29
label = "Meta AI"
30
30
url = "https://www.meta.ai"
31
31
working = True
32
default_model = ''
32
default_model = 'meta-ai'
33
33
34
34
def __init__(self, proxy: str = None, connector: BaseConnector = None):
35
35
self.session = ClientSession(connector=get_connector(connector, proxy), headers=DEFAULT_HEADERS)
@@ -1,6 +1,5 @@
1
1
<!DOCTYPE html>
2
2
<html lang="en" data-framework="javascript">
3
4
3
<head>
5
4
<meta charset="UTF-8">
6
5
<meta http-equiv="X-UA-Compatible" content="IE=edge">
@@ -62,9 +61,8 @@
62
61
onInit: (el, pswp) => {
63
62
lightbox.pswp.on('change', () => {
64
63
const currSlideElement = lightbox.pswp.currSlide.data.element;
65
let captionHTML = '';
66
64
if (currSlideElement) {
67
el.innerHTML = currSlideElement.querySelector('img').getAttribute('alt');
65
el.innerText = currSlideElement.querySelector('img').getAttribute('alt');
68
66
}
69
67
});
70
68
}
@@ -80,7 +78,6 @@
80
78
<script>window.conversation_id = "{{chat_id}}"</script>
81
79
<title>g4f - gui</title>
82
80
</head>
83
84
81
<body>
85
82
<div class="gradient"></div>
86
83
<div class="row">
@@ -92,12 +89,6 @@
92
89
</button>
93
90
</div>
94
91
<div class="bottom_buttons">
95
<!--
96
<button onclick="open_album();">
97
<i class="fa-solid fa-toolbox"></i>
98
<span>Images Album</span>
99
</button>
100
-->
101
92
<button onclick="open_settings();">
102
93
<i class="fa-solid fa-toolbox"></i>
103
94
<span>Open Settings</span>
@@ -118,8 +109,6 @@
118
109
</div>
119
110
</div>
120
111
</div>
121
<div class="images hidden">
122
</div>
123
112
<div class="settings hidden">
124
113
<div class="paper">
125
114
<h3>Settings</h3>
@@ -151,14 +140,14 @@
151
140
<div class="field">
152
141
<span class="label">Auto continue in ChatGPT</span>
153
142
<input id="auto_continue" type="checkbox" name="auto_continue" checked/>
154
<label for="auto_continue" class="toogle" title="Continue large responses in OpenaiChat"></label>
143
<label for="auto_continue" class="toogle" title="Continue large responses in OpenAI ChatGPT"></label>
155
144
</div>
156
145
<div class="field box">
157
146
<label for="message-input-height" class="label" title="">Input max. height</label>
158
147
<input type="number" id="message-input-height" value="200"/>
159
148
</div>
160
149
<div class="field box">
161
<label for="recognition-language" class="label" title="">Speech recognition lang</label>
150
<label for="recognition-language" class="label" title="">Speech recognition language</label>
162
151
<input type="text" id="recognition-language" value="" placeholder="navigator.language"/>
163
152
</div>
164
153
<div class="field box">
@@ -250,7 +239,7 @@
250
239
<div class="box input-box">
251
240
<textarea id="message-input" placeholder="Ask a question" cols="30" rows="10"
252
241
style="white-space: pre-wrap;resize: none;"></textarea>
253
<label class="file-label image-label" for="image" title="Works with Bing, Gemini, OpenaiChat and You">
242
<label class="file-label image-label" for="image" title="">
254
243
<input type="file" id="image" name="image" accept="image/*" required/>
255
244
<i class="fa-regular fa-image"></i>
256
245
</label>
@@ -278,12 +267,13 @@
278
267
<option value="gpt-4o">gpt-4o</option>
279
268
<option value="gpt-4o-mini">gpt-4o-mini</option>
280
269
<option value="llama-3.1-70b">llama-3.1-70b</option>
281
<option value="llama-3.1-70b">llama-3.1-405b</option>
282
<option value="llama-3.1-70b">mixtral-8x7b</option>
270
<option value="llama-3.1-405b">llama-3.1-405b</option>
271
<option value="mixtral-8x7b">mixtral-8x7b</option>
283
272
<option value="gemini-pro">gemini-pro</option>
284
273
<option value="gemini-flash">gemini-flash</option>
285
<option value="claude-3-haiku">claude-3-haiku</option>
286
274
<option value="claude-3.5-sonnet">claude-3.5-sonnet</option>
275
<option value="flux">flux (Image Generation)</option>
276
<option value="dall-e-3">dall-e-3 (Image Generation)</option>
287
277
<option disabled="disabled">----</option>
288
278
</select>
289
279
<select name="model2" id="model2" class="hidden"></select>
@@ -333,7 +333,7 @@ const handle_ask = async () => {
333
333
}
334
334
messageInput.value = "";
335
335
await count_input()
336
await add_conversation(window.conversation_id, message);
336
await add_conversation(window.conversation_id);
337
337
338
338
if ("text" in fileInput.dataset) {
339
339
message += '\n```' + fileInput.dataset.type + '\n';
@@ -544,20 +544,6 @@ async function add_message_chunk(message, message_id) {
544
544
}
545
545
}
546
546
547
cameraInput?.addEventListener("click", (e) => {
548
if (window?.pywebview) {
549
e.preventDefault();
550
pywebview.api.take_picture();
551
}
552
});
553
554
imageInput?.addEventListener("click", (e) => {
555
if (window?.pywebview) {
556
e.preventDefault();
557
pywebview.api.choose_image();
558
}
559
});
560
561
547
const ask_gpt = async (message_id, message_index = -1, regenerate = false, provider = null, model = null) => {
562
548
if (!model && !provider) {
563
549
model = get_selected_model()?.value || null;
@@ -861,7 +847,7 @@ const load_conversation = async (conversation_id, scroll=true) => {
861
847
if (window.GPTTokenizer_cl100k_base) {
862
848
const filtered = prepare_messages(messages, null);
863
849
if (filtered.length > 0) {
864
last_model = last_model?.startsWith("gpt-4") ? "gpt-4" : "gpt-3.5-turbo"
850
last_model = last_model?.startsWith("gpt-3") ? "gpt-3.5-turbo" : "gpt-4"
865
851
let count_total = GPTTokenizer_cl100k_base?.encodeChat(filtered, last_model).length
866
852
if (count_total > 0) {
867
853
elements += `<div class="count_total">(${count_total} tokens used)</div>`;
@@ -916,7 +902,7 @@ async function get_messages(conversation_id) {
916
902
return conversation?.items || [];
917
903
}
918
904
919
async function add_conversation(conversation_id, content) {
905
async function add_conversation(conversation_id) {
920
906
if (appStorage.getItem(`conversation:${conversation_id}`) == null) {
921
907
await save_conversation(conversation_id, {
922
908
id: conversation_id,
@@ -1134,17 +1120,6 @@ function open_settings() {
1134
1120
log_storage.classList.add("hidden");
1135
1121
}
1136
1122
1137
function open_album() {
1138
if (album.classList.contains("hidden")) {
1139
sidebar.classList.remove("shown");
1140
settings.classList.add("hidden");
1141
album.classList.remove("hidden");
1142
history.pushState({}, null, "/images/");
1143
} else {
1144
album.classList.add("hidden");
1145
}
1146
}
1147
1148
1123
const register_settings_storage = async () => {
1149
1124
const optionElements = document.querySelectorAll(optionElementsSelector);
1150
1125
optionElements.forEach((element) => {
@@ -1277,18 +1252,12 @@ window.addEventListener('load', async function() {
1277
1252
await on_load();
1278
1253
if (window.conversation_id == "{{chat_id}}") {
1279
1254
window.conversation_id = uuid();
1280
} else {
1281
await on_api();
1282
1255
}
1283
});
1284
1285
window.addEventListener('pywebviewready', async function() {
1286
1256
await on_api();
1287
1257
});
1288
1258
1289
1259
async function on_load() {
1290
1260
count_input();
1291
1292
1261
if (/\/chat\/.+/.test(window.location.href)) {
1293
1262
load_conversation(window.conversation_id);
1294
1263
} else {
@@ -1334,7 +1303,7 @@ async function on_api() {
1334
1303
messageInput.addEventListener("keydown", async (evt) => {
1335
1304
if (prompt_lock) return;
1336
1305
1337
// If not mobile
1306
// If not mobile and not shift enter
1338
1307
if (!window.matchMedia("(pointer:coarse)").matches && evt.keyCode === 13 && !evt.shiftKey) {
1339
1308
evt.preventDefault();
1340
1309
console.log("pressed enter");
@@ -1396,6 +1365,7 @@ async function on_api() {
1396
1365
await load_provider_models(appStorage.getItem("provider"));
1397
1366
} catch (e) {
1398
1367
console.error(e)
1368
// Redirect to show basic authenfication
1399
1369
if (document.location.pathname == "/chat/") {
1400
1370
document.location.href = `/chat/error`;
1401
1371
}
@@ -1552,15 +1522,6 @@ function get_selected_model() {
1552
1522
}
1553
1523
1554
1524
async function api(ressource, args=null, file=null, message_id=null) {
1555
if (window?.pywebview) {
1556
if (args !== null) {
1557
if (ressource == "models") {
1558
ressource = "provider_models";
1559
}
1560
return pywebview.api[`get_${ressource}`](args);
1561
}
1562
return pywebview.api[`get_${ressource}`]();
1563
}
1564
1525
let api_key;
1565
1526
if (ressource == "models" && args) {
1566
1527
api_key = get_api_key_by_provider(args);