返回提交历史
Modified
README.md
+16
-6
Added
docs/gui.md
+147
-0
Modified
g4f/Provider/Copilot.py
+19
-19
Modified
g4f/Provider/needs_auth/BingCreateImages.py
+2
-2
Modified
g4f/Provider/needs_auth/Gemini.py
+1
-1
Modified
g4f/Provider/needs_auth/MicrosoftDesigner.py
+1
-1
Modified
g4f/Provider/needs_auth/OpenaiChat.py
+1
-1
Modified
g4f/gui/client/index.html
+35
-9
Modified
g4f/gui/client/static/css/style.css
+73
-7
Modified
g4f/gui/client/static/js/chat.v1.js
+127
-29
Modified
g4f/gui/server/api.py
+46
-44
Modified
g4f/gui/server/backend.py
+7
-1
Modified
g4f/image.py
+3
-0
Modified
g4f/providers/types.py
+1
-1
Modified
g4f/requests/__init__.py
+2
-2
XFEstudio/gpt4free
Use custom user data dir for each provider Reuse cookies and access token in Copilot Send in the gui messages to multiple providers at once Add GUI documenation
6a624acf
代码差异
15 个文件
+481
-123
@@ -132,17 +132,27 @@ To ensure the seamless operation of our application, please follow the instructi
132
132
133
133
By following these steps, you should be able to successfully install and run the application on your Windows system. If you encounter any issues during the installation process, please refer to our Issue Tracker or try to get contact over Discord for assistance.
134
134
135
Run the **Webview UI** on other Platforms:
135
---
136
137
### Learn More About the GUI
138
139
For detailed instructions on how to set up, configure, and use the GPT4Free GUI, refer to the **GUI Documentation**:
140
141
- [GUI Documentation](docs/gui.md)
136
142
137
- [/docs/webview](docs/webview.md)
143
This guide includes step-by-step details on provider selection, managing conversations, using advanced features like speech recognition, and more.
138
144
139
##### Use your smartphone:
145
---
146
147
### Use Your Smartphone
140
148
141
Run the Web UI on Your Smartphone:
149
Run the Web UI on your smartphone for easy access on the go. Check out the dedicated guide to learn how to set up and use the GUI on your mobile device:
142
150
143
- [/docs/guides/phone](docs/guides/phone.md)
151
- [Run on Smartphone Guide](docs/guides/phone.md)
152
153
---
144
154
145
#### Use python
155
### Use python
146
156
147
157
##### Prerequisites:
148
158
@@ -0,0 +1,147 @@
1
# G4F - GUI Documentation
2
3
## Overview
4
The G4F GUI is a self-contained, user-friendly interface designed for interacting with multiple AI models from various providers. It allows users to generate text, code, and images effortlessly. Advanced features such as speech recognition, file uploads, conversation backup/restore, and more are included. Both the backend and frontend are fully integrated into the GUI, making setup simple and seamless.
5
6
## Features
7
8
### 1. **Multiple Providers and Models**
9
- **Provider/Model Selection via Dropdown:** Use the select box to choose a specific **provider/model combination**.
10
- **Pinning Provider/Model Combinations:** After selecting a provider and model from the dropdown, click the **pin button** to add the combination to the pinned list.
11
- **Remove Pinned Combinations:** Each pinned provider/model combination is displayed as a button. Clicking on the button removes it from the pinned list.
12
- **Send Requests to Multiple Providers:** You can pin multiple provider/model combinations and send requests to all of them simultaneously, enabling fast and comprehensive content generation.
13
14
### 2. **Text, Code, and Image Generation**
15
- **Text and Code Generation:** Enter prompts to generate text or code outputs.
16
- **Image Generation:** Provide text prompts to generate images, which are shown as thumbnails. Clicking on a thumbnail opens the image in a lightbox view.
17
18
### 3. **Gallery Functionality**
19
- **Image Thumbnails:** Generated images appear as small thumbnails within the conversation.
20
- **Lightbox View:** Clicking a thumbnail opens the image in full size, along with the prompt used to generate it.
21
- **Automatic Image Download:** Enable automatic downloading of generated images through the settings.
22
23
### 4. **Conversation Management**
24
- **Message Reuse:** While messages can't be edited, you can copy and reuse them.
25
- **Message Deletion:** Conversations can be deleted for a cleaner workspace.
26
- **Conversation List:** The left sidebar displays a list of active and past conversations for easy navigation.
27
- **Change Conversation Title:** By clicking the three dots next to a conversation title, you can either delete or change its title.
28
- **Backup and Restore Conversations:** Backup and restore all conversations and messages as a JSON file (accessible via the settings).
29
30
### 5. **Speech Recognition and Synthesis**
31
- **Speech Input:** Use speech recognition to input prompts by speaking instead of typing.
32
- **Speech Output (Text-to-Speech):** The generated text can be read aloud using speech synthesis.
33
- **Custom Language Settings:** Configure the language used for speech recognition to match your preference.
34
35
### 6. **File Uploads**
36
- **Image Uploads:** Upload images that will be appended to your message and sent to the AI provider.
37
- **Text File Uploads:** Upload text files, and their contents will be added to the message to provide more detailed input to the AI.
38
39
### 7. **Web Access and Settings**
40
- **DuckDuckGo Web Access:** Enable web access through DuckDuckGo for privacy-focused browsing.
41
- **Theme Toggle:** Switch between **dark mode** and **light mode** in the settings.
42
- **Provider Visibility:** Hide unused providers in the settings using toggle buttons.
43
- **Log Access:** View application logs, including error messages and debug logs, through the settings.
44
45
### 8. **Authentication**
46
- **Basic Authentication:** Set a password for Basic Authentication using the `--g4f-api-key` argument when starting the web server.
47
48
## Installation
49
50
You can install the G4F GUI either as a full stack or in a lightweight version:
51
52
1. **Full Stack Installation** (includes all packages, including browser support and drivers):
53
```bash
54
pip install -U g4f[all]
55
```
56
57
2. **Slim Installation** (does not include browser drivers, suitable for headless environments):
58
```bash
59
pip install -U g4f[slim]
60
```
61
62
- **Full Stack Installation:** This installs all necessary dependencies, including browser support for web-based interactions.
63
- **Slim Installation:** This version is lighter, with no browser support, ideal for environments where browser interactions are not required.
64
65
## Setup
66
67
### Setting the Environment Variable
68
69
It is **recommended** to set a `G4F_API_KEY` environment variable for authentication. You can do this as follows:
70
71
On **Linux/macOS**:
72
```bash
73
export G4F_API_KEY="your-api-key-here"
74
```
75
76
On **Windows**:
77
```bash
78
set G4F_API_KEY="your-api-key-here"
79
```
80
81
### Start the GUI and Backend
82
83
Run the following command to start both the GUI and backend services based on the G4F client:
84
85
```bash
86
python -m g4f --debug --port 8080 --g4f-api-key $G4F_API_KEY
87
```
88
89
This starts the GUI at `http://localhost:8080` with all necessary backend components running seamlessly.
90
91
### Access the GUI
92
93
Once the server is running, open your browser and navigate to:
94
95
```
96
http://localhost:8080/chat/
97
```
98
99
## Using the Interface
100
101
1. **Select and Manage Providers/Models:**
102
- Use the **select box** to view the list of available providers and models.
103
- Select a **provider/model combination** from the dropdown.
104
- Click the **pin button** to add the combination to your pinned list.
105
- To **unpin** a combination, click the corresponding button in the pinned list.
106
107
2. **Input a Prompt:**
108
- Enter your prompt manually or use **speech recognition** to dictate it.
109
- You can also upload **images** or **text files** to be included in the prompt.
110
111
3. **Generate Content:**
112
- Click the "Generate" button to produce the content.
113
- The AI will generate text, code, or images depending on the prompt.
114
115
4. **View and Interact with Results:**
116
- **For Text/Code:** The generated content will appear in the conversation window.
117
- **For Images:** Generated images will be shown as thumbnails. Click on them to view in full size.
118
119
5. **Backup and Restore Conversations:**
120
- Backup all your conversations as a **JSON file** and restore them at any time via the settings.
121
122
6. **Manage Conversations:**
123
- Delete or rename any conversation by clicking the three dots next to the conversation title.
124
125
### Gallery Functionality
126
127
- **Image Thumbnails:** All generated images are shown as thumbnails within the conversation window.
128
- **Lightbox View:** Clicking a thumbnail opens the image in a larger view along with the associated prompt.
129
- **Automatic Image Download:** Enable automatic downloading of generated images in the settings.
130
131
## Settings Configuration
132
133
1. **API Key:** Set your API key when starting the server by defining the `G4F_API_KEY` environment variable.
134
2. **Provider Visibility:** Hide unused providers through the settings.
135
3. **Theme:** Toggle between **dark mode** and **light mode**. Disabling dark mode switches to a white theme.
136
4. **DuckDuckGo Access:** Enable DuckDuckGo for privacy-focused web browsing.
137
5. **Speech Recognition Language:** Set your preferred language for speech recognition.
138
6. **Log Access:** View logs, including error and debug messages, from the settings menu.
139
7. **Automatic Image Download:** Enable this feature to automatically download generated images.
140
141
## Known Issues
142
143
- **Gallery Loading:** Large images may take time to load depending on system performance.
144
- **Speech Recognition Accuracy:** Accuracy may vary depending on microphone quality or speech clarity.
145
- **Provider Downtime:** Some AI providers may experience downtime or disruptions.
146
147
[Return to Home](/)
@@ -29,13 +29,9 @@ from .. import debug
29
29
30
30
class Conversation(BaseConversation):
31
31
conversation_id: str
32
cookie_jar: CookieJar
33
access_token: str
34
32
35
def __init__(self, conversation_id: str, cookie_jar: CookieJar, access_token: str = None):
33
def __init__(self, conversation_id: str):
36
34
self.conversation_id = conversation_id
37
self.cookie_jar = cookie_jar
38
self.access_token = access_token
39
35
40
36
class Copilot(AbstractProvider, ProviderModelMixin):
41
37
label = "Microsoft Copilot"
@@ -50,6 +46,9 @@ class Copilot(AbstractProvider, ProviderModelMixin):
50
46
51
47
websocket_url = "wss://copilot.microsoft.com/c/api/chat?api-version=2"
52
48
conversation_url = f"{url}/c/api/conversations"
49
50
_access_token: str = None
51
_cookies: CookieJar = None
53
52
54
53
@classmethod
55
54
def create_completion(
@@ -69,42 +68,43 @@ class Copilot(AbstractProvider, ProviderModelMixin):
69
68
raise MissingRequirementsError('Install or update "curl_cffi" package | pip install -U curl_cffi')
70
69
71
70
websocket_url = cls.websocket_url
72
access_token = None
73
71
headers = None
74
cookies = conversation.cookie_jar if conversation is not None else None
75
72
if cls.needs_auth or image is not None:
76
if conversation is None or conversation.access_token is None:
73
if cls._access_token is None:
77
74
try:
78
access_token, cookies = readHAR(cls.url)
75
cls._access_token, cls._cookies = readHAR(cls.url)
79
76
except NoValidHarFileError as h:
80
77
debug.log(f"Copilot: {h}")
81
78
try:
82
79
get_running_loop(check_nested=True)
83
access_token, cookies = asyncio.run(get_access_token_and_cookies(cls.url, proxy))
80
cls._access_token, cls._cookies = asyncio.run(get_access_token_and_cookies(cls.url, proxy))
84
81
except MissingRequirementsError:
85
82
raise h
86
else:
87
access_token = conversation.access_token
88
debug.log(f"Copilot: Access token: {access_token[:7]}...{access_token[-5:]}")
89
websocket_url = f"{websocket_url}&accessToken={quote(access_token)}"
90
headers = {"authorization": f"Bearer {access_token}"}
83
debug.log(f"Copilot: Access token: {cls._access_token[:7]}...{cls._access_token[-5:]}")
84
websocket_url = f"{websocket_url}&accessToken={quote(cls._access_token)}"
85
headers = {"authorization": f"Bearer {cls._access_token}"}
91
86
92
87
with Session(
93
88
timeout=timeout,
94
89
proxy=proxy,
95
90
impersonate="chrome",
96
91
headers=headers,
97
cookies=cookies,
92
cookies=cls._cookies,
98
93
) as session:
94
if cls._access_token is not None:
95
cls._cookies = session.cookies.jar
99
96
response = session.get("https://copilot.microsoft.com/c/api/user")
100
97
raise_for_status(response)
101
debug.log(f"Copilot: User: {response.json().get('firstName', 'null')}")
98
user = response.json().get('firstName')
99
if user is None:
100
cls._access_token = None
101
debug.log(f"Copilot: User: {user or 'null'}")
102
102
if conversation is None:
103
103
response = session.post(cls.conversation_url)
104
104
raise_for_status(response)
105
105
conversation_id = response.json().get("id")
106
106
if return_conversation:
107
yield Conversation(conversation_id, session.cookies.jar, access_token)
107
yield Conversation(conversation_id)
108
108
prompt = format_prompt(messages)
109
109
debug.log(f"Copilot: Created conversation: {conversation_id}")
110
110
else:
@@ -162,7 +162,7 @@ class Copilot(AbstractProvider, ProviderModelMixin):
162
162
raise RuntimeError(f"Invalid response: {last_msg}")
163
163
164
164
async def get_access_token_and_cookies(url: str, proxy: str = None, target: str = "ChatAI",):
165
browser = await get_nodriver(proxy=proxy)
165
browser = await get_nodriver(proxy=proxy, user_data_dir="copilot")
166
166
page = await browser.get(url)
167
167
access_token = None
168
168
while access_token is None:
@@ -9,11 +9,11 @@ from ..bing.create_images import create_images, create_session
9
9
10
10
class BingCreateImages(AsyncGeneratorProvider, ProviderModelMixin):
11
11
label = "Microsoft Designer in Bing"
12
parent = "Bing"
13
12
url = "https://www.bing.com/images/create"
14
13
working = True
15
14
needs_auth = True
16
image_models = ["dall-e"]
15
image_models = ["dall-e-3"]
16
models = image_models
17
17
18
18
def __init__(self, cookies: Cookies = None, proxy: str = None, api_key: str = None) -> None:
19
19
if api_key is not None:
@@ -69,7 +69,7 @@ class Gemini(AsyncGeneratorProvider):
69
69
if debug.logging:
70
70
print("Skip nodriver login in Gemini provider")
71
71
return
72
browser = await get_nodriver(proxy=proxy)
72
browser = await get_nodriver(proxy=proxy, user_data_dir="gemini")
73
73
login_url = os.environ.get("G4F_LOGIN_URL")
74
74
if login_url:
75
75
yield f"Please login: [Google Gemini]({login_url})\n\n"
@@ -142,7 +142,7 @@ def readHAR(url: str) -> tuple[str, str]:
142
142
return api_key, user_agent
143
143
144
144
async def get_access_token_and_user_agent(url: str, proxy: str = None):
145
browser = await get_nodriver(proxy=proxy)
145
browser = await get_nodriver(proxy=proxy, user_data_dir="designer")
146
146
page = await browser.get(url)
147
147
user_agent = await page.evaluate("navigator.userAgent")
148
148
access_token = None
@@ -510,7 +510,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin):
510
510
511
511
@classmethod
512
512
async def nodriver_auth(cls, proxy: str = None):
513
browser = await get_nodriver(proxy=proxy)
513
browser = await get_nodriver(proxy=proxy, user_data_dir="chatgpt")
514
514
page = browser.main_tab
515
515
def on_request(event: nodriver.cdp.network.RequestWillBeSent):
516
516
if event.request.url == start_url or event.request.url.startswith(conversation_url):
@@ -20,6 +20,7 @@
20
20
<script src="/static/js/chat.v1.js" defer></script>
21
21
<script src="https://cdn.jsdelivr.net/npm/markdown-it@13.0.1/dist/markdown-it.min.js"></script>
22
22
<link rel="stylesheet" href="/static/css/dracula.min.css">
23
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe/dist/photoswipe.css">
23
24
<script>
24
25
MathJax = {
25
26
chtml: {
@@ -37,9 +38,34 @@
37
38
</script>
38
39
<script src="https://cdn.jsdelivr.net/npm/gpt-tokenizer/dist/cl100k_base.js" async></script>
39
40
<script src="/static/js/text_to_speech/index.js" async></script>
40
<!--
41
<script src="/static/js/whisper-web/index.js" async></script>
42
-->
41
<script type="module" async>
42
import PhotoSwipeLightbox from 'https://unpkg.com/photoswipe/dist/photoswipe-lightbox.esm.js';
43
const lightbox = new PhotoSwipeLightbox({
44
gallery: '#messages',
45
children: 'a:has(img)',
46
showHideAnimationType: 'none',
47
pswpModule: () => import('https://unpkg.com/photoswipe'),
48
});
49
lightbox.on('uiRegister', function() {
50
lightbox.pswp.ui.registerElement({
51
name: 'custom-caption',
52
order: 9,
53
isButton: false,
54
appendTo: 'root',
55
html: 'Caption text',
56
onInit: (el, pswp) => {
57
lightbox.pswp.on('change', () => {
58
const currSlideElement = lightbox.pswp.currSlide.data.element;
59
let captionHTML = '';
60
if (currSlideElement) {
61
el.innerHTML = currSlideElement.querySelector('img').getAttribute('alt');
62
}
63
});
64
}
65
});
66
});
67
lightbox.init();
68
</script>
43
69
<script>
44
70
const user_image = '<img src="/static/img/user.png" alt="your avatar">';
45
71
const gpt_image = '<img src="/static/img/gpt.png" alt="your avatar">';
@@ -261,16 +287,16 @@
261
287
<option value="">Provider: Auto</option>
262
288
<option value="OpenaiChat">OpenAI ChatGPT</option>
263
289
<option value="Copilot">Microsoft Copilot</option>
264
<option value="ChatGpt">ChatGpt</option>
265
<option value="Gemini">Gemini</option>
266
<option value="MetaAI">Meta AI</option>
267
<option value="DeepInfraChat">DeepInfraChat</option>
268
<option value="Blackbox">Blackbox</option>
290
<option value="Gemini">Google Gemini</option>
269
291
<option value="DDG">DuckDuckGo</option>
270
<option value="Pizzagpt">Pizzagpt</option>
271
292
<option disabled="disabled">----</option>
272
293
</select>
273
294
</div>
295
<div class="field">
296
<button id="pin">
297
<i class="fa-solid fa-thumbtack"></i>
298
</button>
299
</div>
274
300
</div>
275
301
</div>
276
302
<div class="log hidden"></div>
@@ -63,6 +63,7 @@
63
63
--conversations-hover: #c7a2ff4d;
64
64
--scrollbar: var(--colour-3);
65
65
--scrollbar-thumb: var(--blur-bg);
66
--button-hover: var(--colour-5);
66
67
}
67
68
68
69
:root {
@@ -533,7 +534,7 @@ body.white .gradient{
533
534
534
535
.stop_generating, .toolbar .regenerate {
535
536
position: absolute;
536
z-index: 1000000;
537
z-index: 100000;
537
538
top: 0;
538
539
right: 0;
539
540
}
@@ -729,13 +730,8 @@ label[for="camera"] {
729
730
730
731
731
732
select {
732
-webkit-border-radius: 8px;
733
-moz-border-radius: 8px;
734
733
border-radius: 8px;
735
736
-webkit-backdrop-filter: blur(20px);
737
734
backdrop-filter: blur(20px);
738
739
735
cursor: pointer;
740
736
background-color: var(--colour-1);
741
737
border: 1px solid var(--blur-border);
@@ -745,11 +741,47 @@ select {
745
741
overflow: hidden;
746
742
outline: none;
747
743
padding: 8px 16px;
748
749
744
appearance: none;
750
745
width: 160px;
751
746
}
752
747
748
.buttons button {
749
border-radius: 8px;
750
backdrop-filter: blur(20px);
751
cursor: pointer;
752
background-color: var(--colour-1);
753
border: 1px solid var(--blur-border);
754
color: var(--colour-3);
755
padding: 8px;
756
}
757
758
.buttons button.pinned span {
759
max-width: 160px;
760
overflow: hidden;
761
text-wrap: nowrap;
762
margin-right: 16px;
763
display: block;
764
text-overflow: ellipsis;
765
}
766
767
.buttons button.pinned i {
768
position: absolute;
769
top: 10px;
770
right: 6px;
771
}
772
773
select:hover,
774
.buttons button:hover,
775
.stop_generating button:hover,
776
.toolbar .regenerate button:hover,
777
#send-button:hover {
778
background-color: var(--button-hover);
779
}
780
781
#provider option:disabled[value] {
782
display: none;
783
}
784
753
785
#systemPrompt, .settings textarea {
754
786
font-size: 15px;
755
787
width: 100%;
@@ -761,6 +793,39 @@ select {
761
793
resize: vertical;
762
794
}
763
795
796
.pswp {
797
--pswp-placeholder-bg: #000 !important;
798
}
799
.pswp img {
800
object-fit: contain;
801
}
802
.pswp__img--placeholder--blank{
803
display: none !important;
804
}
805
.pswp__custom-caption {
806
opacity: 0 !important;
807
background: rgba(0, 0, 0, 0.3);
808
font-size: 16px;
809
color: #fff;
810
width: calc(100% - 32px);
811
max-width: 400px;
812
padding: 2px 8px;
813
border-radius: 4px;
814
position: absolute;
815
left: 50%;
816
bottom: 16px;
817
transform: translateX(-50%);
818
max-height: 100px;
819
overflow: auto;
820
}
821
.pswp__custom-caption:hover {
822
opacity: 1 !important;
823
}
824
.pswp__custom-caption a {
825
color: #fff;
826
text-decoration: underline;
827
}
828
764
829
.slide-systemPrompt {
765
830
position: absolute;
766
831
top: 0;
@@ -1112,6 +1177,7 @@ ul {
1112
1177
--colour-3: #212529;
1113
1178
--scrollbar: var(--colour-1);
1114
1179
--scrollbar-thumb: var(--gradient);
1180
--button-hover: var(--colour-4);
1115
1181
}
1116
1182
1117
1183
.white .message .assistant .fa-xmark {
@@ -3,7 +3,7 @@ const message_box = document.getElementById(`messages`);
3
3
const messageInput = document.getElementById(`message-input`);
4
4
const box_conversations = document.querySelector(`.top`);
5
5
const stop_generating = document.querySelector(`.stop_generating`);
6
const regenerate = document.querySelector(`.regenerate`);
6
const regenerate_button = document.querySelector(`.regenerate`);
7
7
const sidebar = document.querySelector(".conversations");
8
8
const sidebar_button = document.querySelector(".mobile-sidebar");
9
9
const sendButton = document.getElementById("send-button");
@@ -21,7 +21,7 @@ const chat = document.querySelector(".conversation");
21
21
const album = document.querySelector(".images");
22
22
const log_storage = document.querySelector(".log");
23
23
24
const optionElements = document.querySelectorAll(".settings input, .settings textarea, #model, #model2, #provider")
24
const optionElementsSelector = ".settings input, .settings textarea, #model, #model2, #provider";
25
25
26
26
let provider_storage = {};
27
27
let message_storage = {};
@@ -364,7 +364,7 @@ const handle_ask = async () => {
364
364
}
365
365
</div>
366
366
<div class="count">
367
${count_words_and_tokens(message, get_selected_model())}
367
${count_words_and_tokens(message, get_selected_model()?.value)}
368
368
<i class="fa-solid fa-volume-high"></i>
369
369
<i class="fa-regular fa-clipboard"></i>
370
370
<a><i class="fa-brands fa-whatsapp"></i></a>
@@ -375,7 +375,19 @@ const handle_ask = async () => {
375
375
</div>
376
376
`;
377
377
highlight(message_box);
378
await ask_gpt(message_id);
378
379
const all_pinned = document.querySelectorAll(".buttons button.pinned")
380
if (all_pinned.length > 0) {
381
all_pinned.forEach((el, idx) => ask_gpt(
382
idx == 0 ? message_id : get_message_id(),
383
-1,
384
idx != 0,
385
el.dataset.provider,
386
el.dataset.model
387
));
388
} else {
389
await ask_gpt(message_id);
390
}
379
391
};
380
392
381
393
async function safe_remove_cancel_button() {
@@ -387,16 +399,21 @@ async function safe_remove_cancel_button() {
387
399
stop_generating.classList.add("stop_generating-hidden");
388
400
}
389
401
390
regenerate.addEventListener("click", async () => {
391
regenerate.classList.add("regenerate-hidden");
392
setTimeout(()=>regenerate.classList.remove("regenerate-hidden"), 3000);
393
await hide_message(window.conversation_id);
394
await ask_gpt(get_message_id());
402
regenerate_button.addEventListener("click", async () => {
403
regenerate_button.classList.add("regenerate-hidden");
404
setTimeout(()=>regenerate_button.classList.remove("regenerate-hidden"), 3000);
405
const all_pinned = document.querySelectorAll(".buttons button.pinned")
406
if (all_pinned.length > 0) {
407
all_pinned.forEach((el) => ask_gpt(get_message_id(), -1, true, el.dataset.provider, el.dataset.model));
408
} else {
409
await hide_message(window.conversation_id);
410
await ask_gpt(get_message_id());
411
}
395
412
});
396
413
397
414
stop_generating.addEventListener("click", async () => {
398
415
stop_generating.classList.add("stop_generating-hidden");
399
regenerate.classList.remove("regenerate-hidden");
416
regenerate_button.classList.remove("regenerate-hidden");
400
417
let key;
401
418
for (key in controller_storage) {
402
419
if (!controller_storage[key].signal.aborted) {
@@ -538,7 +555,11 @@ imageInput?.addEventListener("click", (e) => {
538
555
}
539
556
});
540
557
541
const ask_gpt = async (message_id, message_index = -1) => {
558
const ask_gpt = async (message_id, message_index = -1, regenerate = false, provider = null, model = null) => {
559
if (!model && !provider) {
560
model = get_selected_model()?.value || null;
561
provider = providerSelect.options[providerSelect.selectedIndex].value;
562
}
542
563
let messages = await get_messages(window.conversation_id);
543
564
messages = prepare_messages(messages, message_index);
544
565
message_storage[message_id] = "";
@@ -553,7 +574,7 @@ const ask_gpt = async (message_id, message_index = -1) => {
553
574
554
575
const message_el = document.createElement("div");
555
576
message_el.classList.add("message");
556
if (message_index != -1) {
577
if (message_index != -1 || regenerate) {
557
578
message_el.classList.add("regenerate");
558
579
}
559
580
message_el.innerHTML += `
@@ -593,14 +614,13 @@ const ask_gpt = async (message_id, message_index = -1) => {
593
614
try {
594
615
const input = imageInput && imageInput.files.length > 0 ? imageInput : cameraInput;
595
616
const file = input && input.files.length > 0 ? input.files[0] : null;
596
const provider = providerSelect.options[providerSelect.selectedIndex].value;
597
617
const auto_continue = document.getElementById("auto_continue")?.checked;
598
618
const download_images = document.getElementById("download_images")?.checked;
599
619
let api_key = get_api_key_by_provider(provider);
600
620
await api("conversation", {
601
621
id: message_id,
602
622
conversation_id: window.conversation_id,
603
model: get_selected_model(),
623
model: model,
604
624
web_search: document.getElementById("switch").checked,
605
625
provider: provider,
606
626
messages: messages,
@@ -632,7 +652,8 @@ const ask_gpt = async (message_id, message_index = -1) => {
632
652
message_storage[message_id],
633
653
message_provider,
634
654
message_index,
635
synthesize_storage[message_id]
655
synthesize_storage[message_id],
656
regenerate
636
657
);
637
658
await safe_load_conversation(window.conversation_id, message_index == -1);
638
659
} else {
@@ -645,7 +666,7 @@ const ask_gpt = async (message_id, message_index = -1) => {
645
666
await safe_remove_cancel_button();
646
667
await register_message_buttons();
647
668
await load_conversations();
648
regenerate.classList.remove("regenerate-hidden");
669
regenerate_button.classList.remove("regenerate-hidden");
649
670
};
650
671
651
672
async function scroll_to_bottom() {
@@ -848,7 +869,7 @@ const load_conversation = async (conversation_id, scroll=true) => {
848
869
message_box.innerHTML = elements;
849
870
register_message_buttons();
850
871
highlight(message_box);
851
regenerate.classList.remove("regenerate-hidden");
872
regenerate_button.classList.remove("regenerate-hidden");
852
873
853
874
if (scroll) {
854
875
message_box.scrollTo({ top: message_box.scrollHeight, behavior: "smooth" });
@@ -960,7 +981,8 @@ const add_message = async (
960
981
conversation_id, role, content,
961
982
provider = null,
962
983
message_index = -1,
963
synthesize_data = null
984
synthesize_data = null,
985
regenerate = false
964
986
) => {
965
987
const conversation = await get_conversation(conversation_id);
966
988
if (!conversation) return;
@@ -972,6 +994,9 @@ const add_message = async (
972
994
if (synthesize_data) {
973
995
new_message.synthesize = synthesize_data;
974
996
}
997
if (regenerate) {
998
new_message.regenerate = true;
999
}
975
1000
if (message_index == -1) {
976
1001
conversation.items.push(new_message);
977
1002
} else {
@@ -1118,6 +1143,7 @@ function open_album() {
1118
1143
}
1119
1144
1120
1145
const register_settings_storage = async () => {
1146
const optionElements = document.querySelectorAll(optionElementsSelector);
1121
1147
optionElements.forEach((element) => {
1122
1148
if (element.type == "textarea") {
1123
1149
element.addEventListener('input', async (event) => {
@@ -1145,6 +1171,7 @@ const register_settings_storage = async () => {
1145
1171
}
1146
1172
1147
1173
const load_settings_storage = async () => {
1174
const optionElements = document.querySelectorAll(optionElementsSelector);
1148
1175
optionElements.forEach((element) => {
1149
1176
if (!(value = appStorage.getItem(element.id))) {
1150
1177
return;
@@ -1226,7 +1253,7 @@ const count_input = async () => {
1226
1253
if (timeoutId) clearTimeout(timeoutId);
1227
1254
timeoutId = setTimeout(() => {
1228
1255
if (countFocus.value) {
1229
inputCount.innerText = count_words_and_tokens(countFocus.value, get_selected_model());
1256
inputCount.innerText = count_words_and_tokens(countFocus.value, get_selected_model()?.value);
1230
1257
} else {
1231
1258
inputCount.innerText = "";
1232
1259
}
@@ -1267,6 +1294,24 @@ async function on_load() {
1267
1294
load_conversations();
1268
1295
}
1269
1296
1297
const load_provider_option = (input, provider_name) => {
1298
if (input.checked) {
1299
providerSelect.querySelectorAll(`option[value="${provider_name}"]`).forEach(
1300
(el) => el.removeAttribute("disabled")
1301
);
1302
providerSelect.querySelectorAll(`option[data-parent="${provider_name}"]`).forEach(
1303
(el) => el.removeAttribute("disabled")
1304
);
1305
} else {
1306
providerSelect.querySelectorAll(`option[value="${provider_name}"]`).forEach(
1307
(el) => el.setAttribute("disabled", "disabled")
1308
);
1309
providerSelect.querySelectorAll(`option[data-parent="${provider_name}"]`).forEach(
1310
(el) => el.setAttribute("disabled", "disabled")
1311
);
1312
}
1313
};
1314
1270
1315
async function on_api() {
1271
1316
let prompt_lock = false;
1272
1317
messageInput.addEventListener("keydown", async (evt) => {
@@ -1292,22 +1337,42 @@ async function on_api() {
1292
1337
await handle_ask();
1293
1338
});
1294
1339
messageInput.focus();
1295
1296
register_settings_storage();
1297
1340
let provider_options = [];
1298
1341
try {
1299
1342
models = await api("models");
1300
1343
models.forEach((model) => {
1301
1344
let option = document.createElement("option");
1302
option.value = option.text = model;
1345
option.value = option.text = option.dataset.label = model;
1303
1346
modelSelect.appendChild(option);
1304
1347
});
1305
1348
providers = await api("providers")
1306
Object.entries(providers).forEach(([provider, label]) => {
1349
providers.sort((a, b) => a.label.localeCompare(b.label));
1350
providers.forEach((provider) => {
1307
1351
let option = document.createElement("option");
1308
option.value = provider;
1309
option.text = label;
1352
option.value = provider.name;
1353
option.dataset.label = provider.label;
1354
option.text = provider.label
1355
+ (provider.vision ? " (Image Upload)" : "")
1356
+ (provider.image ? " (Image Generation)" : "")
1357
+ (provider.webdriver ? " (Webdriver)" : "")
1358
+ (provider.auth ? " (Auth)" : "");
1359
if (provider.parent)
1360
option.dataset.parent = provider.parent;
1310
1361
providerSelect.appendChild(option);
1362
1363
if (!provider.parent) {
1364
option = document.createElement("div");
1365
option.classList.add("field");
1366
option.innerHTML = `
1367
<div class="field">
1368
<span class="label">Enable ${provider.label}</span>
1369
<input id="Provider${provider.name}" type="checkbox" name="Provider${provider.name}" checked="">
1370
<label for="Provider${provider.name}" class="toogle" title="Remove provider from dropdown"></label>
1371
</div>`;
1372
option.querySelector("input").addEventListener("change", (event) => load_provider_option(event.target, provider.name));
1373
settings.querySelector(".paper").appendChild(option);
1374
provider_options[provider.name] = option;
1375
}
1311
1376
});
1312
1377
await load_provider_models(appStorage.getItem("provider"));
1313
1378
} catch (e) {
@@ -1316,8 +1381,11 @@ async function on_api() {
1316
1381
document.location.href = `/chat/error`;
1317
1382
}
1318
1383
}
1319
1384
register_settings_storage();
1320
1385
await load_settings_storage()
1386
Object.entries(provider_options).forEach(
1387
([provider_name, option]) => load_provider_option(option.querySelector("input"), provider_name)
1388
);
1321
1389
1322
1390
const hide_systemPrompt = document.getElementById("hide-systemPrompt")
1323
1391
const slide_systemPrompt_icon = document.querySelector(".slide-systemPrompt i");
@@ -1455,9 +1523,12 @@ systemPrompt?.addEventListener("input", async () => {
1455
1523
1456
1524
function get_selected_model() {
1457
1525
if (modelProvider.selectedIndex >= 0) {
1458
return modelProvider.options[modelProvider.selectedIndex].value;
1526
return modelProvider.options[modelProvider.selectedIndex];
1459
1527
} else if (modelSelect.selectedIndex >= 0) {
1460
return modelSelect.options[modelSelect.selectedIndex].value;
1528
model = modelSelect.options[modelSelect.selectedIndex];
1529
if (model.value) {
1530
return model;
1531
}
1461
1532
}
1462
1533
}
1463
1534
@@ -1554,6 +1625,7 @@ async function load_provider_models(providerIndex=null) {
1554
1625
models.forEach((model) => {
1555
1626
let option = document.createElement('option');
1556
1627
option.value = model.model;
1628
option.dataset.label = model.model;
1557
1629
option.text = `${model.model}${model.image ? " (Image Generation)" : ""}${model.vision ? " (Image Upload)" : ""}`;
1558
1630
option.selected = model.default;
1559
1631
modelProvider.appendChild(option);
@@ -1564,6 +1636,32 @@ async function load_provider_models(providerIndex=null) {
1564
1636
}
1565
1637
};
1566
1638
providerSelect.addEventListener("change", () => load_provider_models());
1639
document.getElementById("pin").addEventListener("click", async () => {
1640
const pin_container = document.getElementById("pin").parentElement;
1641
let selected_provider = providerSelect.options[providerSelect.selectedIndex];
1642
selected_provider = selected_provider.value ? selected_provider : null;
1643
const selected_model = get_selected_model();
1644
if (selected_provider || selected_model) {
1645
const pinned = document.createElement("button");
1646
pinned.classList.add("pinned");
1647
if (selected_provider) pinned.dataset.provider = selected_provider.value;
1648
if (selected_model) pinned.dataset.model = selected_model.value;
1649
pinned.innerHTML = `
1650
<span>
1651
${selected_provider ? selected_provider.dataset.label || selected_provider.text : ""}
1652
${selected_provider && selected_model ? "/" : ""}
1653
${selected_model ? selected_model.dataset.label || selected_model.text : ""}
1654
</span>
1655
<i class="fa-regular fa-circle-xmark"></i>`;
1656
pinned.addEventListener("click", () => pin_container.removeChild(pinned));
1657
let all_pinned = pin_container.querySelectorAll(".pinned");
1658
while (all_pinned.length > 4) {
1659
pin_container.removeChild(all_pinned[0])
1660
all_pinned = pin_container.querySelectorAll(".pinned");
1661
}
1662
pin_container.appendChild(pinned);
1663
}
1664
});
1567
1665
1568
1666
function save_storage() {
1569
1667
let filename = `chat ${new Date().toLocaleString()}.json`.replaceAll(":", "-");
@@ -8,11 +8,12 @@ from flask import send_from_directory
8
8
from inspect import signature
9
9
10
10
from g4f import version, models
11
from g4f import get_last_provider, ChatCompletion
11
from g4f import get_last_provider, ChatCompletion, get_model_and_provider
12
12
from g4f.errors import VersionNotFoundError
13
13
from g4f.image import ImagePreview, ImageResponse, copy_images, ensure_images_dir, images_dir
14
14
from g4f.Provider import ProviderType, __providers__, __map__
15
15
from g4f.providers.base_provider import ProviderModelMixin
16
from g4f.providers.retry_provider import BaseRetryProvider
16
17
from g4f.providers.response import BaseConversation, FinishReason, SynthesizeData
17
18
from g4f.client.service import convert_to_provider
18
19
from g4f import debug
@@ -47,15 +48,15 @@ class Api:
47
48
48
49
@staticmethod
49
50
def get_providers() -> dict[str, str]:
50
return {
51
provider.__name__: (provider.label if hasattr(provider, "label") else provider.__name__)
52
+ (" (Image Generation)" if getattr(provider, "image_models", None) else "")
53
+ (" (Image Upload)" if getattr(provider, "default_vision_model", None) else "")
54
+ (" (WebDriver)" if "webdriver" in provider.get_parameters() else "")
55
+ (" (Auth)" if provider.needs_auth else "")
56
for provider in __providers__
57
if provider.working
58
}
51
return [{
52
"name": provider.__name__,
53
"label": provider.label if hasattr(provider, "label") else provider.__name__,
54
"parent": getattr(provider, "parent", None),
55
"image": getattr(provider, "image_models", None) is not None,
56
"vision": getattr(provider, "default_vision_model", None) is not None,
57
"webdriver": "webdriver" in provider.get_parameters(),
58
"auth": provider.needs_auth,
59
} for provider in __providers__ if provider.working]
59
60
60
61
@staticmethod
61
62
def get_version() -> dict:
@@ -115,43 +116,44 @@ class Api:
115
116
debug.log_handler = log_handler
116
117
proxy = os.environ.get("G4F_PROXY")
117
118
try:
118
result = ChatCompletion.create(**kwargs)
119
model, provider = get_model_and_provider(
120
kwargs.get("model"), kwargs.get("provider"),
121
stream=True,
122
ignore_stream=True
123
)
124
result = ChatCompletion.create(**{**kwargs, "model": model, "provider": provider})
119
125
first = True
120
if isinstance(result, ImageResponse):
126
for chunk in result:
121
127
if first:
122
128
first = False
123
yield self._format_json("provider", get_last_provider(True))
124
yield self._format_json("content", str(result))
125
else:
126
for chunk in result:
127
if first:
128
first = False
129
yield self._format_json("provider", get_last_provider(True))
130
if isinstance(chunk, BaseConversation):
131
if provider:
132
if provider not in conversations:
133
conversations[provider] = {}
134
conversations[provider][conversation_id] = chunk
135
yield self._format_json("conversation", conversation_id)
136
elif isinstance(chunk, Exception):
137
logger.exception(chunk)
138
yield self._format_json("message", get_error_message(chunk))
139
elif isinstance(chunk, ImagePreview):
140
yield self._format_json("preview", chunk.to_string())
141
elif isinstance(chunk, ImageResponse):
142
images = chunk
143
if download_images:
144
images = asyncio.run(copy_images(chunk.get_list(), chunk.get("cookies"), proxy))
145
images = ImageResponse(images, chunk.alt)
146
yield self._format_json("content", str(images))
147
elif isinstance(chunk, SynthesizeData):
148
yield self._format_json("synthesize", chunk.to_json())
149
elif not isinstance(chunk, FinishReason):
150
yield self._format_json("content", str(chunk))
151
if debug.logs:
152
for log in debug.logs:
153
yield self._format_json("log", str(log))
154
debug.logs = []
129
if isinstance(provider, BaseRetryProvider):
130
provider = provider.last_provider
131
yield self._format_json("provider", {**provider.get_dict(), "model": model})
132
if isinstance(chunk, BaseConversation):
133
if provider:
134
if provider not in conversations:
135
conversations[provider] = {}
136
conversations[provider][conversation_id] = chunk
137
yield self._format_json("conversation", conversation_id)
138
elif isinstance(chunk, Exception):
139
logger.exception(chunk)
140
yield self._format_json("message", get_error_message(chunk))
141
elif isinstance(chunk, ImagePreview):
142
yield self._format_json("preview", chunk.to_string())
143
elif isinstance(chunk, ImageResponse):
144
images = chunk
145
if download_images:
146
images = asyncio.run(copy_images(chunk.get_list(), chunk.get("cookies"), proxy))
147
images = ImageResponse(images, chunk.alt)
148
yield self._format_json("content", str(images))
149
elif isinstance(chunk, SynthesizeData):
150
yield self._format_json("synthesize", chunk.to_json())
151
elif not isinstance(chunk, FinishReason):
152
yield self._format_json("content", str(chunk))
153
if debug.logs:
154
for log in debug.logs:
155
yield self._format_json("log", str(log))
156
debug.logs = []
155
157
except Exception as e:
156
158
logger.exception(e)
157
159
yield self._format_json('error', get_error_message(e))
@@ -55,6 +55,12 @@ class Backend_Api(Api):
55
55
return jsonify(response)
56
56
return response
57
57
58
def jsonify_providers(**kwargs):
59
response = self.get_providers(**kwargs)
60
if isinstance(response, list):
61
return jsonify(response)
62
return response
63
58
64
self.routes = {
59
65
'/backend-api/v2/models': {
60
66
'function': jsonify_models,
@@ -65,7 +71,7 @@ class Backend_Api(Api):
65
71
'methods': ['GET']
66
72
},
67
73
'/backend-api/v2/providers': {
68
'function': self.get_providers,
74
'function': jsonify_providers,
69
75
'methods': ['GET']
70
76
},
71
77
'/backend-api/v2/version': {