返回提交历史
Modified
g4f/Provider/ARTA.py
+6
-4
Modified
g4f/Provider/You.py
+2
-2
Modified
g4f/Provider/hf/HuggingFaceMedia.py
+3
-1
Modified
g4f/Provider/needs_auth/DeepSeekAPI.py
+2
-2
Modified
g4f/api/__init__.py
+18
-10
Modified
g4f/client/__init__.py
+14
-12
Modified
g4f/errors.py
+1
-1
Modified
g4f/gui/client/index.html
+8
-4
Modified
g4f/gui/client/static/css/style.css
+19
-14
Modified
g4f/gui/client/static/js/chat.v1.js
+89
-28
Modified
g4f/gui/server/backend_api.py
+32
-0
Modified
g4f/gui/server/website.py
+15
-4
Modified
g4f/image/__init__.py
+20
-1
Modified
g4f/image/copy_images.py
+10
-5
XFEstudio/gpt4free
Add chat share function
ae1fae7e
代码差异
14 个文件
+239
-88
@@ -176,6 +176,7 @@ class ARTA(AsyncGeneratorProvider, ProviderModelMixin):
176
176
# Step 3: Check Generation Status
177
177
status_url = cls.status_check_url.format(record_id=record_id)
178
178
counter = 0
179
start_time = time.time()
179
180
while True:
180
181
async with session.get(status_url, headers=headers, proxy=proxy) as status_response:
181
182
status_data = await status_response.json()
@@ -183,14 +184,15 @@ class ARTA(AsyncGeneratorProvider, ProviderModelMixin):
183
184
184
185
if status == "DONE":
185
186
image_urls = [image["url"] for image in status_data.get("response", [])]
186
yield Reasoning(status="Finished")
187
duration = time.time() - start_time
188
yield Reasoning(label="Generated", status=f"{n} image(s) in {duration:.2f}s")
187
189
yield ImageResponse(images=image_urls, alt=prompt)
188
190
return
189
191
elif status in ("IN_QUEUE", "IN_PROGRESS"):
190
yield Reasoning(status=("Waiting" if status == "IN_QUEUE" else "Generating") + "." * counter)
191
await asyncio.sleep(2) # Poll every 5 seconds
192
yield Reasoning(label=("Waiting" if status == "IN_QUEUE" else "Generating"), status="." * counter)
193
await asyncio.sleep(2) # Poll every 2 seconds
192
194
counter += 1
193
195
if counter > 3:
194
counter = 0
196
counter = 1
195
197
else:
196
198
raise ResponseError(f"Image generation failed with status: {status}")
@@ -7,7 +7,7 @@ import uuid
7
7
from ..typing import AsyncResult, Messages, ImageType, Cookies
8
8
from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
9
9
from .helper import format_prompt
10
from ..image import EXTENSIONS_MAP, to_bytes, is_accepted_format
10
from ..image import MEDIA_TYPE_MAP, to_bytes, is_accepted_format
11
11
from ..requests import StreamSession, FormData, raise_for_status, get_nodriver
12
12
from ..providers.response import ImagePreview, ImageResponse
13
13
from ..cookies import get_cookies
@@ -159,7 +159,7 @@ class You(AsyncGeneratorProvider, ProviderModelMixin):
159
159
upload_nonce = await response.text()
160
160
data = FormData()
161
161
content_type = is_accepted_format(file)
162
filename = f"image.{EXTENSIONS_MAP[content_type]}" if filename is None else filename
162
filename = f"image.{MEDIA_TYPE_MAP[content_type]}" if filename is None else filename
163
163
data.add_field('file', file, content_type=content_type, filename=filename)
164
164
async with client.post(
165
165
f"{cls.url}/api/upload",
@@ -202,7 +202,9 @@ class HuggingFaceMedia(AsyncGeneratorProvider, ProviderModelMixin):
202
202
background_tasks.add(task)
203
203
task.add_done_callback(background_tasks.discard)
204
204
while background_tasks:
205
yield Reasoning(label="Generating", status=f"{time.time() - started:.2f}s")
205
diff = time.time() - started
206
if diff > 1:
207
yield Reasoning(label="Generating", status=f"{diff:.2f}s")
206
208
await asyncio.sleep(0.2)
207
209
provider_info, media_response = await task
208
210
yield Reasoning(label="Finished", status=f"{time.time() - started:.2f}s")
@@ -72,12 +72,12 @@ class DeepSeekAPI(AsyncAuthedProvider, ProviderModelMixin):
72
72
):
73
73
if chunk['type'] == 'thinking':
74
74
if not is_thinking:
75
yield Reasoning(None, "Is thinking...")
75
yield Reasoning(status="Is thinking...")
76
76
is_thinking = time.time()
77
77
yield Reasoning(chunk['content'])
78
78
elif chunk['type'] == 'text':
79
79
if is_thinking:
80
yield Reasoning(None, f"Thought for {time.time() - is_thinking:.2f}s")
80
yield Reasoning(status=f"Thought for {time.time() - is_thinking:.2f}s")
81
81
is_thinking = 0
82
82
if chunk['content']:
83
83
yield chunk['content']
@@ -38,7 +38,7 @@ import g4f.debug
38
38
from g4f.client import AsyncClient, ChatCompletion, ImagesResponse, convert_to_provider
39
39
from g4f.providers.response import BaseConversation, JsonConversation
40
40
from g4f.client.helper import filter_none
41
from g4f.image import is_data_an_media
41
from g4f.image import is_data_an_media, EXTENSIONS_MAP
42
42
from g4f.image.copy_images import images_dir, copy_media, get_source_url
43
43
from g4f.errors import ProviderNotFoundError, ModelNotFoundError, MissingAuthError, NoValidHarFileError
44
44
from g4f.cookies import read_cookie_files, get_cookies_dir
@@ -179,7 +179,7 @@ class Api:
179
179
return ErrorResponse.from_message("G4F API key required", HTTP_401_UNAUTHORIZED)
180
180
if AppConfig.g4f_api_key is None or not secrets.compare_digest(AppConfig.g4f_api_key, user_g4f_api_key):
181
181
return ErrorResponse.from_message("Invalid G4F API key", HTTP_403_FORBIDDEN)
182
elif not AppConfig.demo and not path.startswith("/images/"):
182
elif not AppConfig.demo and not path.startswith("/images/") and not path.startswith("/media/"):
183
183
if user_g4f_api_key is not None:
184
184
if not secrets.compare_digest(AppConfig.g4f_api_key, user_g4f_api_key):
185
185
return ErrorResponse.from_message("Invalid G4F API key", HTTP_403_FORBIDDEN)
@@ -189,7 +189,7 @@ class Api:
189
189
except HTTPException as e:
190
190
return ErrorResponse.from_message(e.detail, e.status_code, e.headers)
191
191
response = await call_next(request)
192
response.headers["X-Username"] = username
192
response.headers["x-user"] = username
193
193
return response
194
194
return await call_next(request)
195
195
@@ -220,7 +220,7 @@ class Api:
220
220
return HTMLResponse('g4f API: Go to '
221
221
'<a href="/v1/models">models</a>, '
222
222
'<a href="/v1/chat/completions">chat/completions</a>, or '
223
'<a href="/v1/images/generate">images/generate</a> <br><br>'
223
'<a href="/v1/media/generate">media/generate</a> <br><br>'
224
224
'Open Swagger UI at: '
225
225
'<a href="/docs">/docs</a>')
226
226
@@ -259,7 +259,7 @@ class Api:
259
259
provider: ProviderType = ProviderUtils.convert[provider]
260
260
if not hasattr(provider, "get_models"):
261
261
models = []
262
elif credentials is not None:
262
elif credentials is not None and credentials.credentials != "secret":
263
263
models = provider.get_models(api_key=credentials.credentials)
264
264
else:
265
265
models = provider.get_models()
@@ -404,6 +404,7 @@ class Api:
404
404
HTTP_404_NOT_FOUND: {"model": ErrorResponseModel},
405
405
HTTP_500_INTERNAL_SERVER_ERROR: {"model": ErrorResponseModel},
406
406
}
407
@self.app.post("/v1/media/generate", responses=responses)
407
408
@self.app.post("/v1/images/generate", responses=responses)
408
409
@self.app.post("/v1/images/generations", responses=responses)
409
410
async def generate_image(
@@ -555,9 +556,14 @@ class Api:
555
556
HTTP_200_OK: {"content": {"image/*": {}}},
556
557
HTTP_404_NOT_FOUND: {}
557
558
})
558
async def get_image(filename, request: Request):
559
@self.app.get("/media/{filename}", responses={
560
HTTP_200_OK: {"content": {"image/*": {}, "audio/*": {}}, "video/*": {}},
561
HTTP_404_NOT_FOUND: {}
562
})
563
async def get_media(filename, request: Request):
559
564
target = os.path.join(images_dir, os.path.basename(filename))
560
565
ext = os.path.splitext(filename)[1][1:]
566
mime_type = EXTENSIONS_MAP.get(ext)
561
567
stat_result = SimpleNamespace()
562
568
stat_result.st_size = 0
563
569
if os.path.isfile(target):
@@ -565,12 +571,14 @@ class Api:
565
571
stat_result.st_mtime = int(f"{filename.split('_')[0]}") if filename.startswith("1") else 0
566
572
headers = {
567
573
"cache-control": "public, max-age=31536000",
568
"content-type": f"image/{ext.replace('jpg', 'jpeg') or 'jpeg'}",
569
574
"last-modified": formatdate(stat_result.st_mtime, usegmt=True),
570
575
"etag": f'"{hashlib.md5(filename.encode()).hexdigest()}"',
571
576
**({
572
577
"content-length": str(stat_result.st_size),
573
} if stat_result.st_size else {})
578
} if stat_result.st_size else {}),
579
**({} if mime_type is None else {
580
"content-type": mime_type,
581
})
574
582
}
575
583
response = FileResponse(
576
584
target,
@@ -584,13 +592,13 @@ class Api:
584
592
return NotModifiedResponse(response.headers)
585
593
except KeyError:
586
594
pass
587
if not os.path.isfile(target):
595
if not os.path.isfile(target) and mime_type is not None:
588
596
source_url = get_source_url(str(request.query_params))
589
597
ssl = None
590
598
if source_url is None:
591
599
backend_url = os.environ.get("G4F_BACKEND_URL")
592
600
if backend_url:
593
source_url = f"{backend_url}/images/{filename}"
601
source_url = f"{backend_url}/media/{filename}"
594
602
ssl = False
595
603
if source_url is not None:
596
604
try:
@@ -13,7 +13,7 @@ from ..image.copy_images import copy_media
13
13
from ..typing import Messages, ImageType
14
14
from ..providers.types import ProviderType, BaseRetryProvider
15
15
from ..providers.response import *
16
from ..errors import NoImageResponseError
16
from ..errors import NoMediaResponseError
17
17
from ..providers.retry_provider import IterListProvider
18
18
from ..providers.asyncio import to_sync_generator
19
19
from ..Provider.needs_auth import BingCreateImages, OpenaiAccount
@@ -268,6 +268,7 @@ class Client(BaseClient):
268
268
super().__init__(**kwargs)
269
269
self.chat: Chat = Chat(self, provider)
270
270
self.images: Images = Images(self, image_provider)
271
self.media: Images = Images(self, image_provider)
271
272
272
273
class Completions:
273
274
def __init__(self, client: Client, provider: Optional[ProviderType] = None):
@@ -406,7 +407,7 @@ class Images:
406
407
else:
407
408
response = await self._generate_image_response(provider_handler, provider_name, model, prompt, **kwargs)
408
409
409
if isinstance(response, ImageResponse):
410
if isinstance(response, MediaResponse):
410
411
return await self._process_image_response(
411
412
response,
412
413
model,
@@ -417,8 +418,8 @@ class Images:
417
418
if response is None:
418
419
if error is not None:
419
420
raise error
420
raise NoImageResponseError(f"No image response from {provider_name}")
421
raise NoImageResponseError(f"Unexpected response type: {type(response)}")
421
raise NoMediaResponseError(f"No image response from {provider_name}")
422
raise NoMediaResponseError(f"Unexpected response type: {type(response)}")
422
423
423
424
async def _generate_image_response(
424
425
self,
@@ -428,7 +429,7 @@ class Images:
428
429
prompt: str,
429
430
prompt_prefix: str = "Generate a image: ",
430
431
**kwargs
431
) -> ImageResponse:
432
) -> MediaResponse:
432
433
messages = [{"role": "user", "content": f"{prompt_prefix}{prompt}"}]
433
434
response = None
434
435
if hasattr(provider_handler, "create_async_generator"):
@@ -439,7 +440,7 @@ class Images:
439
440
prompt=prompt,
440
441
**kwargs
441
442
):
442
if isinstance(item, ImageResponse):
443
if isinstance(item, MediaResponse):
443
444
response = item
444
445
break
445
446
elif hasattr(provider_handler, "create_completion"):
@@ -450,7 +451,7 @@ class Images:
450
451
prompt=prompt,
451
452
**kwargs
452
453
):
453
if isinstance(item, ImageResponse):
454
if isinstance(item, MediaResponse):
454
455
response = item
455
456
break
456
457
else:
@@ -501,17 +502,17 @@ class Images:
501
502
else:
502
503
response = await self._generate_image_response(provider_handler, provider_name, model, prompt, **kwargs)
503
504
504
if isinstance(response, ImageResponse):
505
if isinstance(response, MediaResponse):
505
506
return await self._process_image_response(response, model, provider_name, response_format, proxy)
506
507
if response is None:
507
508
if error is not None:
508
509
raise error
509
raise NoImageResponseError(f"No image response from {provider_name}")
510
raise NoImageResponseError(f"Unexpected response type: {type(response)}")
510
raise NoMediaResponseError(f"No image response from {provider_name}")
511
raise NoMediaResponseError(f"Unexpected response type: {type(response)}")
511
512
512
513
async def _process_image_response(
513
514
self,
514
response: ImageResponse,
515
response: MediaResponse,
515
516
model: str,
516
517
provider: str,
517
518
response_format: Optional[str] = None,
@@ -533,7 +534,7 @@ class Images:
533
534
else:
534
535
# Save locally for None (default) case
535
536
images = await copy_media(response.get_list(), response.get("cookies"), proxy)
536
images = [Image.model_construct(url=f"/images/{os.path.basename(image)}", revised_prompt=response.alt) for image in images]
537
images = [Image.model_construct(url=f"/media/{os.path.basename(image)}", revised_prompt=response.alt) for image in images]
537
538
538
539
return ImagesResponse.model_construct(
539
540
created=int(time.time()),
@@ -552,6 +553,7 @@ class AsyncClient(BaseClient):
552
553
super().__init__(**kwargs)
553
554
self.chat: AsyncChat = AsyncChat(self, provider)
554
555
self.images: AsyncImages = AsyncImages(self, image_provider)
556
self.media: AsyncImages = self.images
555
557
556
558
class AsyncChat:
557
559
completions: AsyncCompletions
@@ -34,7 +34,7 @@ class NestAsyncioError(MissingRequirementsError):
34
34
class MissingAuthError(Exception):
35
35
...
36
36
37
class NoImageResponseError(Exception):
37
class NoMediaResponseError(Exception):
38
38
...
39
39
40
40
class ResponseError(Exception):
@@ -49,7 +49,7 @@
49
49
<script src="https://cdn.jsdelivr.net/npm/gpt-tokenizer/dist/cl100k_base.js" async></script>
50
50
<script src="https://cdn.jsdelivr.net/npm/gpt-tokenizer/dist/o200k_base.js" async></script>
51
51
</template>
52
<script>
52
<script async>
53
53
if (localStorage.getItem("countTokens") != "false") {
54
54
const template = document.head.querySelector('template');
55
55
document.head.appendChild(template.content);
@@ -61,10 +61,14 @@
61
61
const gpt_image = '<img src="/static/img/gpt.png" alt="your avatar">';
62
62
</script>
63
63
<script src="/static/js/highlight.min.js" async></script>
64
<script>window.conversation_id = "{{chat_id}}"</script>
64
<script>window.conversation_id = "{{conversation_id}}"</script>
65
<script>window.chat_id = "{{chat_id}}"</script>
65
66
<title>G4F Chat</title>
66
67
</head>
67
68
<body>
69
<script async>
70
localStorage.getItem("darkMode") == "false" ? document.body.classList.add("white") : null;
71
</script>
68
72
<div class="gradient"></div>
69
73
<div class="sidebar shown">
70
74
<div class="top">
@@ -118,7 +122,7 @@
118
122
<label for="hide-systemPrompt" class="toogle" title="For more space on phones"></label>
119
123
</div>
120
124
<div class="field">
121
<span class="label">Download generated images</span>
125
<span class="label">Download generated images, audios and videos</span>
122
126
<input type="checkbox" id="download_media" checked/>
123
127
<label for="download_media" class="toogle" title="Download and save generated images, audios and videos"></label>
124
128
</div>
@@ -212,7 +216,7 @@
212
216
G4F Chat
213
217
</div>
214
218
<textarea id="chatPrompt" class="box" placeholder="System prompt"></textarea>
215
<button class="slide-systemPrompt">
219
<button class="slide-header">
216
220
<i class="fa-solid fa-angles-up"></i>
217
221
</button>
218
222
<div class="chat-body" id="chatBody"></div>
@@ -321,8 +321,9 @@ body:not(.white) a:visited{
321
321
white-space: pre-wrap;
322
322
}
323
323
324
.message .content img{
324
.message .content img, .message .content video{
325
325
max-width: 400px;
326
max-height: 400px;
326
327
}
327
328
328
329
.message .content .audio{
@@ -667,8 +668,9 @@ input-count .text {
667
668
.micro-label {
668
669
cursor: pointer;
669
670
position: absolute;
670
top: 10px;
671
left: 10px;
671
top: 8px;
672
left: 8px;
673
padding: 2px;
672
674
}
673
675
674
676
.file-label:has(> input:valid),
@@ -683,11 +685,12 @@ input-count .text {
683
685
}
684
686
685
687
label.image-label {
686
top: 32px;
688
top: 30px;
687
689
}
688
690
689
691
label[for="micro"] {
690
top: 54px;
692
top: 50px;
693
padding: 4px;
691
694
}
692
695
693
696
@media (pointer:none), (pointer:coarse) {
@@ -872,7 +875,7 @@ input.model:hover
872
875
min-height: 59px;
873
876
height: 59px;
874
877
resize: vertical;
875
padding: var(--inner-gap) var(--section-gap);
878
padding: var(--inner-gap) 28px;
876
879
}
877
880
878
881
#systemPrompt, #chatPrompt, .settings textarea, form textarea {
@@ -935,15 +938,15 @@ input.model:hover
935
938
top: auto !important;
936
939
}
937
940
938
.slide-systemPrompt {
941
.slide-header {
939
942
position: absolute;
940
top: 42px;
943
top: 0;
941
944
z-index: 1;
942
padding: var(--inner-gap) 10px;
945
padding: 10px;
943
946
border: none;
944
947
background: transparent;
945
948
cursor: pointer;
946
height: 49px;
949
height: 40px;
947
950
color: var(--colour-3);
948
951
}
949
952
@@ -1159,7 +1162,7 @@ ul {
1159
1162
}
1160
1163
1161
1164
.sidebar.shown {
1162
width: 300px;
1165
width: 400px;
1163
1166
padding: 15px;
1164
1167
margin-right: 10px;
1165
1168
}
@@ -1475,7 +1478,7 @@ form .field.saved .fa-xmark {
1475
1478
.conversation .user-input,
1476
1479
.conversation .chat-buttons,
1477
1480
.conversation .chat-toolbar,
1478
.conversation .slide-systemPrompt,
1481
.conversation .slide-header,
1479
1482
.message .count i,
1480
1483
.message .assistant,
1481
1484
.message .user {
@@ -1509,7 +1512,7 @@ form .field.saved .fa-xmark {
1509
1512
overflow: hidden;
1510
1513
}
1511
1514
.chat-header {
1512
padding: 10px;
1515
padding: 10px 28px;
1513
1516
font-weight: 500;
1514
1517
white-space: nowrap;
1515
1518
text-overflow: ellipsis;
@@ -1549,10 +1552,12 @@ form .field.saved .fa-xmark {
1549
1552
.chat-footer .send-buttons button {
1550
1553
background: var(--blur-bg);
1551
1554
color: white;
1552
border: none;
1553
1555
padding: 12px 15px;
1554
1556
margin: 0 10px;
1555
1557
border-radius: 5px;
1556
1558
cursor: pointer;
1557
1559
border: 1px dashed #e4d4ffa6;
1560
}
1561
.chat-footer .send-buttons button:hover {
1562
border-style: solid;
1558
1563
}
@@ -63,8 +63,6 @@ appStorage = window.localStorage || {
63
63
length: 0
64
64
}
65
65
66
appStorage.getItem("darkMode") == "false" ? document.body.classList.add("white") : null;
67
68
66
let markdown_render = (content) => escapeHtml(content);
69
67
if (window.markdownit) {
70
68
const markdown = window.markdownit();
@@ -81,7 +79,7 @@ if (window.markdownit) {
81
79
.replaceAll('<code>', '<code class="language-plaintext">')
82
80
.replaceAll('<i class="', '<i class="')
83
81
.replaceAll('"></i>', '"></i>')
84
.replaceAll('<video controls src="', '<video controls width="400" src="')
82
.replaceAll('<video controls src="', '<video controls loop src="')
85
83
.replaceAll('"></video>', '"></video>')
86
84
.replaceAll('<audio controls src="', '<audio controls src="')
87
85
.replaceAll('"></audio>', '"></audio>')
@@ -581,7 +579,7 @@ stop_generating.addEventListener("click", async () => {
581
579
}
582
580
}
583
581
}
584
await load_conversation(window.conversation_id, false);
582
await safe_load_conversation(window.conversation_id, false);
585
583
});
586
584
587
585
document.querySelector(".media-player .fa-x").addEventListener("click", ()=>{
@@ -831,7 +829,7 @@ async function add_message_chunk(message, message_id, provider, scroll, finish_m
831
829
for (const [key, value] of Object.entries(message.conversation)) {
832
830
conversation.data[key] = value;
833
831
}
834
await save_conversation(conversation_id, conversation);
832
await save_conversation(conversation_id, conversation, false);
835
833
} else if (message.type == "provider") {
836
834
provider_storage[message_id] = message.provider;
837
835
let provider_el = content_map.content.querySelector('.provider');
@@ -1289,6 +1287,7 @@ const delete_conversation = async (conversation_id) => {
1289
1287
};
1290
1288
1291
1289
const set_conversation = async (conversation_id) => {
1290
window.chat_id = null;
1292
1291
if (title_ids_storage[conversation_id]) {
1293
1292
conversation_id = title_ids_storage[conversation_id];
1294
1293
}
@@ -1300,7 +1299,7 @@ const set_conversation = async (conversation_id) => {
1300
1299
window.conversation_id = conversation_id;
1301
1300
1302
1301
await clear_conversation();
1303
await load_conversation(conversation_id);
1302
await load_conversation(await get_conversation(conversation_id));
1304
1303
load_conversations();
1305
1304
hide_sidebar(true);
1306
1305
};
@@ -1364,15 +1363,14 @@ function merge_messages(message1, message2) {
1364
1363
// console.log(merge_messages("1 != 2", "```python\n1 != 2;"));
1365
1364
// console.log(merge_messages("1 != 2;\n1 != 3;\n", "1 != 2;\n1 != 3;\n"));
1366
1365
1367
const load_conversation = async (conversation_id, scroll=true) => {
1368
let conversation = await get_conversation(conversation_id);
1369
let messages = conversation?.items || [];
1370
console.debug("Conversation:", conversation)
1371
1366
const load_conversation = async (conversation, scroll=true) => {
1372
1367
if (!conversation) {
1373
1368
return;
1374
1369
}
1375
let title = conversation.title || conversation.new_title;
1370
let messages = conversation?.items || [];
1371
console.debug("Conversation:", conversation.id)
1372
1373
let title = conversation.new_title || conversation.title;
1376
1374
title = title ? `${title} - G4F` : window.title;
1377
1375
if (title) {
1378
1376
document.title = title;
@@ -1550,7 +1548,8 @@ async function safe_load_conversation(conversation_id, scroll=true) {
1550
1548
}
1551
1549
}
1552
1550
if (!is_running) {
1553
return await load_conversation(conversation_id, scroll);
1551
let conversation = await get_conversation(conversation_id);
1552
return await load_conversation(conversation, scroll);
1554
1553
}
1555
1554
}
1556
1555
@@ -1563,9 +1562,10 @@ async function get_conversation(conversation_id) {
1563
1562
1564
1563
async function save_conversation(conversation_id, conversation) {
1565
1564
conversation.updated = Date.now();
1565
const data = JSON.stringify(conversation)
1566
1566
appStorage.setItem(
1567
1567
`conversation:${conversation_id}`,
1568
JSON.stringify(conversation)
1568
data
1569
1569
);
1570
1570
}
1571
1571
@@ -1617,6 +1617,14 @@ const remove_message = async (conversation_id, index) => {
1617
1617
}
1618
1618
conversation.items = new_items;
1619
1619
await save_conversation(conversation_id, conversation);
1620
if (window.chat_id) {
1621
const url = `/backend-api/v2/chat/${window.chat_id}`;
1622
response = await fetch(url, {
1623
method: 'POST',
1624
headers: {'content-type': 'application/json'},
1625
body: data,
1626
});
1627
}
1620
1628
};
1621
1629
1622
1630
const get_message = async (conversation_id, index) => {
@@ -1685,6 +1693,14 @@ const add_message = async (
1685
1693
conversation.items = new_messages;
1686
1694
}
1687
1695
await save_conversation(conversation_id, conversation);
1696
if (window.chat_id) {
1697
const url = `/backend-api/v2/chat/${window.chat_id}`;
1698
fetch(url, {
1699
method: 'POST',
1700
headers: {'content-type': 'application/json'},
1701
body: JSON.stringify(conversation),
1702
});
1703
}
1688
1704
return conversation.items.length - 1;
1689
1705
};
1690
1706
@@ -2021,12 +2037,56 @@ chatPrompt.addEventListener("input", function() {
2021
2037
});
2022
2038
2023
2039
window.addEventListener('load', async function() {
2040
if (!window.conversation_id) {
2041
window.conversation_id = window.chat_id;
2042
}
2043
const response = await fetch(`/backend-api/v2/chat/${window.chat_id ? window.chat_id : window.conversation_id}`, {
2044
headers: {'accept': 'application/json'},
2045
});
2046
if (response.ok) {
2047
let conversation = await response.json();
2048
if (window.chat_id && (!window.conversation_id || conversation.id == window.conversation_id)) {
2049
window.conversation_id = conversation.id;
2050
await load_conversation(conversation);
2051
appStorage.setItem(
2052
`conversation:${conversation.id}`,
2053
JSON.stringify(conversation)
2054
);
2055
let refreshOnHide = true;
2056
document.addEventListener("visibilitychange", () => {
2057
if (document.hidden) {
2058
refreshOnHide = false;
2059
} else {
2060
refreshOnHide = true;
2061
}
2062
});
2063
return setInterval(async () => {
2064
if (!refreshOnHide || !window.chat_id) {
2065
return;
2066
}
2067
const response = await fetch(`/backend-api/v2/chat/${window.chat_id}`, {
2068
headers: {'accept': 'application/json', 'if-none-match': conversation.updated},
2069
});
2070
if (response.status == 200) {
2071
const new_conversation = await response.json();
2072
if (conversation.id == window.conversation_id && new_conversation.updated != conversation.updated) {
2073
conversation = new_conversation;
2074
appStorage.setItem(
2075
`conversation:${conversation.id}`,
2076
JSON.stringify(conversation)
2077
);
2078
await load_conversation(conversation);
2079
}
2080
}
2081
}, 5000);
2082
}
2083
}
2024
2084
await safe_load_conversation(window.conversation_id, false);
2025
2085
});
2026
2086
2027
2087
window.addEventListener('DOMContentLoaded', async function() {
2028
2088
await on_load();
2029
if (window.conversation_id == "{{chat_id}}") {
2089
if (!window.conversation_id == "{{chat_id}}") {
2030
2090
window.conversation_id = uuid();
2031
2091
} else {
2032
2092
await on_api();
@@ -2289,11 +2349,9 @@ async function on_api() {
2289
2349
);
2290
2350
2291
2351
const hide_systemPrompt = document.getElementById("hide-systemPrompt")
2292
const slide_systemPrompt_icon = document.querySelector(".slide-systemPrompt i");
2352
const slide_systemPrompt_icon = document.querySelector(".slide-header i");
2293
2353
if (hide_systemPrompt.checked) {
2294
2354
chatPrompt.classList.add("hidden");
2295
slide_systemPrompt_icon.classList.remove("fa-angles-up");
2296
slide_systemPrompt_icon.classList.add("fa-angles-down");
2297
2355
}
2298
2356
hide_systemPrompt.addEventListener('change', async (event) => {
2299
2357
if (event.target.checked) {
@@ -2302,10 +2360,10 @@ async function on_api() {
2302
2360
chatPrompt.classList.remove("hidden");
2303
2361
}
2304
2362
});
2305
document.querySelector(".slide-systemPrompt")?.addEventListener("click", () => {
2306
hide_systemPrompt.click();
2307
const checked = hide_systemPrompt.checked;
2308
chatPrompt.classList[checked ? "add": "remove"]("hidden");
2363
document.querySelector(".slide-header")?.addEventListener("click", () => {
2364
const checked = slide_systemPrompt_icon.classList.contains("fa-angles-up");
2365
document.querySelector(".chat-header").classList[checked ? "add": "remove"]("hidden");
2366
chatPrompt.classList[checked || hide_systemPrompt.checked ? "add": "remove"]("hidden");
2309
2367
slide_systemPrompt_icon.classList[checked ? "remove": "add"]("fa-angles-up");
2310
2368
slide_systemPrompt_icon.classList[checked ? "add": "remove"]("fa-angles-down");
2311
2369
});
@@ -2361,7 +2419,6 @@ async function load_version() {
2361
2419
}
2362
2420
2363
2421
function renderMediaSelect() {
2364
mediaSelect.classList.remove("hidden");
2365
2422
const oldImages = mediaSelect.querySelectorAll("a:has(img)");
2366
2423
oldImages.forEach((el)=>el.remove());
2367
2424
Object.entries(image_storage).forEach(([object_url, file]) => {
@@ -2472,10 +2529,15 @@ function connectToSSE(url, do_refine, bucket_id) {
2472
2529
inputCount.innerText = `Download: ${data.count} files`;
2473
2530
} else if (data.action == "done") {
2474
2531
if (do_refine) {
2475
do_refine = false;
2476
connectToSSE(`/backend-api/v2/files/${bucket_id}?refine_chunks_with_spacy=true`, do_refine, bucket_id);
2532
connectToSSE(`/backend-api/v2/files/${bucket_id}?refine_chunks_with_spacy=true`, false, bucket_id);
2477
2533
return;
2478
2534
}
2535
fileInput.value = "";
2536
paperclip.classList.remove("blink");
2537
if (!data.size) {
2538
inputCount.innerText = "No content found";
2539
return
2540
}
2479
2541
appStorage.setItem(`bucket:${bucket_id}`, data.size);
2480
2542
inputCount.innerText = "Files are loaded successfully";
2481
2543
if (!userInput.value) {
@@ -2483,8 +2545,6 @@ function connectToSSE(url, do_refine, bucket_id) {
2483
2545
handle_ask(false);
2484
2546
} else {
2485
2547
userInput.value += (userInput.value ? "\n" : "") + JSON.stringify({bucket_id: bucket_id}) + "\n";
2486
paperclip.classList.remove("blink");
2487
fileInput.value = "";
2488
2548
}
2489
2549
}
2490
2550
};
@@ -2518,9 +2578,10 @@ async function upload_files(fileInput) {
2518
2578
}
2519
2579
if (result.media) {
2520
2580
result.media.forEach((filename)=> {
2521
const url = `/backend-api/v2/files/${bucket_id}/media/${filename}`;
2581
const url = `/files/${bucket_id}/media/${filename}`;
2522
2582
image_storage[url] = {bucket_id: bucket_id, name: filename};
2523
2583
});
2584
mediaSelect.classList.remove("hidden");
2524
2585
renderMediaSelect();
2525
2586
}
2526
2587
}
@@ -58,6 +58,7 @@ class Backend_Api(Api):
58
58
app (Flask): Flask application instance to attach routes to.
59
59
"""
60
60
self.app: Flask = app
61
self.chat_cache = {}
61
62
62
63
if app.demo:
63
64
@app.route('/', methods=['GET'])
@@ -210,6 +211,10 @@ class Backend_Api(Api):
210
211
'/images/<path:name>': {
211
212
'function': self.serve_images,
212
213
'methods': ['GET']
214
},
215
'/media/<path:name>': {
216
'function': self.serve_images,
217
'methods': ['GET']
213
218
}
214
219
}
215
220
@@ -359,6 +364,33 @@ class Backend_Api(Api):
359
364
return "File saved", 200
360
365
return 'Not supported file', 400
361
366
367
@self.app.route('/backend-api/v2/chat/<chat_id>', methods=['GET'])
368
def get_chat(chat_id: str) -> str:
369
chat_id = secure_filename(chat_id)
370
if int(self.chat_cache.get(chat_id, -1)) == int(request.headers.get("if-none-match", 0)):
371
return jsonify({"error": {"message": "Not modified"}}), 304
372
bucket_dir = get_bucket_dir(chat_id)
373
file = os.path.join(bucket_dir, "chat.json")
374
if not os.path.isfile(file):
375
return jsonify({"error": {"message": "Not found"}}), 404
376
with open(file, 'r') as f:
377
chat_data = json.load(f)
378
if int(chat_data.get("updated", 0)) == int(request.headers.get("if-none-match", 0)):
379
return jsonify({"error": {"message": "Not modified"}}), 304
380
self.chat_cache[chat_id] = chat_data.get("updated", 0)
381
return jsonify(chat_data), 200
382
383
@self.app.route('/backend-api/v2/chat/<chat_id>', methods=['POST'])
384
def upload_chat(chat_id: str) -> dict:
385
chat_data = {**request.json}
386
chat_id = secure_filename(chat_id)
387
bucket_dir = get_bucket_dir(chat_id)
388
os.makedirs(bucket_dir, exist_ok=True)
389
with open(os.path.join(bucket_dir, "chat.json"), 'w') as f:
390
json.dump(chat_data, f)
391
self.chat_cache[chat_id] = chat_data.get("updated", 0)
392
return {"chat_id": chat_id}
393
362
394
def handle_synthesize(self, provider: str):
363
395
try:
364
396
provider_handler = convert_to_provider(provider)
@@ -16,6 +16,14 @@ class Website:
16
16
'function': self._chat,
17
17
'methods': ['GET', 'POST']
18
18
},
19
'/chat/<chat_id>/': {
20
'function': self._chat_id,
21
'methods': ['GET', 'POST']
22
},
23
'/chat/<chat_id>/<conversation_id>': {
24
'function': self._chat_id,
25
'methods': ['GET', 'POST']
26
},
19
27
'/chat/menu/': {
20
28
'function': redirect_home,
21
29
'methods': ['GET', 'POST']
@@ -32,11 +40,14 @@ class Website:
32
40
33
41
def _chat(self, conversation_id):
34
42
if conversation_id == "share":
35
return render_template('index.html', chat_id=str(uuid.uuid4()))
36
return render_template('index.html', chat_id=conversation_id)
43
return render_template('index.html', conversation_id=str(uuid.uuid4()))
44
return render_template('index.html', conversation_id=conversation_id)
45
46
def _chat_id(self, chat_id, conversation_id: str = ""):
47
return render_template('index.html', chat_id=chat_id, conversation_id=conversation_id)
37
48
38
49
def _index(self):
39
return render_template('index.html', chat_id=str(uuid.uuid4()))
50
return render_template('index.html', conversation_id=str(uuid.uuid4()))
40
51
41
52
def _settings(self):
42
return render_template('index.html', chat_id=str(uuid.uuid4()))
53
return render_template('index.html', conversation_id=str(uuid.uuid4()))