返回提交历史
Modified
.gitignore
+2
-1
Modified
README.md
+6
-1
Modified
g4f/Provider/needs_auth/Gemini.py
+2
-7
Modified
g4f/client/async_client.py
+2
-1
Modified
g4f/gui/client/index.html
+2
-2
Modified
g4f/gui/client/static/css/style.css
+32
-10
Modified
g4f/gui/client/static/js/chat.v1.js
+24
-0
Modified
g4f/gui/server/api.py
+35
-11
Modified
g4f/gui/server/backend.py
+4
-4
Added
generated_images/.gitkeep
+0
-0
XFEstudio/gpt4free
Add some options to messages in gui, saves generated images local
80b2e9b8
代码差异
10 个文件
+109
-37
@@ -64,4 +64,5 @@ dist.py
64
64
x.txt
65
65
bench.py
66
66
to-reverse.txt
67
g4f/Provider/OpenaiChat2.py
67
g4f/Provider/OpenaiChat2.py
68
generated_images/
@@ -92,7 +92,12 @@ As per the survey, here is a list of improvements to come
92
92
93
93
```sh
94
94
docker pull hlohaus789/g4f
95
docker run -p 8080:8080 -p 1337:1337 -p 7900:7900 --shm-size="2g" -v ${PWD}/har_and_cookies:/app/har_and_cookies hlohaus789/g4f:latest
95
docker run \
96
-p 8080:8080 -p 1337:1337 -p 7900:7900 \
97
--shm-size="2g" \
98
-v ${PWD}/har_and_cookies:/app/har_and_cookies \
99
-v ${PWD}/generated_images:/app/generated_images \
100
hlohaus789/g4f:latest
96
101
```
97
102
98
103
3. **Access the Client:**
@@ -4,7 +4,6 @@ import os
4
4
import json
5
5
import random
6
6
import re
7
import base64
8
7
9
8
from aiohttp import ClientSession, BaseConnector
10
9
@@ -193,14 +192,10 @@ class Gemini(AsyncGeneratorProvider):
193
192
yield content
194
193
if image_prompt:
195
194
images = [image[0][3][3] for image in response_part[4][0][12][7][0]]
196
resolved_images = []
197
195
if response_format == "b64_json":
198
for image in images:
199
async with client.get(image) as response:
200
data = base64.b64encode(await response.content.read()).decode()
201
resolved_images.append(data)
202
yield ImageDataResponse(resolved_images, image_prompt)
196
yield ImageResponse(images, image_prompt, {"cookies": cls._cookies})
203
197
else:
198
resolved_images = []
204
199
preview = []
205
200
for image in images:
206
201
async with client.get(image, allow_redirects=False) as fetch:
@@ -171,7 +171,8 @@ async def iter_image_response(
171
171
if isinstance(chunk, ImageProviderResponse):
172
172
if response_format == "b64_json":
173
173
async with ClientSession(
174
connector=get_connector(connector, proxy)
174
connector=get_connector(connector, proxy),
175
cookies=chunk.options.get("cookies")
175
176
) as session:
176
177
async def fetch_image(image):
177
178
async with session.get(image) as response:
@@ -32,10 +32,10 @@
32
32
<script type="module" src="https://cdn.jsdelivr.net/npm/mistral-tokenizer-js" async>
33
33
import mistralTokenizer from "mistral-tokenizer-js"
34
34
</script>
35
<script type="module" src="https://belladoreai.github.io/llama-tokenizer-js/llama-tokenizer.js" async>
35
<script type="module" src="https://cdn.jsdelivr.net/gh/belladoreai/llama-tokenizer-js@master/llama-tokenizer.js" async>
36
36
import llamaTokenizer from "llama-tokenizer-js"
37
37
</script>
38
<script src="https://unpkg.com/gpt-tokenizer/dist/cl100k_base.js" async></script>
38
<script src="https://cdn.jsdelivr.net/npm/gpt-tokenizer/dist/cl100k_base.js" async></script>
39
39
<script src="/static/js/text_to_speech/index.js" async></script>
40
40
<!--
41
41
<script src="/static/js/whisper-web/index.js" async></script>
@@ -265,6 +265,14 @@ body {
265
265
padding-bottom: 0;
266
266
}
267
267
268
.message.print {
269
height: 100%;
270
position: absolute;
271
background-color: #fff;
272
z-index: 100;
273
top: 0;
274
}
275
268
276
.message.regenerate {
269
277
opacity: 0.75;
270
278
}
@@ -339,14 +347,14 @@ body {
339
347
flex-wrap: wrap;
340
348
}
341
349
342
.message .content,
343
.message .content a:link,
344
.message .content a:visited{
350
.message .content_inner,
351
.message .content_inner a:link,
352
.message .content_inner a:visited{
345
353
font-size: 15px;
346
354
line-height: 1.3;
347
355
color: var(--colour-3);
348
356
}
349
.message .content pre{
357
.message .content_inner pre{
350
358
white-space: pre-wrap;
351
359
}
352
360
@@ -389,19 +397,19 @@ body {
389
397
390
398
.message .count .fa-clipboard,
391
399
.message .count .fa-volume-high,
392
.message .count .fa-rotate {
400
.message .count .fa-rotate,
401
.message .count .fa-print {
393
402
z-index: 1000;
394
403
cursor: pointer;
395
404
}
396
405
397
.message .count .fa-clipboard {
406
.message .count .fa-clipboard,
407
.message .count .fa-whatsapp {
398
408
color: var(--colour-3);
399
409
}
400
410
401
.message .count .fa-clipboard.clicked {
402
color: var(--accent);
403
}
404
411
.message .count .fa-clipboard.clicked,
412
.message .count .fa-print.clicked,
405
413
.message .count .fa-volume-high.active {
406
414
color: var(--accent);
407
415
}
@@ -1121,4 +1129,18 @@ a:-webkit-any-link {
1121
1129
50% {
1122
1130
opacity: 0;
1123
1131
}
1132
}
1133
1134
@media print {
1135
#systemPrompt:placeholder-shown,
1136
.conversations,
1137
.conversation .user-input,
1138
.conversation .buttons,
1139
.conversation .toolbar,
1140
.conversation .slide-systemPrompt,
1141
.message .count i,
1142
.message .assistant,
1143
.message .user {
1144
display: none;
1145
}
1124
1146
}
@@ -192,6 +192,26 @@ const register_message_buttons = async () => {
192
192
})
193
193
}
194
194
});
195
document.querySelectorAll(".message .fa-whatsapp").forEach(async (el) => {
196
if (!el.parentElement.href) {
197
const text = el.parentElement.parentElement.parentElement.innerText;
198
el.parentElement.href = `https://wa.me/?text=${encodeURIComponent(text)}`;
199
}
200
});
201
document.querySelectorAll(".message .fa-print").forEach(async (el) => {
202
if (!("click" in el.dataset)) {
203
el.dataset.click = "true";
204
el.addEventListener("click", async () => {
205
const message_el = el.parentElement.parentElement.parentElement;
206
el.classList.add("clicked");
207
message_box.scrollTop = 0;
208
message_el.classList.add("print");
209
setTimeout(() => el.classList.remove("clicked"), 1000);
210
setTimeout(() => message_el.classList.remove("print"), 1000);
211
window.print()
212
})
213
}
214
});
195
215
}
196
216
197
217
const delete_conversations = async () => {
@@ -253,6 +273,8 @@ const handle_ask = async () => {
253
273
${count_words_and_tokens(message, get_selected_model())}
254
274
<i class="fa-solid fa-volume-high"></i>
255
275
<i class="fa-regular fa-clipboard"></i>
276
<a><i class="fa-brands fa-whatsapp"></i></a>
277
<i class="fa-solid fa-print"></i>
256
278
</div>
257
279
</div>
258
280
</div>
@@ -625,6 +647,8 @@ const load_conversation = async (conversation_id, scroll=true) => {
625
647
${count_words_and_tokens(item.content, next_provider?.model)}
626
648
<i class="fa-solid fa-volume-high"></i>
627
649
<i class="fa-regular fa-clipboard"></i>
650
<a><i class="fa-brands fa-whatsapp"></i></a>
651
<i class="fa-solid fa-print"></i>
628
652
</div>
629
653
</div>
630
654
</div>
@@ -1,18 +1,27 @@
1
1
from __future__ import annotations
2
2
3
3
import logging
4
import json
5
from typing import Iterator
4
import os
5
import os.path
6
import uuid
7
import asyncio
8
import time
9
from aiohttp import ClientSession
10
from typing import Iterator, Optional
11
from flask import send_from_directory
6
12
7
13
from g4f import version, models
8
14
from g4f import get_last_provider, ChatCompletion
9
15
from g4f.errors import VersionNotFoundError
10
from g4f.image import ImagePreview
16
from g4f.typing import Cookies
17
from g4f.image import ImagePreview, ImageResponse, is_accepted_format
18
from g4f.requests.aiohttp import get_connector
11
19
from g4f.Provider import ProviderType, __providers__, __map__
12
20
from g4f.providers.base_provider import ProviderModelMixin, FinishReason
13
21
from g4f.providers.conversation import BaseConversation
14
22
15
23
conversations: dict[dict[str, BaseConversation]] = {}
24
images_dir = "./generated_images"
16
25
17
26
class Api():
18
27
@@ -110,14 +119,8 @@ class Api():
110
119
"latest_version": version.utils.latest_version,
111
120
}
112
121
113
def generate_title(self):
114
"""
115
Generates and returns a title based on the request data.
116
117
Returns:
118
dict: A dictionary with the generated title.
119
"""
120
return {'title': ''}
122
def serve_images(self, name):
123
return send_from_directory(os.path.abspath(images_dir), name)
121
124
122
125
def _prepare_conversation_kwargs(self, json_data: dict, kwargs: dict):
123
126
"""
@@ -185,6 +188,27 @@ class Api():
185
188
yield self._format_json("message", get_error_message(chunk))
186
189
elif isinstance(chunk, ImagePreview):
187
190
yield self._format_json("preview", chunk.to_string())
191
elif isinstance(chunk, ImageResponse):
192
async def copy_images(images: list[str], cookies: Optional[Cookies] = None):
193
async with ClientSession(
194
connector=get_connector(None, os.environ.get("G4F_PROXY")),
195
cookies=cookies
196
) as session:
197
async def copy_image(image):
198
async with session.get(image) as response:
199
target = os.path.join(images_dir, f"{int(time.time())}_{str(uuid.uuid4())}")
200
with open(target, "wb") as f:
201
async for chunk in response.content.iter_any():
202
f.write(chunk)
203
with open(target, "rb") as f:
204
extension = is_accepted_format(f.read(12)).split("/")[-1]
205
extension = "jpg" if extension == "jpeg" else extension
206
new_target = f"{target}.{extension}"
207
os.rename(target, new_target)
208
return f"/images/{os.path.basename(new_target)}"
209
return await asyncio.gather(*[copy_image(image) for image in images])
210
images = asyncio.run(copy_images(chunk.get_list(), chunk.options.get("cookies")))
211
yield self._format_json("content", str(ImageResponse(images, chunk.alt)))
188
212
elif not isinstance(chunk, FinishReason):
189
213
yield self._format_json("content", str(chunk))
190
214
except Exception as e:
@@ -47,13 +47,13 @@ class Backend_Api(Api):
47
47
'function': self.handle_conversation,
48
48
'methods': ['POST']
49
49
},
50
'/backend-api/v2/gen.set.summarize:title': {
51
'function': self.generate_title,
52
'methods': ['POST']
53
},
54
50
'/backend-api/v2/error': {
55
51
'function': self.handle_error,
56
52
'methods': ['POST']
53
},
54
'/images/<path:name>': {
55
'function': self.serve_images,
56
'methods': ['GET']
57
57
}
58
58
}
59
59
此文件没有可显示的逐行差异。