Improve background page

Improve share js functions
Fix photoswipe in UI
Support n parameter in PollinationsAI
This commit is contained in:
hlohaus
2025-03-26 21:39:19 +01:00
parent c3cc412dc8
commit a1871dafeb
11 changed files with 127 additions and 83 deletions

View File

@@ -3,6 +3,7 @@ from __future__ import annotations
import json import json
import random import random
import requests import requests
import asyncio
from urllib.parse import quote_plus from urllib.parse import quote_plus
from typing import Optional from typing import Optional
from aiohttp import ClientSession from aiohttp import ClientSession
@@ -148,6 +149,7 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
private: bool = False, private: bool = False,
enhance: bool = False, enhance: bool = False,
safe: bool = False, safe: bool = False,
n: int = 1,
# Text generation parameters # Text generation parameters
media: MediaListType = None, media: MediaListType = None,
temperature: float = None, temperature: float = None,
@@ -187,7 +189,8 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
nologo=nologo, nologo=nologo,
private=private, private=private,
enhance=enhance, enhance=enhance,
safe=safe safe=safe,
n=n
): ):
yield chunk yield chunk
else: else:
@@ -223,12 +226,10 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
nologo: bool, nologo: bool,
private: bool, private: bool,
enhance: bool, enhance: bool,
safe: bool safe: bool,
n: int
) -> AsyncResult: ) -> AsyncResult:
if not cache and seed is None:
seed = random.randint(9999, 99999999)
params = use_aspect_ratio({ params = use_aspect_ratio({
"seed": seed,
"width": width, "width": width,
"height": height, "height": height,
"model": model, "model": model,
@@ -238,12 +239,27 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
"safe": str(safe).lower() "safe": str(safe).lower()
}, aspect_ratio) }, aspect_ratio)
query = "&".join(f"{k}={quote_plus(str(v))}" for k, v in params.items() if v is not None) query = "&".join(f"{k}={quote_plus(str(v))}" for k, v in params.items() if v is not None)
prompt = quote_plus(prompt)[:8112] # Limit URL length prompt = quote_plus(prompt)[:2048-256-len(query)]
url = f"{cls.image_api_endpoint}prompt/{prompt}?{query}" url = f"{cls.image_api_endpoint}prompt/{prompt}?{query}"
def get_image_url(i: int = 0, seed: Optional[int] = None):
if i == 0:
if not cache and seed is None:
seed = random.randint(0, 2**32)
else:
seed = random.randint(0, 2**32)
return f"{url}&seed={seed}" if seed else url
async with ClientSession(headers=DEFAULT_HEADERS, connector=get_connector(proxy=proxy)) as session: async with ClientSession(headers=DEFAULT_HEADERS, connector=get_connector(proxy=proxy)) as session:
async with session.get(url, allow_redirects=False) as response: async def get_image(i: int = 0, seed: Optional[int] = None):
await raise_for_status(response) async with session.get(get_image_url(i, seed), allow_redirects=False) as response:
yield ImageResponse(str(response.url), prompt) try:
await raise_for_status(response)
except Exception as e:
debug.error(f"Error fetching image: {e}")
return str(response.url)
return str(response.url)
yield ImageResponse(await asyncio.gather(*[
get_image(i, seed) for i in range(int(n))
]), prompt)
@classmethod @classmethod
async def _generate_text( async def _generate_text(

View File

@@ -45,6 +45,7 @@ class PollinationsImage(PollinationsAI):
private: bool = False, private: bool = False,
enhance: bool = False, enhance: bool = False,
safe: bool = False, safe: bool = False,
n: int = 4,
**kwargs **kwargs
) -> AsyncResult: ) -> AsyncResult:
# Calling model updates before creating a generator # Calling model updates before creating a generator
@@ -61,6 +62,7 @@ class PollinationsImage(PollinationsAI):
nologo=nologo, nologo=nologo,
private=private, private=private,
enhance=enhance, enhance=enhance,
safe=safe safe=safe,
n=n
): ):
yield chunk yield chunk

View File

@@ -28,7 +28,6 @@
.gradient { .gradient {
position: absolute; position: absolute;
z-index: -1;
left: 50vw; left: 50vw;
border-radius: 50%; border-radius: 50%;
background: radial-gradient(circle at center, var(--accent), var(--gradient)); background: radial-gradient(circle at center, var(--accent), var(--gradient));
@@ -73,12 +72,6 @@
overflow: hidden; overflow: hidden;
} }
iframe {
background: transparent;
width: 100%;
border: none;
}
.hidden { .hidden {
display: none; display: none;
} }
@@ -86,7 +79,6 @@
#background, #image-feed, #video-feed { #background, #image-feed, #video-feed {
height: 100%; height: 100%;
position: absolute; position: absolute;
z-index: -1;
object-fit: cover; object-fit: cover;
object-position: center; object-position: center;
width: 100%; width: 100%;
@@ -100,29 +92,41 @@
<video id="video-feed" class="hidden" alt="Video Feed" src="/search/video" autoplay></video> <video id="video-feed" class="hidden" alt="Video Feed" src="/search/video" autoplay></video>
<!-- Gradient Background Circle --> <!-- Gradient Background Circle -->
<div class="gradient"></div></div> <div class="gradient"></div>
<script> <script>
(async () => { (async () => {
const url = "https://image.pollinations.ai/feed"; const url = "https://image.pollinations.ai/feed";
const imageFeed = document.getElementById("image-feed"); const imageFeed = document.getElementById("image-feed");
const videoFeed = document.getElementById("video-feed"); const videoFeed = document.getElementById("video-feed");
const gradient = document.querySelector(".gradient"); const gradient = document.querySelector(".gradient");
const images = [] const images = [];
let es = null; let es = null;
let skipVideo = 1; let skipVideo = 1;
let errorVideo = false; let skipImage = 0;
let errorVideo = 0;
let errorImage = 0;
videoFeed.onloadeddata = () => { videoFeed.onloadeddata = () => {
videoFeed.classList.remove("hidden"); videoFeed.classList.remove("hidden");
gradient.classList.add("hidden"); gradient.classList.add("hidden");
}; };
videoFeed.onerror = () => { videoFeed.onerror = (e) => {
videoFeed.classList.add("hidden"); videoFeed.classList.add("hidden");
errorVideo = true; errorVideo += 1;
if (errorVideo > 3) {
gradient.classList.remove("hidden");
return;
}
videoFeed.src = "/search/video?skip=" + skipVideo;
skipVideo++;
}; };
videoFeed.onended = () => { videoFeed.onended = () => {
videoFeed.src = "/search/video?skip=" + skipVideo; videoFeed.src = "/search/video?skip=" + skipVideo;
skipVideo++; skipVideo++;
}; };
videoFeed.onclick = () => {
videoFeed.src = "/search/video?skip=" + skipVideo;
skipVideo++;
};
function initES() { function initES() {
if (es == null || es.readyState == EventSource.CLOSED) { if (es == null || es.readyState == EventSource.CLOSED) {
const eventSource = new EventSource(url); const eventSource = new EventSource(url);
@@ -152,9 +156,21 @@
} }
} }
} }
initES(); let refreshOnHide = true;
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
refreshOnHide = false;
} else {
refreshOnHide = true;
}
});
setInterval(() => { setInterval(() => {
if (!errorVideo) { if (errorVideo < 3 || !refreshOnHide) {
return;
}
if (errorImage < 3) {
imageFeed.src = "/search/image+g4f?skip=" + skipImage;
skipImage++;
return; return;
} }
if (images.length > 0) { if (images.length > 0) {
@@ -168,6 +184,14 @@
imageFeed.onerror = () => { imageFeed.onerror = () => {
imageFeed.classList.add("hidden"); imageFeed.classList.add("hidden");
gradient.classList.remove("hidden"); gradient.classList.remove("hidden");
errorImage++;
};
imageFeed.onload = () => {
imageFeed.classList.remove("hidden");
gradient.classList.add("hidden");
};
imageFeed.onclick = () => {
imageFeed.src = "/search/image?random=" + Math.random();
}; };
})(); })();
</script> </script>

View File

@@ -63,7 +63,7 @@
<script src="/static/js/highlight.min.js" async></script> <script src="/static/js/highlight.min.js" async></script>
<script> <script>
window.conversation_id = "{{conversation_id}}"; window.conversation_id = "{{conversation_id}}";
window.chat_id = "{{chat_id}}"; window.share_id = "{{share_id}}";
window.share_url = "{{share_url}}"; window.share_url = "{{share_url}}";
window.start_id = "{{conversation_id}}"; window.start_id = "{{conversation_id}}";
</script> </script>

View File

@@ -74,15 +74,15 @@
}); });
document.getElementById('generateQRCode').addEventListener('click', async () => { document.getElementById('generateQRCode').addEventListener('click', async () => {
const chat_id = generate_uuid(); const share_id = generate_uuid();
const url = `${share_url}/backend-api/v2/chat/${encodeURI(chat_id)}`; const url = `${share_url}/backend-api/v2/chat/${encodeURI(share_id)}`;
const response = await fetch(url, { const response = await fetch(url, {
method: 'POST', method: 'POST',
headers: {'content-type': 'application/json'}, headers: {'content-type': 'application/json'},
body: localStorage.getItem(`conversation:${conversation_id}`) body: localStorage.getItem(`conversation:${conversation_id}`)
}); });
const share = `${share_url}/chat/${encodeURI(chat_id)}/${encodeURI(conversation_id)}`; const share = `${share_url}/chat/${encodeURI(share_id)}/${encodeURI(conversation_id)}`;
const qrcodeStatus = document.getElementById('qrcode-status'); const qrcodeStatus = document.getElementById('qrcode-status');
if (response.status !== 200) { if (response.status !== 200) {
qrcodeStatus.innerText = 'Error generating QR code: ' + response.statusText; qrcodeStatus.innerText = 'Error generating QR code: ' + response.statusText;
@@ -115,8 +115,8 @@
const constraints = { const constraints = {
video: { video: {
width: { ideal: 1280 }, width: { ideal: 800 },
height: { ideal: 1280 }, height: { ideal: 800 },
facingMode: facingMode facingMode: facingMode
}, },
audio: false audio: false

View File

@@ -298,8 +298,8 @@ const register_message_buttons = async () => {
if (message_el) { if (message_el) {
if ("index" in message_el.dataset) { if ("index" in message_el.dataset) {
await remove_message(window.conversation_id, message_el.dataset.index); await remove_message(window.conversation_id, message_el.dataset.index);
chatBody.removeChild(message_el);
} }
message_el.remove();
} }
reloadConversation = true; reloadConversation = true;
await safe_load_conversation(window.conversation_id, false); await safe_load_conversation(window.conversation_id, false);
@@ -842,7 +842,7 @@ async function add_message_chunk(message, message_id, provider, scroll, finish_m
for (const [key, value] of Object.entries(message.conversation)) { for (const [key, value] of Object.entries(message.conversation)) {
conversation.data[key] = value; conversation.data[key] = value;
} }
await save_conversation(conversation_id, conversation); await save_conversation(conversation_id, get_conversation_data(conversation));
} else if (message.type == "auth") { } else if (message.type == "auth") {
error_storage[message_id] = message.message error_storage[message_id] = message.message
content_map.inner.innerHTML += markdown_render(`**An error occured:** ${message.message}`); content_map.inner.innerHTML += markdown_render(`**An error occured:** ${message.message}`);
@@ -1227,7 +1227,6 @@ function sanitize(input, replacement) {
} }
async function set_conversation_title(conversation_id, title) { async function set_conversation_title(conversation_id, title) {
window.chat_id = null;
conversation = await get_conversation(conversation_id) conversation = await get_conversation(conversation_id)
conversation.new_title = title; conversation.new_title = title;
const new_id = sanitize(title, " "); const new_id = sanitize(title, " ");
@@ -1319,7 +1318,6 @@ const set_conversation = async (conversation_id) => {
const new_conversation = async () => { const new_conversation = async () => {
history.pushState({}, null, `/chat/`); history.pushState({}, null, `/chat/`);
window.chat_id = null;
window.conversation_id = uuid(); window.conversation_id = uuid();
document.title = window.title || document.title; document.title = window.title || document.title;
document.querySelector(".chat-header").innerText = "New Conversation - G4F"; document.querySelector(".chat-header").innerText = "New Conversation - G4F";
@@ -1391,7 +1389,7 @@ const load_conversation = async (conversation, scroll=true) => {
document.title = title; document.title = title;
} }
const chatHeader = document.querySelector(".chat-header"); const chatHeader = document.querySelector(".chat-header");
if (window.chat_id) { if (window.share_id && conversation.id == window.start_id) {
chatHeader.innerHTML = '<i class="fa-solid fa-qrcode"></i> ' + escapeHtml(title); chatHeader.innerHTML = '<i class="fa-solid fa-qrcode"></i> ' + escapeHtml(title);
} else { } else {
chatHeader.innerText = title; chatHeader.innerText = title;
@@ -1581,16 +1579,16 @@ async function get_conversation(conversation_id) {
return conversation; return conversation;
} }
async function save_conversation(conversation_id, conversation) { function get_conversation_data(conversation) {
conversation.updated = Date.now(); conversation.updated = Date.now();
const data = JSON.stringify(conversation) return JSON.stringify(conversation);
}
async function save_conversation(conversation_id, data) {
appStorage.setItem( appStorage.setItem(
`conversation:${conversation_id}`, `conversation:${conversation_id}`,
data data
); );
if (conversation_id != window.start_id) {
window.chat_id = null;
}
} }
async function get_messages(conversation_id) { async function get_messages(conversation_id) {
@@ -1600,13 +1598,13 @@ async function get_messages(conversation_id) {
async function add_conversation(conversation_id) { async function add_conversation(conversation_id) {
if (appStorage.getItem(`conversation:${conversation_id}`) == null) { if (appStorage.getItem(`conversation:${conversation_id}`) == null) {
await save_conversation(conversation_id, { await save_conversation(conversation_id, get_conversation_data({
id: conversation_id, id: conversation_id,
title: "", title: "",
added: Date.now(), added: Date.now(),
system: chatPrompt?.value, system: chatPrompt?.value,
items: [], items: [],
}); }));
} }
try { try {
add_url_to_history(`/chat/${conversation_id}`); add_url_to_history(`/chat/${conversation_id}`);
@@ -1622,7 +1620,7 @@ async function save_system_message() {
const conversation = await get_conversation(window.conversation_id); const conversation = await get_conversation(window.conversation_id);
if (conversation) { if (conversation) {
conversation.system = chatPrompt?.value; conversation.system = chatPrompt?.value;
await save_conversation(window.conversation_id, conversation); await save_conversation(window.conversation_id, get_conversation_data(conversation));
} }
} }
@@ -1640,9 +1638,10 @@ const remove_message = async (conversation_id, index) => {
} }
} }
conversation.items = new_items; conversation.items = new_items;
await save_conversation(conversation_id, conversation); const data = get_conversation_data(conversation);
if (window.chat_id && window.conversation_id == window.start_id) { await save_conversation(conversation_id, data);
const url = `${window.share_url}/backend-api/v2/chat/${window.chat_id}`; if (window.share_id && window.conversation_id == window.start_id) {
const url = `${window.share_url}/backend-api/v2/chat/${window.share_id}`;
await fetch(url, { await fetch(url, {
method: 'POST', method: 'POST',
headers: {'content-type': 'application/json'}, headers: {'content-type': 'application/json'},
@@ -1716,13 +1715,14 @@ const add_message = async (
}); });
conversation.items = new_messages; conversation.items = new_messages;
} }
await save_conversation(conversation_id, conversation); data = get_conversation_data(conversation);
if (window.chat_id && conversation_id == window.start_id) { await save_conversation(conversation_id, data);
const url = `${window.share_url}/backend-api/v2/chat/${window.chat_id}`; if (window.share_id && conversation_id == window.start_id) {
const url = `${window.share_url}/backend-api/v2/chat/${window.share_id}`;
fetch(url, { fetch(url, {
method: 'POST', method: 'POST',
headers: {'content-type': 'application/json'}, headers: {'content-type': 'application/json'},
body: JSON.stringify(conversation), body: data
}); });
} }
return conversation.items.length - 1; return conversation.items.length - 1;
@@ -1758,7 +1758,7 @@ const load_conversations = async () => {
// appStorage.removeItem(`conversation:${conversation.id}`); // appStorage.removeItem(`conversation:${conversation.id}`);
// return; // return;
// } // }
const shareIcon = (conversation.id == window.start_id && window.chat_id) ? '<i class="fa-solid fa-qrcode"></i>': ''; const shareIcon = (conversation.id == window.start_id && window.share_id) ? '<i class="fa-solid fa-qrcode"></i>': '';
html.push(` html.push(`
<div class="convo" id="convo-${conversation.id}"> <div class="convo" id="convo-${conversation.id}">
<div class="left" onclick="set_conversation('${conversation.id}')"> <div class="left" onclick="set_conversation('${conversation.id}')">
@@ -2071,14 +2071,14 @@ chatPrompt.addEventListener("input", function() {
}); });
window.addEventListener('load', async function() { window.addEventListener('load', async function() {
if (!window.chat_id) { if (!window.share_id) {
return await load_conversation(JSON.parse(appStorage.getItem(`conversation:${window.conversation_id}`))); return await load_conversation(JSON.parse(appStorage.getItem(`conversation:${window.conversation_id}`)));
} }
if (!window.conversation_id) { if (!window.conversation_id) {
window.conversation_id = window.chat_id; window.conversation_id = window.share_id;
} }
const response = await fetch(`${window.share_url}/backend-api/v2/chat/${window.chat_id ? window.chat_id : window.conversation_id}`, { const response = await fetch(`${window.share_url}/backend-api/v2/chat/${window.share_id}`, {
headers: {'accept': 'application/json'}, headers: {'accept': 'application/json', 'x-conversation-id': window.conversation_id},
}); });
if (!response.ok) { if (!response.ok) {
return await load_conversation(JSON.parse(appStorage.getItem(`conversation:${window.conversation_id}`))); return await load_conversation(JSON.parse(appStorage.getItem(`conversation:${window.conversation_id}`)));
@@ -2087,10 +2087,7 @@ window.addEventListener('load', async function() {
if (!window.conversation_id || conversation.id == window.conversation_id) { if (!window.conversation_id || conversation.id == window.conversation_id) {
window.conversation_id = conversation.id; window.conversation_id = conversation.id;
await load_conversation(conversation); await load_conversation(conversation);
appStorage.setItem( await save_conversation(window.conversation_id, JSON.stringify(conversation));
`conversation:${conversation.id}`,
JSON.stringify(conversation)
);
await load_conversations(); await load_conversations();
let refreshOnHide = true; let refreshOnHide = true;
document.addEventListener("visibilitychange", () => { document.addEventListener("visibilitychange", () => {
@@ -2101,7 +2098,7 @@ window.addEventListener('load', async function() {
} }
}); });
var refreshIntervalId = setInterval(async () => { var refreshIntervalId = setInterval(async () => {
if (!window.chat_id) { if (!window.share_id) {
clearInterval(refreshIntervalId); clearInterval(refreshIntervalId);
return; return;
} }
@@ -2111,7 +2108,7 @@ window.addEventListener('load', async function() {
if (window.conversation_id != window.start_id) { if (window.conversation_id != window.start_id) {
return; return;
} }
const response = await fetch(`${window.share_url}/backend-api/v2/chat/${window.chat_id}`, { const response = await fetch(`${window.share_url}/backend-api/v2/chat/${window.share_id}`, {
headers: { headers: {
'accept': 'application/json', 'accept': 'application/json',
'if-none-match': conversation.updated, 'if-none-match': conversation.updated,

View File

@@ -4,7 +4,7 @@ import PhotoSwipeAutoHideUI from "https://cdn.jsdelivr.net/gh/arnowelzel/photosw
import PhotoSwipeSlideshow from "https://cdn.jsdelivr.net/gh/dpet23/photoswipe-slideshow@v2.0.0/photoswipe-slideshow.esm.min.js"; import PhotoSwipeSlideshow from "https://cdn.jsdelivr.net/gh/dpet23/photoswipe-slideshow@v2.0.0/photoswipe-slideshow.esm.min.js";
const lightbox = new PhotoSwipeLightbox({ const lightbox = new PhotoSwipeLightbox({
gallery: '#messages', gallery: '#chatBody',
children: 'a:has(img)', children: 'a:has(img)',
initialZoomLevel: 'fill', initialZoomLevel: 'fill',
secondaryZoomLevel: 1, secondaryZoomLevel: 1,

View File

@@ -371,6 +371,8 @@ class Backend_Api(Api):
match_files = [file for file, count in match_files.items() if count >= request.args.get("min", len(search))] match_files = [file for file, count in match_files.items() if count >= request.args.get("min", len(search))]
if int(request.args.get("skip", 0)) >= len(match_files): if int(request.args.get("skip", 0)) >= len(match_files):
return jsonify({"error": {"message": "Not found"}}), 404 return jsonify({"error": {"message": "Not found"}}), 404
if (request.args.get("random", False)):
return redirect(f"/media/{random.choice(match_files)}"), 302
return redirect(f"/media/{match_files[int(request.args.get("skip", 0))]}"), 302 return redirect(f"/media/{match_files[int(request.args.get("skip", 0))]}"), 302
@app.route('/backend-api/v2/upload_cookies', methods=['POST']) @app.route('/backend-api/v2/upload_cookies', methods=['POST'])
@@ -386,12 +388,12 @@ class Backend_Api(Api):
return "File saved", 200 return "File saved", 200
return 'Not supported file', 400 return 'Not supported file', 400
@self.app.route('/backend-api/v2/chat/<chat_id>', methods=['GET']) @self.app.route('/backend-api/v2/chat/<share_id>', methods=['GET'])
def get_chat(chat_id: str) -> str: def get_chat(share_id: str) -> str:
chat_id = secure_filename(chat_id) share_id = secure_filename(share_id)
if self.chat_cache.get(chat_id, 0) == request.headers.get("if-none-match", 0): if self.chat_cache.get(share_id, 0) == request.headers.get("if-none-match", 0):
return jsonify({"error": {"message": "Not modified"}}), 304 return jsonify({"error": {"message": "Not modified"}}), 304
bucket_dir = get_bucket_dir(chat_id) bucket_dir = get_bucket_dir(share_id)
file = os.path.join(bucket_dir, "chat.json") file = os.path.join(bucket_dir, "chat.json")
if not os.path.isfile(file): if not os.path.isfile(file):
return jsonify({"error": {"message": "Not found"}}), 404 return jsonify({"error": {"message": "Not found"}}), 404
@@ -399,23 +401,23 @@ class Backend_Api(Api):
chat_data = json.load(f) chat_data = json.load(f)
if chat_data.get("updated", 0) == request.headers.get("if-none-match", 0): if chat_data.get("updated", 0) == request.headers.get("if-none-match", 0):
return jsonify({"error": {"message": "Not modified"}}), 304 return jsonify({"error": {"message": "Not modified"}}), 304
self.chat_cache[chat_id] = chat_data.get("updated", 0) self.chat_cache[share_id] = chat_data.get("updated", 0)
return jsonify(chat_data), 200 return jsonify(chat_data), 200
@self.app.route('/backend-api/v2/chat/<chat_id>', methods=['POST']) @self.app.route('/backend-api/v2/chat/<share_id>', methods=['POST'])
def upload_chat(chat_id: str) -> dict: def upload_chat(share_id: str) -> dict:
chat_data = {**request.json} chat_data = {**request.json}
updated = chat_data.get("updated", 0) updated = chat_data.get("updated", 0)
cache_value = self.chat_cache.get(chat_id, 0) cache_value = self.chat_cache.get(share_id, 0)
if updated == cache_value: if updated == cache_value:
return jsonify({"error": {"message": "invalid date"}}), 400 return jsonify({"error": {"message": "invalid date"}}), 400
chat_id = secure_filename(chat_id) share_id = secure_filename(share_id)
bucket_dir = get_bucket_dir(chat_id) bucket_dir = get_bucket_dir(share_id)
os.makedirs(bucket_dir, exist_ok=True) os.makedirs(bucket_dir, exist_ok=True)
with open(os.path.join(bucket_dir, "chat.json"), 'w') as f: with open(os.path.join(bucket_dir, "chat.json"), 'w') as f:
json.dump(chat_data, f) json.dump(chat_data, f)
self.chat_cache[chat_id] = updated self.chat_cache[share_id] = updated
return {"chat_id": chat_id} return {"share_id": share_id}
def handle_synthesize(self, provider: str): def handle_synthesize(self, provider: str):
try: try:

View File

@@ -19,12 +19,12 @@ class Website:
'function': self._chat, 'function': self._chat,
'methods': ['GET', 'POST'] 'methods': ['GET', 'POST']
}, },
'/chat/<chat_id>/': { '/chat/<share_id>/': {
'function': self._chat_id, 'function': self._share_id,
'methods': ['GET', 'POST'] 'methods': ['GET', 'POST']
}, },
'/chat/<chat_id>/<conversation_id>': { '/chat/<share_id>/<conversation_id>': {
'function': self._chat_id, 'function': self._share_id,
'methods': ['GET', 'POST'] 'methods': ['GET', 'POST']
}, },
'/chat/menu/': { '/chat/menu/': {
@@ -50,9 +50,9 @@ class Website:
return render_template('index.html', conversation_id=str(uuid.uuid4())) return render_template('index.html', conversation_id=str(uuid.uuid4()))
return render_template('index.html', conversation_id=conversation_id) return render_template('index.html', conversation_id=conversation_id)
def _chat_id(self, chat_id, conversation_id: str = ""): def _share_id(self, share_id, conversation_id: str = ""):
share_url = os.environ.get("G4F_SHARE_URL", "") share_url = os.environ.get("G4F_SHARE_URL", "")
return render_template('index.html', share_url=share_url, chat_id=chat_id, conversation_id=conversation_id) return render_template('index.html', share_url=share_url, share_id=share_id, conversation_id=conversation_id)
def _index(self): def _index(self):
return render_template('index.html', conversation_id=str(uuid.uuid4())) return render_template('index.html', conversation_id=str(uuid.uuid4()))

View File

@@ -25,8 +25,10 @@ def get_media_extension(media: str) -> str:
"""Extract media file extension from URL or filename""" """Extract media file extension from URL or filename"""
match = re.search(r"\.(j?[a-z]{3})(?:\?|$)", media, re.IGNORECASE) match = re.search(r"\.(j?[a-z]{3})(?:\?|$)", media, re.IGNORECASE)
extension = match.group(1).lower() if match else "" extension = match.group(1).lower() if match else ""
if not extension:
return ""
if extension not in EXTENSIONS_MAP: if extension not in EXTENSIONS_MAP:
raise ValueError(f"Unsupported media extension: {extension}") raise ValueError(f"Unsupported media extension: {extension} in: {media}")
return f".{extension}" return f".{extension}"
def ensure_images_dir(): def ensure_images_dir():

View File

@@ -154,6 +154,7 @@ async def get_nodriver(
if not os.path.exists(browser_executable_path): if not os.path.exists(browser_executable_path):
browser_executable_path = None browser_executable_path = None
lock_file = Path(get_cookies_dir()) / ".nodriver_is_open" lock_file = Path(get_cookies_dir()) / ".nodriver_is_open"
lock_file.parent.mkdir(exist_ok=True)
# Implement a short delay (milliseconds) to prevent race conditions. # Implement a short delay (milliseconds) to prevent race conditions.
await asyncio.sleep(0.1 * random.randint(0, 50)) await asyncio.sleep(0.1 * random.randint(0, 50))
if lock_file.exists(): if lock_file.exists():