XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet
公开
关注 0 Fork 0 Star 1
返回提交历史

XFEstudio/gpt4free

Update mobile template in UI

ff365ff5
hlohaus <983577+hlohaus@users.noreply.github.com>
提交于

代码差异

7 个文件 +357 -350
Modified g4f/Provider/hf/HuggingFaceMedia.py +27 -5
@@ -33,15 +33,24 @@ class HuggingFaceMedia(AsyncGeneratorProvider, ProviderModelMixin):
33 33 response = requests.get(url)
34 34 if response.ok:
35 35 models = response.json()
36 cls.models = [
37 model["id"]
36 providers = {
37 model["id"]: [
38 provider
39 for provider in model.get("inferenceProviderMapping")
40 if provider.get("status") == "live" and provider.get("task") in cls.tasks
41 ]
38 42 for model in models
39 43 if [
40 44 provider
41 45 for provider in model.get("inferenceProviderMapping")
42 46 if provider.get("status") == "live" and provider.get("task") in cls.tasks
43 47 ]
44 ]
48 }
49 new_models = []
50 for model, provider_keys in providers.items():
51 new_models.append(model)
52 for provider_data in provider_keys:
53 new_models.append(f"{model}:{provider_data.get('provider')}")
45 54 cls.task_mapping = {
46 55 model["id"]: [
47 56 provider.get("task")
@@ -49,6 +58,14 @@ class HuggingFaceMedia(AsyncGeneratorProvider, ProviderModelMixin):
49 58 ].pop()
50 59 for model in models
51 60 }
61 prepend_models = []
62 for model, provider_keys in providers.items():
63 task = cls.task_mapping.get(model)
64 if task == "text-to-video":
65 prepend_models.append(model)
66 for provider_data in provider_keys:
67 prepend_models.append(f"{model}:{provider_data.get('provider')}")
68 cls.models = prepend_models + [model for model in new_models if model not in prepend_models]
52 69 else:
53 70 cls.models = []
54 71 return cls.models
@@ -85,6 +102,9 @@ class HuggingFaceMedia(AsyncGeneratorProvider, ProviderModelMixin):
85 102 aspect_ratio: str = "1:1",
86 103 **kwargs
87 104 ):
105 selected_provider = None
106 if ":" in model:
107 model, selected_provider = model.split(":", 1)
88 108 provider_mapping = await cls.get_mapping(model, api_key)
89 109 headers = {
90 110 'Accept-Encoding': 'gzip, deflate',
@@ -98,6 +118,8 @@ class HuggingFaceMedia(AsyncGeneratorProvider, ProviderModelMixin):
98 118 async def generate(extra_data: dict, prompt: str):
99 119 last_response = None
100 120 for provider_key, provider in provider_mapping.items():
121 if selected_provider is not None and selected_provider != provider_key:
122 continue
101 123 provider_info = ProviderInfo(**{**cls.get_dict(), "label": f"HuggingFace ({provider_key})", "url": f"{cls.url}/{model}"})
102 124
103 125 api_base = f"https://router.huggingface.co/{provider_key}"
@@ -124,7 +146,7 @@ class HuggingFaceMedia(AsyncGeneratorProvider, ProviderModelMixin):
124 146 **extra_data
125 147 }
126 148 elif provider_key == "replicate":
127 url = f"{api_base}/v1/models/{provider_id}/prediction"
149 url = f"{api_base}/v1/models/{provider_id}/predictions"
128 150 data = {
129 151 "input": {
130 152 "prompt": prompt,
@@ -151,7 +173,7 @@ class HuggingFaceMedia(AsyncGeneratorProvider, ProviderModelMixin):
151 173 }
152 174
153 175 async with StreamSession(
154 headers=headers if provider_key == "free" or api_key is None else {**headers, "Authorization": f"Bearer {api_key}"},
176 headers=headers if provider_key == "hf-free" or api_key is None else {**headers, "Authorization": f"Bearer {api_key}"},
155 177 proxy=proxy,
156 178 timeout=timeout
157 179 ) as session:
Modified g4f/Provider/needs_auth/OpenaiChat.py +2 -1
@@ -384,7 +384,7 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
384 384 #f"Proofofwork: {'False' if proofofwork is None else proofofwork[:12]+'...'}",
385 385 #f"AccessToken: {'False' if cls._api_key is None else cls._api_key[:12]+'...'}",
386 386 )]
387 if action == "continue" and conversation.message_id is None:
387 if action is None or action == "variant" or action == "continue" and conversation.message_id is None:
388 388 action = "next"
389 389 data = {
390 390 "action": action,
@@ -400,6 +400,7 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
400 400 "client_contextual_info":{"is_dark_mode":False,"time_since_loaded":random.randint(20, 500),"page_height":578,"page_width":1850,"pixel_ratio":1,"screen_height":1080,"screen_width":1920},
401 401 "paragen_cot_summary_display_override":"allow"
402 402 }
403 print(data)
403 404 if conversation.conversation_id is not None:
404 405 data["conversation_id"] = conversation.conversation_id
405 406 debug.log(f"OpenaiChat: Use conversation: {conversation.conversation_id}")
Modified g4f/gui/client/index.html +109 -100
@@ -60,14 +60,13 @@
60 60 const user_image = '<img src="/static/img/user.png" alt="your avatar">';
61 61 const gpt_image = '<img src="/static/img/gpt.png" alt="your avatar">';
62 62 </script>
63 <script src="/static/js/highlight.min.js"></script>
63 <script src="/static/js/highlight.min.js" async></script>
64 64 <script>window.conversation_id = "{{chat_id}}"</script>
65 <title>g4f - gui</title>
65 <title>G4F Chat</title>
66 66 </head>
67 67 <body>
68 68 <div class="gradient"></div>
69 <div class="row">
70 <div class="box conversations">
69 <div class="sidebar shown">
71 70 <div class="top">
72 71 <button class="new_convo" onclick="new_conversation()">
73 72 <i class="fa-regular fa-plus"></i>
@@ -120,8 +119,8 @@
120 119 </div>
121 120 <div class="field">
122 121 <span class="label">Download generated images</span>
123 <input type="checkbox" id="download_images" checked/>
124 <label for="download_images" class="toogle" title="Download and save generated images to /generated_images"></label>
122 <input type="checkbox" id="download_media" checked/>
123 <label for="download_media" class="toogle" title="Download and save generated images, audios and videos"></label>
125 124 </div>
126 125 <div class="field">
127 126 <span class="label">Refine files with spaCy</span>
@@ -148,8 +147,8 @@
148 147 <textarea id="systemPrompt" placeholder="You are a helpful assistant." data-example="If you need to generate images, you can use the following format: ![keywords](/generate/filename.jpg). This will enable the use of an image generation tool."></textarea>
149 148 </div>
150 149 <div class="field box">
151 <label for="message-input-height" class="label" title="">Input max. height</label>
152 <input type="number" id="message-input-height" value="200"/>
150 <label for="userInput-height" class="label" title="">Input max. height</label>
151 <input type="number" id="userInput-height" value="200"/>
153 152 </div>
154 153 <div class="field box">
155 154 <label for="recognition-language" class="label" title="">Speech recognition language</label>
@@ -208,114 +207,124 @@
208 207 </button>
209 208 </div>
210 209 </div>
211 <div class="conversation">
210 <div class="chat-container">
211 <div class="chat-header box">
212 G4F Chat
213 </div>
212 214 <textarea id="chatPrompt" class="box" placeholder="System prompt"></textarea>
213 <div id="messages" class="box"></div>
214 215 <button class="slide-systemPrompt">
215 216 <i class="fa-solid fa-angles-up"></i>
216 217 </button>
217 <div class="media_player">
218 <i class="fa-regular fa-x"></i>
219 </div>
220 <div class="media-select hidden">
221 <label class="image-select" for="image" title="">
222 <input type="file" id="image" name="image" accept="image/*" required/>
223 <i class="fa-regular fa-image"></i>
224 </label>
225 <label class="capture-camera" for="camera">
226 <input type="file" id="camera" name="camera" accept="image/*" capture="camera" required/>
227 <i class="fa-solid fa-camera"></i>
228 </label>
229 <button class="close">
230 <i class="fa-solid fa-xmark"></i>
231 </button>
232 </div>
233 <div class="toolbar">
234 <div id="input-count" class="">
235 <button class="hide-input">
236 <i class="fa-solid fa-angles-down"></i>
237 </button>
238 <input type="checkbox" id="agree" name="agree" value="yes" checked>
239 <label for="agree" class="text" onclick="this.innerText='';">Scroll to bottom</label>
240 </div>
241 <div class="stop_generating stop_generating-hidden">
242 <button id="cancelButton">
243 <span>Stop Generating</span>
244 <i class="fa-solid fa-stop"></i>
245 </button>
218 <div class="chat-body" id="chatBody"></div>
219 <div class="chat-footer">
220 <div class="chat-toolbar">
221 <div id="input-count" class="">
222 <button class="hide-input">
223 <i class="fa-solid fa-angles-down"></i>
224 </button>
225 <input type="checkbox" id="agree" name="agree" value="yes" checked>
226 <label for="agree" class="text" onclick="this.innerText='';">Scroll to bottom</label>
227 </div>
228 <div class="stop_generating stop_generating-hidden">
229 <button id="cancelButton">
230 <span>Stop Generating</span>
231 <i class="fa-solid fa-stop"></i>
232 </button>
233 </div>
234 <div class="regenerate">
235 <button id="regenerateButton">
236 <span>Regenerate</span>
237 <i class="fa-solid fa-rotate"></i>
238 </button>
239 </div>
246 240 </div>
247 <div class="regenerate">
248 <button id="regenerateButton">
249 <span>Regenerate</span>
250 <i class="fa-solid fa-rotate"></i>
251 </button>
241 <div class="media-player">
242 <i class="fa-regular fa-x"></i>
252 243 </div>
253 </div>
254 <div class="user-input">
255 <div class="box input-area">
256 <textarea id="message-input" placeholder="Ask a question" cols="30" rows="10"
257 style="white-space: pre-wrap;resize: none;"></textarea>
258 <label class="file-label image-label">
244 <div class="media-select hidden">
245 <label class="image-select" for="image" title="">
246 <input type="file" id="image" name="image" accept="image/*" required/>
259 247 <i class="fa-regular fa-image"></i>
260 248 </label>
261 <label class="file-label" for="file">
262 <input type="file" id="file" name="file" accept=".txt, .html, .xml, .json, .js, .har, .sh, .py, .php, .css, .yaml, .sql, .log, .csv, .twig, .md, .pdf, .docx, .odt, .epub, .xlsx, .zip" required multiple/>
263 <i class="fa-solid fa-paperclip"></i>
264 </label>
265 <label class="micro-label" for="micro">
266 <i class="fa-solid fa-microphone-slash"></i>
249 <label class="capture-camera" for="camera">
250 <input type="file" id="camera" name="camera" accept="image/*" capture="camera" required/>
251 <i class="fa-solid fa-camera"></i>
267 252 </label>
268 <div id="send-button">
269 <i class="fa-solid fa-square-plus"></i>
270 <i class="fa-regular fa-paper-plane"></i>
271 <a href="" id="download" class="hidden"></a>
272 </div>
273 </div>
274 </div>
275 <div class="buttons">
276 <div class="field">
277 <button id="search">
278 <i class="fa-solid fa-search"></i>
253 <button class="close">
254 <i class="fa-solid fa-xmark"></i>
279 255 </button>
280 256 </div>
281 <div class="field">
282 <select name="model" id="model">
283 <option value="" selected="selected">Model: Default</option>
284 <option value="gpt-4">gpt-4</option>
285 <option value="gpt-4o">gpt-4o</option>
286 <option value="gpt-4o-mini">gpt-4o-mini</option>
287 <option value="llama-3.1-70b">llama-3.1-70b</option>
288 <option value="mixtral-8x7b">mixtral-8x7b</option>
289 <option value="claude-3.5-sonnet">claude-3.5-sonnet</option>
290 <option value="flux">flux (Image Generation)</option>
291 <option value="dall-e-3">dall-e-3 (Image Generation)</option>
292 <option disabled="disabled">----</option>
293 </select>
294 <select name="model2" id="model2" class="hidden model"></select>
295 <input type="text" id="model3" value="" class="hidden model" placeholder="Model:"/>
296 </div>
297 <div class="field">
298 <select name="provider" id="provider">
299 <option value="">Provider: Auto</option>
300 <option value="OpenaiChat">OpenAI ChatGPT</option>
301 <option value="Copilot">Microsoft Copilot</option>
302 <option value="Gemini">Google Gemini</option>
303 <option value="DDG">DuckDuckGo AI Chat</option>
304 <option value="Blackbox">Blackbox AI</option>
305 <option value="Custom Model">Custom Model</option>
306 <option disabled="disabled">----</option>
307 </select>
257 <div class="user-input">
258 <div class="input-area">
259 <textarea id="userInput" class="box" placeholder="Type a message..." cols="30" rows="10"
260 style="white-space: pre-wrap;resize: none;"></textarea>
261 <label class="file-label image-label">
262 <i class="fa-regular fa-image"></i>
263 </label>
264 <label class="file-label" for="file">
265 <input type="file" id="file" name="file" accept=".txt, .html, .xml, .json, .js, .har, .sh, .py, .php, .css, .yaml, .sql, .log, .csv, .twig, .md, .pdf, .docx, .odt, .epub, .xlsx, .zip" required multiple/>
266 <i class="fa-solid fa-paperclip"></i>
267 </label>
268 <label class="micro-label" for="micro">
269 <i class="fa-solid fa-microphone-slash"></i>
270 </label>
271 <div class="send-buttons">
272 <button id="addButton">
273 <i class="fa-solid fa-square-plus"></i>
274 Add
275 </button>
276 <button id="sendButton">
277 <i class="fa-regular fa-paper-plane"></i>
278 <a href="" id="download" class="hidden"></a>
279 Send
280 </button>
281 </div>
282 </div>
308 283 </div>
309 <div class="field">
310 <button id="pin">
311 <i class="fa-solid fa-thumbtack"></i>
312 </button>
284 <div class="chat-buttons">
285 <div class="field">
286 <button id="search">
287 <i class="fa-solid fa-search"></i>
288 </button>
289 </div>
290 <div class="field">
291 <select name="model" id="model">
292 <option value="" selected="selected">Model: Default</option>
293 <option value="gpt-4">gpt-4</option>
294 <option value="gpt-4o">gpt-4o</option>
295 <option value="gpt-4o-mini">gpt-4o-mini</option>
296 <option value="llama-3.1-70b">llama-3.1-70b</option>
297 <option value="mixtral-8x7b">mixtral-8x7b</option>
298 <option value="claude-3.5-sonnet">claude-3.5-sonnet</option>
299 <option value="flux">flux (Image Generation)</option>
300 <option value="dall-e-3">dall-e-3 (Image Generation)</option>
301 <option disabled="disabled">----</option>
302 </select>
303 <select name="model2" id="model2" class="hidden model"></select>
304 <input type="text" id="model3" value="" class="hidden model" placeholder="Model:"/>
305 </div>
306 <div class="field">
307 <select name="provider" id="provider">
308 <option value="">Provider: Auto</option>
309 <option value="OpenaiChat">OpenAI ChatGPT</option>
310 <option value="Copilot">Microsoft Copilot</option>
311 <option value="Gemini">Google Gemini</option>
312 <option value="DDG">DuckDuckGo AI Chat</option>
313 <option value="Blackbox">Blackbox AI</option>
314 <option value="Custom Model">Custom Model</option>
315 <option disabled="disabled">----</option>
316 </select>
317 </div>
318 <div class="field">
319 <button id="pin">
320 <i class="fa-solid fa-thumbtack"></i>
321 </button>
322 </div>
323 <div id="pin_container" class="field"></div>
313 324 </div>
314 <div id="pin_container" class="field"></div>
315 325 </div>
316 326 </div>
317 327 <div class="log hidden"></div>
318 </div>
319 328 <div class="mobile-sidebar">
320 329 <i class="fa-solid fa-bars"></i>
321 330 </div>
Modified g4f/gui/client/static/css/style.css +114 -160
@@ -63,6 +63,7 @@ body {
63 63 background: var(--background);
64 64 color: var(--colour-3);
65 65 height: 100vh;
66 display: flex;
66 67 }
67 68
68 69 body:not(.white) a:link,
@@ -104,8 +105,6 @@ body:not(.white) a:visited{
104 105 backdrop-filter: blur(20px);
105 106 -webkit-backdrop-filter: blur(20px);
106 107 background-color: var(--blur-bg);
107 height: 100%;
108 width: 100%;
109 108 border-radius: var(--border-radius-1);
110 109 border: 1px solid var(--blur-border);
111 110 }
@@ -131,67 +130,20 @@ body:not(.white) a:visited{
131 130 text-decoration: underline;
132 131 }
133 132
134 .conversations {
135 max-width: 300px;
136 padding: var(--section-gap);
137 overflow: auto;
138 flex-shrink: 0;
139 display: flex;
140 flex-direction: column;
141 justify-content: space-between;
142 }
143
144 .conversation {
145 width: 100%;
146 height: 100%;
147 display: flex;
148 flex-direction: column;
149 gap: 5px;
150 }
151
152 .conversation #messages {
153 width: 100%;
154 height: 100%;
155 display: flex;
156 flex-direction: column;
157 overflow: auto;
158 overflow-wrap: break-word;
159 padding-bottom: 10px;
160 background-color: transparent;
161 }
162
163 .conversation .user-input {
133 .chat-footer .user-input {
164 134 margin-bottom: 4px;
165 135 }
166 136
167 .conversation .user-input input {
168 font-size: 15px;
169 width: 100%;
170 height: 100%;
171 padding: 12px 15px;
172 background: none;
173 border: none;
174 outline: none;
175 color: var(--colour-3);
176 }
177
178 .conversation .user-input input::placeholder {
137 .chat-footer .user-input input::placeholder {
179 138 color: var(--user-input)
180 139 }
181 140
182 .conversations {
183 display: flex;
184 flex-direction: column;
185 gap: 10px;
186 padding: 10px;
187 }
188
189 .conversations .title {
141 .sidebar .title {
190 142 font-size: 14px;
191 143 font-weight: 500;
192 144 }
193 145
194 .conversations .convo {
146 .sidebar .convo {
195 147 padding: 8px 12px;
196 148 display: flex;
197 149 gap: 10px;
@@ -202,7 +154,7 @@ body:not(.white) a:visited{
202 154 border-radius: var(--border-radius-1);
203 155 }
204 156
205 .conversations .convo .left {
157 .sidebar .convo .left {
206 158 width: 100%;
207 159 cursor: pointer;
208 160 display: flex;
@@ -210,20 +162,20 @@ body:not(.white) a:visited{
210 162 gap: 4px;
211 163 }
212 164
213 .conversations .convo .fa-ellipsis-vertical {
165 .sidebar .convo .fa-ellipsis-vertical {
214 166 position: absolute;
215 167 right: 8px;
216 168 width: 14px;
217 169 text-align: center;
218 170 }
219 171
220 .conversations .convo .choise {
172 .sidebar .convo .choise {
221 173 position: absolute;
222 174 right: 8px;
223 175 background-color: var(--blur-bg);
224 176 }
225 177
226 .conversations i, .bottom_buttons i, .mem0 button i {
178 .sidebar i, .bottom_buttons i, .mem0 button i {
227 179 color: var(--conversations);
228 180 cursor: pointer;
229 181 }
@@ -486,22 +438,22 @@ body:not(.white) a:visited{
486 438 border-left: .25em solid var(--colour-4);
487 439 }
488 440
489 .media_player {
441 .media-player {
490 442 display: none;
491 443 }
492 444
493 .media_player audio {
445 .media-player audio {
494 446 right: 28px;
495 447 position: absolute;
496 448 top: -4px;
497 449 z-index: 900;
498 450 }
499 451
500 .media_player.show {
452 .media-player.show {
501 453 display: block;
502 454 }
503 455
504 .media_player .fa-x {
456 .media-player .fa-x {
505 457 position: absolute;
506 458 right: 8px;
507 459 top: 8px;
@@ -568,7 +520,7 @@ body:not(.white) a:visited{
568 520 font-size: 14px;
569 521 }
570 522
571 .toolbar {
523 .chat-toolbar {
572 524 position: relative;
573 525 }
574 526
@@ -591,14 +543,14 @@ input-count .text {
591 543 display: none;
592 544 }
593 545
594 .stop_generating, .toolbar .regenerate {
546 .stop_generating, .chat-toolbar .regenerate {
595 547 position: absolute;
596 548 top: 0;
597 549 right: 0;
598 550 animation: show_popup 0.4s;
599 551 }
600 552
601 .stop_generating button, .toolbar .regenerate button, button.regenerate_button, button.continue_button, button.options_button {
553 .stop_generating button, .chat-toolbar .regenerate button, button.regenerate_button, button.continue_button, button.options_button {
602 554 backdrop-filter: blur(20px);
603 555 -webkit-backdrop-filter: blur(20px);
604 556 background-color: var(--blur-bg);
@@ -614,13 +566,13 @@ input-count .text {
614 566 height: 28px;
615 567 }
616 568
617 .toolbar .regenerate {
569 .chat-toolbar .regenerate {
618 570 left: 50%;
619 571 transform: translateX(-50%);
620 572 right: auto;
621 573 }
622 574
623 .toolbar .regenerate span, .regenerate_button span, .continue_button span, .options_button div {
575 .chat-toolbar .regenerate span, .regenerate_button span, .continue_button span, .options_button div {
624 576 display: none;
625 577 }
626 578
@@ -665,12 +617,12 @@ input-count .text {
665 617 .stop_generating {
666 618 right: 4px;
667 619 }
668 .toolbar .regenerate span {
620 .chat-toolbar .regenerate span {
669 621 display: block;
670 622 }
671 623 }
672 624
673 .toolbar .hide-input {
625 .chat-toolbar .hide-input {
674 626 background: transparent;
675 627 border: none;
676 628 color: var(--colour-3);
@@ -744,14 +696,7 @@ label[for="micro"] {
744 696 }
745 697 }
746 698
747 #messages form {
748 position: absolute;
749 width: 100%;
750 background: var(--button-hover);
751 z-index: 2000;
752 }
753
754 .buttons input[type="checkbox"],
699 .chat-buttons input[type="checkbox"],
755 700 .settings input[type="checkbox"],
756 701 form input[type="checkbox"] {
757 702 height: 0;
@@ -759,7 +704,7 @@ form input[type="checkbox"] {
759 704 display: none;
760 705 }
761 706
762 .buttons label,
707 .chat-buttons label,
763 708 .settings label.toogle,
764 709 form label.toogle {
765 710 cursor: pointer;
@@ -780,7 +725,7 @@ form label.toogle {
780 725 margin-left: 0;
781 726 }
782 727
783 .buttons label:after,
728 .chat-buttons label:after,
784 729 .settings label.toogle:after,
785 730 form label.toogle:after {
786 731 content: "";
@@ -795,7 +740,7 @@ form label.toogle:after {
795 740 transition: 0.33s;
796 741 }
797 742
798 .buttons input:checked+label,
743 .chat-buttons input:checked+label,
799 744 .settings input:checked+label,
800 745 form input:checked+label {
801 746 background: var(--accent);
@@ -810,13 +755,13 @@ form input:checked+label {
810 755 width: 100%;
811 756 }
812 757
813 .buttons input:checked+label:after,
758 .chat-buttons input:checked+label:after,
814 759 .settings input:checked+label:after,
815 760 form input:checked+label:after {
816 761 left: calc(100% - 5px - 20px);
817 762 }
818 763
819 .buttons {
764 .chat-buttons {
820 765 display: flex;
821 766 align-items: center;
822 767 justify-content: left;
@@ -874,7 +819,7 @@ select, input.model {
874 819 border-radius: 25px;
875 820 }
876 821
877 .buttons button, button.regenerate_button, button.continue_button, button.options_button {
822 .chat-buttons button, button.regenerate_button, button.continue_button, button.options_button {
878 823 border-radius: 8px;
879 824 backdrop-filter: blur(20px);
880 825 cursor: pointer;
@@ -895,7 +840,7 @@ button.options_button {
895 840 margin-left: auto;
896 841 }
897 842
898 .buttons button.pinned span {
843 .chat-buttons button.pinned span {
899 844 max-width: 160px;
900 845 overflow: hidden;
901 846 text-wrap: nowrap;
@@ -904,7 +849,7 @@ button.options_button {
904 849 text-overflow: ellipsis;
905 850 }
906 851
907 .buttons button.pinned i {
852 .chat-buttons button.pinned i {
908 853 position: absolute;
909 854 top: 10px;
910 855 right: 6px;
@@ -912,10 +857,10 @@ button.options_button {
912 857
913 858 select:hover,
914 859 input.model:hover
915 .buttons button:hover,
860 .chat-buttons button:hover,
916 861 .stop_generating button:hover,
917 .toolbar .regenerate button:hover,
918 #send-button:hover {
862 .chat-toolbar .regenerate button:hover,
863 .send-buttons button:hover {
919 864 background-color: var(--button-hover);
920 865 }
921 866
@@ -932,7 +877,6 @@ input.model:hover
932 877
933 878 #systemPrompt, #chatPrompt, .settings textarea, form textarea {
934 879 font-size: 15px;
935 width: 100%;
936 880 color: var(--colour-3);
937 881 outline: none;
938 882 transition: max-height 0.15s ease-out;
@@ -993,7 +937,8 @@ input.model:hover
993 937
994 938 .slide-systemPrompt {
995 939 position: absolute;
996 top: 0;
940 top: 42px;
941 z-index: 1;
997 942 padding: var(--inner-gap) 10px;
998 943 border: none;
999 944 background: transparent;
@@ -1028,8 +973,6 @@ input.model:hover
1028 973
1029 974 .input-area {
1030 975 display: flex;
1031 align-items: center;
1032 padding: 10px;
1033 976 }
1034 977
1035 978 .info {
@@ -1080,7 +1023,7 @@ input.model:hover
1080 1023 text-decoration: none;
1081 1024 }
1082 1025
1083 .conversations .top {
1026 .sidebar .top {
1084 1027 display: flex;
1085 1028 flex-direction: column;
1086 1029 gap: var(--inner-gap);
@@ -1158,7 +1101,6 @@ ul {
1158 1101 }
1159 1102
1160 1103 .mobile-sidebar {
1161 display: none;
1162 1104 position: fixed;
1163 1105 z-index: 1000;
1164 1106 top: 10px;
@@ -1190,9 +1132,8 @@ ul {
1190 1132 padding-top: 10px;
1191 1133 }
1192 1134 @media screen and (max-width: 990px) {
1193 .conversations {
1194 display: none;
1195 width: 100%;
1135 .sidebar {
1136 width: 300px;
1196 1137 max-width: none;
1197 1138 }
1198 1139
@@ -1201,17 +1142,13 @@ ul {
1201 1142 padding-top: 18px;
1202 1143 }
1203 1144
1204 .buttons {
1145 .chat-buttons {
1205 1146 align-items: flex-start;
1206 1147 flex-wrap: wrap;
1207 1148 gap: 8px;
1208 1149 padding-left: 4px;
1209 1150 }
1210 1151
1211 .mobile-sidebar {
1212 display: flex;
1213 }
1214
1215 1152 #chatPrompt {
1216 1153 padding-left: 30px;
1217 1154 }
@@ -1221,23 +1158,10 @@ ul {
1221 1158 }
1222 1159 }
1223 1160
1224 .shown {
1225 display: flex;
1226 }
1227
1228 .conversation .user-input textarea {
1229 font-size: 15px;
1230 width: 100%;
1231 height: 100%;
1232 padding: 12px var(--inner-gap);
1233 background: none;
1234 border: none;
1235 outline: none;
1236 color: var(--colour-3);
1237
1238 resize: vertical;
1239 max-height: 200px;
1240 min-height: 100px;
1161 .sidebar.shown {
1162 width: 300px;
1163 padding: 15px;
1164 margin-right: 10px;
1241 1165 }
1242 1166
1243 1167 /* style for hljs copy */
@@ -1370,24 +1294,6 @@ ul {
1370 1294 color: var(--colour-3);
1371 1295 }
1372 1296
1373 #send-button {
1374 border: 1px dashed #e4d4ffa6;
1375 border-radius: 4px;
1376 cursor: pointer;
1377 position: absolute;
1378 bottom: 8px;
1379 right: 4px;
1380 padding: 4px;
1381 }
1382
1383 #send-button:hover {
1384 border: 1px solid #e4d4ffc9;
1385 }
1386
1387 #send-button i {
1388 padding: 2px;
1389 }
1390
1391 1297 form textarea {
1392 1298 height: 20px;
1393 1299 min-height: 20px;
@@ -1538,16 +1444,15 @@ form .field.saved .fa-xmark {
1538 1444 font-size: 15px;
1539 1445 }
1540 1446
1541 #message-input {
1542 height: 90px;
1447 #userInput {
1448 min-height: 94px;
1449 height: 94px;
1543 1450 flex: 1;
1544 padding: 10px;
1545 padding-left: 24px;
1546 border-radius: 20px;
1547 font-size: 14px;
1548 margin-right: 10px;
1549 outline: none;
1451 padding: 10px 30px;
1452 color: var(--colour-3);
1550 1453 max-height: 200px;
1454 outline: none;
1455 font-size: 14px;
1551 1456 }
1552 1457
1553 1458 .hidden, input.hidden {
@@ -1566,10 +1471,10 @@ form .field.saved .fa-xmark {
1566 1471
1567 1472 @media print {
1568 1473 #chatPrompt:placeholder-shown,
1569 .conversations,
1474 .sidebar,
1570 1475 .conversation .user-input,
1571 .conversation .buttons,
1572 .conversation .toolbar,
1476 .conversation .chat-buttons,
1477 .conversation .chat-toolbar,
1573 1478 .conversation .slide-systemPrompt,
1574 1479 .message .count i,
1575 1480 .message .assistant,
@@ -1584,21 +1489,70 @@ form .field.saved .fa-xmark {
1584 1489 }
1585 1490 }
1586 1491
1587 /* Media queries for mobile devices */
1588 @media (max-width: 768px) {
1589 .row {
1590 flex-direction: column;
1591 }
1592
1593 .conversations, .settings {
1594 width: 100%;
1595 max-width: 100%;
1596 margin: 0;
1597 }
1598 }
1599
1600 1492 @media (max-width: 480px) {
1601 1493 .info .convo-title {
1602 1494 font-size: 12px;
1603 1495 }
1604 1496 }
1497
1498 .sidebar {
1499 display: flex;
1500 flex-direction: column;
1501 transition: width 0.3s ease-in-out;
1502 overflow: hidden;
1503 width: 0;
1504 }
1505 .chat-container {
1506 flex: 1;
1507 display: flex;
1508 flex-direction: column;
1509 overflow: hidden;
1510 }
1511 .chat-header {
1512 padding: 10px;
1513 font-weight: 500;
1514 white-space: nowrap;
1515 text-overflow: ellipsis;
1516 overflow: hidden;
1517 }
1518 @media only screen and (min-width: 40em) {
1519 .sidebar {
1520 width: 300px;
1521 padding: 15px;
1522 margin-right: 10px;
1523 }
1524 }
1525 .chat-body {
1526 flex: 1;
1527 padding: 10px;
1528 overflow-y: auto;
1529 display: flex;
1530 flex-direction: column;
1531 }
1532 .chat-footer {
1533 display: flex;
1534 padding: 4px 0;
1535 flex-direction: column;
1536 }
1537 .chat-footer input {
1538 flex: 1;
1539 padding: 10px;
1540 border: none;
1541 border-radius: 5px;
1542 outline: none;
1543 }
1544 .send-buttons {
1545 display: flex;
1546 gap: 10px;
1547 flex-direction: column;
1548 }
1549 .chat-footer .send-buttons button {
1550 background: var(--blur-bg);
1551 color: white;
1552 border: none;
1553 padding: 12px 15px;
1554 margin: 0 10px;
1555 border-radius: 5px;
1556 cursor: pointer;
1557 border: 1px dashed #e4d4ffa6;
1558 }
Modified g4f/gui/client/static/js/chat.v1.js +93 -83
@@ -1,12 +1,13 @@
1 1 const colorThemes = document.querySelectorAll('[name="theme"]');
2 const message_box = document.getElementById(`messages`);
3 const messageInput = document.getElementById(`message-input`);
2 const chatBody = document.getElementById(`chatBody`);
3 const userInput = document.getElementById("userInput");
4 4 const box_conversations = document.querySelector(`.top`);
5 5 const stop_generating = document.querySelector(`.stop_generating`);
6 6 const regenerate_button = document.querySelector(`.regenerate`);
7 const sidebar = document.querySelector(".conversations");
7 const sidebar = document.querySelector(".sidebar");
8 8 const sidebar_button = document.querySelector(".mobile-sidebar");
9 const sendButton = document.getElementById("send-button");
9 const sendButton = document.getElementById("sendButton");
10 const addButton = document.getElementById("addButton");
10 11 const imageInput = document.querySelector(".image-label");
11 12 const mediaSelect = document.querySelector(".media-select");
12 13 const imageSelect = document.getElementById("image");
@@ -20,7 +21,7 @@ const modelProvider = document.getElementById("model2");
20 21 const custom_model = document.getElementById("model3");
21 22 const chatPrompt = document.getElementById("chatPrompt");
22 23 const settings = document.querySelector(".settings");
23 const chat = document.querySelector(".conversation");
24 const chat = document.querySelector(".chat-container");
24 25 const album = document.querySelector(".images");
25 26 const log_storage = document.querySelector(".log");
26 27 const switchInput = document.getElementById("switch");
@@ -47,11 +48,11 @@ let wakeLock = null;
47 48 let countTokensEnabled = true;
48 49 let reloadConversation = true;
49 50
50 messageInput.addEventListener("blur", () => {
51 userInput.addEventListener("blur", () => {
51 52 document.documentElement.scrollTop = 0;
52 53 });
53 54
54 messageInput.addEventListener("focus", () => {
55 userInput.addEventListener("focus", () => {
55 56 document.documentElement.scrollTop = document.documentElement.scrollHeight;
56 57 });
57 58
@@ -216,8 +217,8 @@ const get_message_el = (el) => {
216 217 }
217 218
218 219 function register_message_images() {
219 message_box.querySelectorAll(`.loading-indicator`).forEach((el) => el.remove());
220 message_box.querySelectorAll(`.message img:not([alt="your avatar"])`).forEach(async (el) => {
220 chatBody.querySelectorAll(`.loading-indicator`).forEach((el) => el.remove());
221 chatBody.querySelectorAll(`.message img:not([alt="your avatar"])`).forEach(async (el) => {
221 222 if (!el.complete) {
222 223 const indicator = document.createElement("span");
223 224 indicator.classList.add("loading-indicator");
@@ -264,7 +265,7 @@ function register_message_images() {
264 265 }
265 266
266 267 const register_message_buttons = async () => {
267 message_box.querySelectorAll(".message .content .provider").forEach(async (el) => {
268 chatBody.querySelectorAll(".message .content .provider").forEach(async (el) => {
268 269 if (el.dataset.click) {
269 270 return
270 271 }
@@ -288,7 +289,7 @@ const register_message_buttons = async () => {
288 289 });
289 290 });
290 291
291 message_box.querySelectorAll(".message .fa-xmark").forEach(async (el) => {
292 chatBody.querySelectorAll(".message .fa-xmark").forEach(async (el) => {
292 293 if (el.dataset.click) {
293 294 return
294 295 }
@@ -306,7 +307,7 @@ const register_message_buttons = async () => {
306 307 });
307 308 });
308 309
309 message_box.querySelectorAll(".message .fa-clipboard").forEach(async (el) => {
310 chatBody.querySelectorAll(".message .fa-clipboard").forEach(async (el) => {
310 311 if (el.dataset.click) {
311 312 return
312 313 }
@@ -330,7 +331,7 @@ const register_message_buttons = async () => {
330 331 });
331 332 })
332 333
333 message_box.querySelectorAll(".message .fa-file-export").forEach(async (el) => {
334 chatBody.querySelectorAll(".message .fa-file-export").forEach(async (el) => {
334 335 if (el.dataset.click) {
335 336 return
336 337 }
@@ -355,7 +356,7 @@ const register_message_buttons = async () => {
355 356 });
356 357 })
357 358
358 message_box.querySelectorAll(".message .fa-volume-high").forEach(async (el) => {
359 chatBody.querySelectorAll(".message .fa-volume-high").forEach(async (el) => {
359 360 if (el.dataset.click) {
360 361 return
361 362 }
@@ -366,7 +367,7 @@ const register_message_buttons = async () => {
366 367 if (message_el.dataset.synthesize_url) {
367 368 el.classList.add("active");
368 369 setTimeout(()=>el.classList.remove("active"), 2000);
369 const media_player = document.querySelector(".media_player");
370 const media_player = document.querySelector(".media-player");
370 371 if (!media_player.classList.contains("show")) {
371 372 media_player.classList.add("show");
372 373 audio = new Audio(message_el.dataset.synthesize_url);
@@ -382,7 +383,7 @@ const register_message_buttons = async () => {
382 383 });
383 384 });
384 385
385 message_box.querySelectorAll(".message .regenerate_button").forEach(async (el) => {
386 chatBody.querySelectorAll(".message .regenerate_button").forEach(async (el) => {
386 387 if (el.dataset.click) {
387 388 return
388 389 }
@@ -395,7 +396,7 @@ const register_message_buttons = async () => {
395 396 });
396 397 });
397 398
398 message_box.querySelectorAll(".message .continue_button").forEach(async (el) => {
399 chatBody.querySelectorAll(".message .continue_button").forEach(async (el) => {
399 400 if (el.dataset.click) {
400 401 return
401 402 }
@@ -411,7 +412,7 @@ const register_message_buttons = async () => {
411 412 });
412 413 });
413 414
414 message_box.querySelectorAll(".message .fa-whatsapp").forEach(async (el) => {
415 chatBody.querySelectorAll(".message .fa-whatsapp").forEach(async (el) => {
415 416 if (el.dataset.click) {
416 417 return
417 418 }
@@ -422,7 +423,7 @@ const register_message_buttons = async () => {
422 423 });
423 424 });
424 425
425 message_box.querySelectorAll(".message .fa-print").forEach(async (el) => {
426 chatBody.querySelectorAll(".message .fa-print").forEach(async (el) => {
426 427 if (el.dataset.click) {
427 428 return
428 429 }
@@ -430,7 +431,7 @@ const register_message_buttons = async () => {
430 431 el.addEventListener("click", async () => {
431 432 const message_el = get_message_el(el);
432 433 el.classList.add("clicked");
433 message_box.scrollTop = 0;
434 chatBody.scrollTop = 0;
434 435 message_el.classList.add("print");
435 436 setTimeout(() => {
436 437 el.classList.remove("clicked");
@@ -440,7 +441,7 @@ const register_message_buttons = async () => {
440 441 });
441 442 });
442 443
443 message_box.querySelectorAll(".message .reasoning_title").forEach(async (el) => {
444 chatBody.querySelectorAll(".message .reasoning_title").forEach(async (el) => {
444 445 if (el.dataset.click) {
445 446 return
446 447 }
@@ -468,15 +469,15 @@ const delete_conversations = async () => {
468 469 };
469 470
470 471 const handle_ask = async (do_ask_gpt = true) => {
471 messageInput.style.height = "82px";
472 messageInput.focus();
472 userInput.style.height = "82px";
473 userInput.focus();
473 474 await scroll_to_bottom();
474 475
475 let message = messageInput.value.trim();
476 let message = userInput.value.trim();
476 477 if (message.length <= 0) {
477 478 return;
478 479 }
479 messageInput.value = "";
480 userInput.value = "";
480 481 await count_input()
481 482 await add_conversation(window.conversation_id);
482 483
@@ -518,7 +519,7 @@ const handle_ask = async (do_ask_gpt = true) => {
518 519 </div>
519 520 </div>
520 521 `;
521 message_box.appendChild(message_el);
522 chatBody.appendChild(message_el);
522 523 highlight(message_el);
523 524 if (do_ask_gpt) {
524 525 const all_pinned = document.querySelectorAll(".buttons button.pinned")
@@ -583,10 +584,10 @@ stop_generating.addEventListener("click", async () => {
583 584 await load_conversation(window.conversation_id, false);
584 585 });
585 586
586 document.querySelector(".media_player .fa-x").addEventListener("click", ()=>{
587 const media_player = document.querySelector(".media_player");
587 document.querySelector(".media-player .fa-x").addEventListener("click", ()=>{
588 const media_player = document.querySelector(".media-player");
588 589 media_player.classList.remove("show");
589 const audio = document.querySelector(".media_player audio");
590 const audio = document.querySelector(".media-player audio");
590 591 media_player.removeChild(audio);
591 592 });
592 593
@@ -948,7 +949,7 @@ const ask_gpt = async (message_id, message_index = -1, regenerate = false, provi
948 949 await lazy_scroll_to_bottom();
949 950 }
950 951 if (countTokensEnabled) {
951 let count_total = message_box.querySelector('.count_total');
952 let count_total = chatBody.querySelector('.count_total');
952 953 count_total ? count_total.parentElement.removeChild(count_total) : null;
953 954 }
954 955
@@ -970,9 +971,9 @@ const ask_gpt = async (message_id, message_index = -1, regenerate = false, provi
970 971 </div>
971 972 `;
972 973 if (message_index == -1) {
973 message_box.appendChild(message_el);
974 chatBody.appendChild(message_el);
974 975 } else {
975 parent_message = message_box.querySelector(`.message[data-index="${message_index}"]`);
976 parent_message = chatBody.querySelector(`.message[data-index="${message_index}"]`);
976 977 if (!parent_message) {
977 978 return;
978 979 }
@@ -1166,7 +1167,7 @@ const ask_gpt = async (message_id, message_index = -1, regenerate = false, provi
1166 1167
1167 1168 async function scroll_to_bottom() {
1168 1169 window.scrollTo(0, 0);
1169 message_box.scrollTop = message_box.scrollHeight;
1170 chatBody.scrollTop = chatBody.scrollHeight;
1170 1171 }
1171 1172
1172 1173 async function lazy_scroll_to_bottom() {
@@ -1193,10 +1194,10 @@ const clear_conversations = async () => {
1193 1194 };
1194 1195
1195 1196 const clear_conversation = async () => {
1196 let messages = message_box.getElementsByTagName(`div`);
1197 let messages = chatBody.getElementsByTagName(`div`);
1197 1198
1198 1199 while (messages.length > 0) {
1199 message_box.removeChild(messages[0]);
1200 chatBody.removeChild(messages[0]);
1200 1201 }
1201 1202 };
1202 1203
@@ -1301,7 +1302,7 @@ const set_conversation = async (conversation_id) => {
1301 1302 await clear_conversation();
1302 1303 await load_conversation(conversation_id);
1303 1304 load_conversations();
1304 hide_sidebar();
1305 hide_sidebar(true);
1305 1306 };
1306 1307
1307 1308 const new_conversation = async () => {
@@ -1372,10 +1373,11 @@ const load_conversation = async (conversation_id, scroll=true) => {
1372 1373 return;
1373 1374 }
1374 1375 let title = conversation.title || conversation.new_title;
1375 title = title ? `${title} - g4f` : window.title;
1376 title = title ? `${title} - G4F` : window.title;
1376 1377 if (title) {
1377 1378 document.title = title;
1378 1379 }
1380 document.querySelector(".chat-header").innerText = title;
1379 1381
1380 1382 if (chatPrompt) {
1381 1383 chatPrompt.value = conversation.system || "";
@@ -1521,19 +1523,19 @@ const load_conversation = async (conversation_id, scroll=true) => {
1521 1523 }
1522 1524 }
1523 1525
1524 message_box.innerHTML = elements.join("");
1526 chatBody.innerHTML = elements.join("");
1525 1527 [...new Set(providers)].forEach(async (provider) => {
1526 1528 await load_provider_parameters(provider);
1527 1529 });
1528 1530 await register_message_buttons();
1529 highlight(message_box);
1531 highlight(chatBody);
1530 1532 regenerate_button.classList.remove("regenerate-hidden");
1531 1533
1532 1534 if (scroll && document.querySelector("#input-count input").checked) {
1533 message_box.scrollTo({ top: message_box.scrollHeight, behavior: "smooth" });
1535 chatBody.scrollTo({ top: chatBody.scrollHeight, behavior: "smooth" });
1534 1536
1535 1537 setTimeout(() => {
1536 message_box.scrollTop = message_box.scrollHeight;
1538 chatBody.scrollTop = chatBody.scrollHeight;
1537 1539 }, 500);
1538 1540 return true;
1539 1541 }
@@ -1726,15 +1728,15 @@ const load_conversations = async () => {
1726 1728 box_conversations.innerHTML += html.join("");
1727 1729 };
1728 1730
1729 const hide_input = document.querySelector(".toolbar .hide-input");
1731 const hide_input = document.querySelector(".chat-toolbar .hide-input");
1730 1732 hide_input.addEventListener("click", async (e) => {
1731 1733 const icon = hide_input.querySelector("i");
1732 1734 const func = icon.classList.contains("fa-angles-down") ? "add" : "remove";
1733 1735 const remv = icon.classList.contains("fa-angles-down") ? "remove" : "add";
1734 1736 icon.classList[func]("fa-angles-up");
1735 1737 icon.classList[remv]("fa-angles-down");
1736 document.querySelector(".conversation .user-input").classList[func]("hidden");
1737 document.querySelector(".conversation .buttons").classList[func]("hidden");
1738 document.querySelector(".chat-footer .user-input").classList[func]("hidden");
1739 document.querySelector(".chat-footer .buttons").classList[func]("hidden");
1738 1740 });
1739 1741
1740 1742 const uuid = () => {
@@ -1757,8 +1759,10 @@ function get_message_id() {
1757 1759 return BigInt(`0b${unix}${random_bytes}`).toString();
1758 1760 };
1759 1761
1760 async function hide_sidebar() {
1761 sidebar.classList.remove("shown");
1762 async function hide_sidebar(remove_shown=false) {
1763 if (remove_shown) {
1764 sidebar.classList.remove("shown");
1765 }
1762 1766 sidebar_button.classList.remove("rotated");
1763 1767 settings.classList.add("hidden");
1764 1768 chat.classList.remove("hidden");
@@ -1778,9 +1782,11 @@ async function hide_settings() {
1778 1782 window.addEventListener('popstate', hide_sidebar, false);
1779 1783
1780 1784 sidebar_button.addEventListener("click", async () => {
1781 if (sidebar.classList.contains("shown")) {
1785 if (sidebar.classList.contains("shown") || sidebar_button.classList.contains("rotated")) {
1782 1786 await hide_sidebar();
1783 1787 chat.classList.remove("hidden");
1788 sidebar.classList.remove("shown");
1789 sidebar_button.classList.remove("rotated");
1784 1790 } else {
1785 1791 await show_menu();
1786 1792 chat.classList.add("hidden");
@@ -1893,7 +1899,7 @@ const load_settings_storage = async () => {
1893 1899 const say_hello = async () => {
1894 1900 tokens = [`Hello`, `!`, ` How`,` can`, ` I`,` assist`,` you`,` today`,`?`]
1895 1901
1896 message_box.innerHTML += `
1902 chatBody.innerHTML += `
1897 1903 <div class="message">
1898 1904 <div class="assistant">
1899 1905 ${gpt_image}
@@ -1913,6 +1919,9 @@ const say_hello = async () => {
1913 1919 }
1914 1920
1915 1921 function count_tokens(model, text, prompt_tokens = 0) {
1922 if (!text) {
1923 return 0;
1924 }
1916 1925 if (model) {
1917 1926 if (window.llamaTokenizer)
1918 1927 if (model.startsWith("llama") || model.startsWith("codellama")) {
@@ -1988,7 +1997,7 @@ function update_message(content_map, message_id, content = null, scroll = true)
1988 1997 }, 100));
1989 1998 };
1990 1999
1991 let countFocus = messageInput;
2000 let countFocus = userInput;
1992 2001 const count_input = async () => {
1993 2002 if (countTokensEnabled && countFocus.value) {
1994 2003 if (window.matchMedia("(pointer:coarse)")) {
@@ -2000,14 +2009,14 @@ const count_input = async () => {
2000 2009 inputCount.innerText = "";
2001 2010 }
2002 2011 };
2003 messageInput.addEventListener("keyup", count_input);
2012 userInput.addEventListener("keyup", count_input);
2004 2013 chatPrompt.addEventListener("keyup", count_input);
2005 2014 chatPrompt.addEventListener("focus", function() {
2006 2015 countFocus = chatPrompt;
2007 2016 count_input();
2008 2017 });
2009 2018 chatPrompt.addEventListener("input", function() {
2010 countFocus = messageInput;
2019 countFocus = userInput;
2011 2020 count_input();
2012 2021 });
2013 2022
@@ -2037,9 +2046,9 @@ async function on_load() {
2037 2046 let chat_url = new URL(window.location.href)
2038 2047 let chat_params = new URLSearchParams(chat_url.search);
2039 2048 if (chat_params.get("prompt")) {
2040 messageInput.value = chat_params.get("prompt");
2041 messageInput.style.height = messageInput.scrollHeight + "px";
2042 messageInput.focus();
2049 userInput.value = chat_params.get("prompt");
2050 userInput.style.height = userInput.scrollHeight + "px";
2051 userInput.focus();
2043 2052 //await handle_ask();
2044 2053 }
2045 2054 } else if (/\/chat\/[?$]/.test(window.location.href)) {
@@ -2089,10 +2098,10 @@ const load_provider_option = (input, provider_name) => {
2089 2098 async function on_api() {
2090 2099 load_version();
2091 2100 let prompt_lock = false;
2092 messageInput.addEventListener("keydown", async (evt) => {
2101 userInput.addEventListener("keydown", async (evt) => {
2093 2102 if (prompt_lock) return;
2094 2103 // If not mobile and not shift enter
2095 let do_enter = messageInput.value.endsWith("\n\n\n\n");
2104 let do_enter = userInput.value.endsWith("\n\n\n\n");
2096 2105 if (do_enter || !window.matchMedia("(pointer:coarse)").matches && evt.keyCode === 13 && !evt.shiftKey) {
2097 2106 evt.preventDefault();
2098 2107 console.log("pressed enter");
@@ -2100,10 +2109,10 @@ async function on_api() {
2100 2109 setTimeout(()=>prompt_lock=false, 3000);
2101 2110 await handle_ask(!do_enter);
2102 2111 } else {
2103 messageInput.style.height = messageInput.scrollHeight + "px";
2112 userInput.style.height = userInput.scrollHeight + "px";
2104 2113 }
2105 2114 });
2106 sendButton.querySelector(".fa-paper-plane").addEventListener(`click`, async () => {
2115 sendButton.addEventListener(`click`, async () => {
2107 2116 console.log("clicked send");
2108 2117 if (prompt_lock) return;
2109 2118 prompt_lock = true;
@@ -2111,11 +2120,11 @@ async function on_api() {
2111 2120 stop_recognition();
2112 2121 await handle_ask();
2113 2122 });
2114 sendButton.querySelector(".fa-square-plus").addEventListener(`click`, async () => {
2123 addButton.addEventListener(`click`, async () => {
2115 2124 stop_recognition();
2116 2125 await handle_ask(false);
2117 2126 });
2118 messageInput.addEventListener(`click`, async () => {
2127 userInput.addEventListener(`click`, async () => {
2119 2128 stop_recognition();
2120 2129 });
2121 2130
@@ -2145,6 +2154,7 @@ async function on_api() {
2145 2154 <option value="G4F">G4F framework</option>
2146 2155 <option value="Gemini">Gemini Provider</option>
2147 2156 <option value="HuggingFace">HuggingFace</option>
2157 <option value="HuggingFaceMedia">HuggingFace (Image/Video Generation)</option>
2148 2158 <option value="HuggingSpace">HuggingSpace</option>
2149 2159 <option value="HuggingChat">HuggingChat</option>`;
2150 2160 document.getElementById("pin").disabled = true;
@@ -2158,8 +2168,8 @@ async function on_api() {
2158 2168 }
2159 2169 });
2160 2170 login_urls = {
2161 "HuggingFace": ["HuggingFace", "https://huggingface.co/settings/tokens", []],
2162 "HuggingSpace": ["HuggingSpace", "https://huggingface.co/spaces/roxky/g4f-new?get_gpu_token=true", []],
2171 "HuggingFace": ["HuggingFace", "https://huggingface.co/settings/tokens", ["HuggingFaceMedia"]],
2172 "HuggingSpace": ["HuggingSpace", "", []],
2163 2173 };
2164 2174 } else {
2165 2175 providers = await api("providers")
@@ -2299,13 +2309,13 @@ async function on_api() {
2299 2309 slide_systemPrompt_icon.classList[checked ? "remove": "add"]("fa-angles-up");
2300 2310 slide_systemPrompt_icon.classList[checked ? "add": "remove"]("fa-angles-down");
2301 2311 });
2302 const messageInputHeight = document.getElementById("message-input-height");
2303 if (messageInputHeight) {
2304 if (messageInputHeight.value) {
2305 messageInput.style.maxHeight = `${messageInputHeight.value}px`;
2312 const userInputHeight = document.getElementById("message-input-height");
2313 if (userInputHeight) {
2314 if (userInputHeight.value) {
2315 userInput.style.maxHeight = `${userInputHeight.value}px`;
2306 2316 }
2307 messageInputHeight.addEventListener('change', async () => {
2308 messageInput.style.maxHeight = `${messageInputHeight.value}px`;
2317 userInputHeight.addEventListener('change', async () => {
2318 userInput.style.maxHeight = `${userInputHeight.value}px`;
2309 2319 });
2310 2320 }
2311 2321 const darkMode = document.getElementById("darkMode");
@@ -2333,7 +2343,7 @@ async function load_version() {
2333 2343 document.title = window.title;
2334 2344 }
2335 2345 let text = "version ~ "
2336 if (versions["version"] != versions["latest_version"]) {
2346 if (versions["latest_version"] && versions["version"] != versions["latest_version"]) {
2337 2347 let release_url = 'https://github.com/xtekky/gpt4free/releases/latest';
2338 2348 let title = `New version: ${versions["latest_version"]}`;
2339 2349 text += `<a href="${release_url}" target="_blank" title="${title}">${versions["version"]}</a> 🆕`;
@@ -2468,11 +2478,11 @@ function connectToSSE(url, do_refine, bucket_id) {
2468 2478 }
2469 2479 appStorage.setItem(`bucket:${bucket_id}`, data.size);
2470 2480 inputCount.innerText = "Files are loaded successfully";
2471 if (!messageInput.value) {
2472 messageInput.value = JSON.stringify({bucket_id: bucket_id});
2481 if (!userInput.value) {
2482 userInput.value = JSON.stringify({bucket_id: bucket_id});
2473 2483 handle_ask(false);
2474 2484 } else {
2475 messageInput.value += (messageInput.value ? "\n" : "") + JSON.stringify({bucket_id: bucket_id}) + "\n";
2485 userInput.value += (userInput.value ? "\n" : "") + JSON.stringify({bucket_id: bucket_id}) + "\n";
2476 2486 paperclip.classList.remove("blink");
2477 2487 fileInput.value = "";
2478 2488 }
@@ -2773,15 +2783,15 @@ async function load_provider_models(provider=null) {
2773 2783 };
2774 2784 providerSelect.addEventListener("change", () => {
2775 2785 load_provider_models()
2776 messageInput.focus();
2786 userInput.focus();
2777 2787 });
2778 modelSelect.addEventListener("change", () => messageInput.focus());
2779 modelProvider.addEventListener("change", () => messageInput.focus());
2788 modelSelect.addEventListener("change", () => userInput.focus());
2789 modelProvider.addEventListener("change", () => userInput.focus());
2780 2790 custom_model.addEventListener("change", () => {
2781 2791 if (!custom_model.value) {
2782 2792 load_provider_models();
2783 2793 }
2784 messageInput.focus();
2794 userInput.focus();
2785 2795 });
2786 2796
2787 2797 document.getElementById("pin").addEventListener("click", async () => {
@@ -2817,7 +2827,7 @@ switchInput.addEventListener("change", () => {
2817 2827 });
2818 2828 searchButton.addEventListener("click", async () => {
2819 2829 switchInput.click();
2820 messageInput.focus();
2830 userInput.focus();
2821 2831 });
2822 2832
2823 2833 function save_storage(settings=false) {
@@ -2910,20 +2920,20 @@ if (SpeechRecognition) {
2910 2920 let buffer;
2911 2921 let lastDebounceTranscript;
2912 2922 recognition.onstart = function() {
2913 startValue = messageInput.value;
2923 startValue = userInput.value;
2914 2924 lastDebounceTranscript = "";
2915 messageInput.readOnly = true;
2925 userInput.readOnly = true;
2916 2926 buffer = "";
2917 2927 };
2918 2928 recognition.onend = function() {
2919 2929 if (buffer) {
2920 messageInput.value = `${startValue ? startValue + "\n" : ""}${buffer}`;
2930 userInput.value = `${startValue ? startValue + "\n" : ""}${buffer}`;
2921 2931 }
2922 2932 if (microLabel.classList.contains("recognition")) {
2923 2933 recognition.start();
2924 2934 } else {
2925 messageInput.readOnly = false;
2926 messageInput.focus();
2935 userInput.readOnly = false;
2936 userInput.focus();
2927 2937 }
2928 2938 };
2929 2939 recognition.onresult = function(event) {
@@ -2951,7 +2961,7 @@ if (SpeechRecognition) {
2951 2961 if (microLabel.classList.contains("recognition")) {
2952 2962 microLabel.classList.remove("recognition");
2953 2963 recognition.stop();
2954 messageInput.value = `${startValue ? startValue + "\n" : ""}${buffer}`;
2964 userInput.value = `${startValue ? startValue + "\n" : ""}${buffer}`;
2955 2965 count_input();
2956 2966 return true;
2957 2967 }
Modified g4f/image/__init__.py +10 -1
@@ -13,10 +13,18 @@ try:
13 13 except ImportError:
14 14 has_requirements = False
15 15
16 from ..providers.helper import filter_none
16 17 from ..typing import ImageType, Union, Image
17 18 from ..errors import MissingRequirementsError
18 19
19 ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'webp', 'webm', 'svg', 'mp3', 'wav', 'mp4', 'flac', 'opus', 'ogg', 'mkv'}
20 ALLOWED_EXTENSIONS = {
21 # Image
22 'png', 'jpg', 'jpeg', 'gif', 'webp',
23 # Audio
24 'wav', 'mp3', 'flac', 'opus', 'ogg',
25 # Video
26 'mkv', 'webm', 'mp4'
27 }
20 28
21 29 EXTENSIONS_MAP: dict[str, str] = {
22 30 "image/png": "png",
@@ -260,6 +268,7 @@ def to_input_audio(audio: ImageType, filename: str = None) -> str:
260 268 raise ValueError("Invalid input audio")
261 269
262 270 def use_aspect_ratio(extra_data: dict, aspect_ratio: str) -> Image:
271 extra_data = filter_none(**extra_data)
263 272 if aspect_ratio == "1:1":
264 273 extra_data = {
265 274 "width": 1024,
Modified g4f/image/copy_images.py +2 -0
@@ -39,6 +39,8 @@ def get_source_url(image: str, default: str = None) -> str:
39 39 return default
40 40
41 41 def secure_filename(filename: str) -> str:
42 if filename is None:
43 return None
42 44 # Keep letters, numbers, basic punctuation and all Unicode chars
43 45 filename = re.sub(
44 46 r'[^\w.,_-]+',