返回提交历史
Added
g4f/gui/client/background.html
+181
-0
Modified
g4f/gui/client/demo.html
+16
-106
Modified
g4f/gui/client/qrcode.html
+23
-11
Modified
g4f/gui/client/static/css/style.css
+2
-2
Modified
g4f/gui/client/static/js/chat.v1.js
+55
-36
Modified
g4f/gui/server/backend_api.py
+3
-3
Modified
g4f/gui/server/website.py
+8
-1
XFEstudio/gpt4free
Add background page
142e4876
代码差异
7 个文件
+288
-159
@@ -0,0 +1,181 @@
1
2
<!DOCTYPE html>
3
<html lang="en">
4
<head>
5
<meta charset="UTF-8">
6
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7
<title>G4F DEMO</title>
8
<link rel="apple-touch-icon" sizes="180x180" href="/static/img/apple-touch-icon.png">
9
<link rel="icon" type="image/png" sizes="32x32" href="/static/img/favicon-32x32.png">
10
<link rel="icon" type="image/png" sizes="16x16" href="/static/img/favicon-16x16.png">
11
<link rel="manifest" href="/static/img/site.webmanifest">
12
<style>
13
:root {
14
--colour-1: #000000;
15
--colour-2: #ccc;
16
--colour-3: #e4d4ff;
17
--colour-4: #f0f0f0;
18
--colour-5: #181818;
19
--colour-6: #242424;
20
--accent: #8b3dff;
21
--gradient: #1a1a1a;
22
--background: #16101b;
23
--size: 70vw;
24
--top: 50%;
25
--blur: 40px;
26
--opacity: 0.6;
27
}
28
29
.gradient {
30
position: absolute;
31
z-index: -1;
32
left: 50vw;
33
border-radius: 50%;
34
background: radial-gradient(circle at center, var(--accent), var(--gradient));
35
width: var(--size);
36
height: var(--size);
37
top: var(--top);
38
transform: translate(-50%, -50%);
39
filter: blur(var(--blur)) opacity(var(--opacity));
40
animation: zoom_gradient 6s infinite alternate;
41
display: none;
42
max-height: 100%;
43
transition: max-height 0.25s ease-in;
44
}
45
46
.gradient.hidden {
47
max-height: 0;
48
transition: max-height 0.15s ease-out;
49
}
50
51
@media only screen and (min-width: 40em) {
52
body .gradient{
53
display: block;
54
}
55
}
56
57
@keyframes zoom_gradient {
58
0% {
59
transform: translate(-50%, -50%) scale(1);
60
}
61
100% {
62
transform: translate(-50%, -50%) scale(1.2);
63
}
64
}
65
66
/* Body and text color */
67
body {
68
background: var(--background);
69
height: 100vh;
70
margin: 0;
71
padding: 0;
72
overflow: hidden;
73
}
74
75
iframe {
76
background: transparent;
77
width: 100%;
78
border: none;
79
}
80
81
.hidden {
82
display: none;
83
}
84
85
#background, #image-feed {
86
height: 100%;
87
position: absolute;
88
z-index: -1;
89
object-fit: cover;
90
object-position: center;
91
width: 100%;
92
background: black;
93
}
94
95
/* Animation for the gradient circle */
96
@keyframes zoom_gradient {
97
0% {
98
transform: translate(-50%, -50%) scale(1);
99
}
100
100% {
101
transform: translate(-50%, -50%) scale(1.5);
102
}
103
}
104
</style>
105
<link rel="stylesheet" href="/static/css/all.min.css">
106
<script>
107
(async () => {
108
const isIframe = window.self !== window.top;
109
const backendUrl = "{{backend_url}}";
110
let url = new URL(window.location.href)
111
if (isIframe && backendUrl) {
112
window.location.replace(`${backendUrl}${url.search}`);
113
return;
114
}
115
})();
116
</script>
117
<script src="https://unpkg.com/es-module-shims@1.7.0/dist/es-module-shims.js"></script>
118
<script type="importmap">
119
{
120
"imports": {
121
"@huggingface/hub": "https://cdn.jsdelivr.net/npm/@huggingface/hub@0.21.0/+esm"
122
}
123
}
124
</script>
125
</head>
126
<body>
127
<img id="image-feed" class="hidden" alt="Image Feed">
128
129
<!-- Gradient Background Circle -->
130
<div class="gradient"></div></div>
131
<script>
132
(async () => {
133
const url = "https://image.pollinations.ai/feed";
134
const imageFeed = document.getElementById("image-feed");
135
const images = []
136
let es = null;
137
function initES() {
138
if (es == null || es.readyState == EventSource.CLOSED) {
139
const eventSource = new EventSource(url);
140
eventSource.onmessage = (event) => {
141
const data = JSON.parse(event.data);
142
if (data.nsfw || !data.nologo || data.width < 512 || !data.imageURL || data.isChild || data.status != "end_generating") {
143
return;
144
}
145
const lower = data.prompt.toLowerCase();
146
const tags = ["nsfw", "timeline", "feet", "blood", "soap", "orally", "heel", "latex", "bathroom", "boobs", "charts", "gel", "logo", "infographic", "warts", " bra ", "prostitute", "curvy", "breasts", "written", "bodies", "naked", "classroom", "malone", "dirty", "shoes", "shower", "banner", "fat", "nipples", "couple", "sexual", "sandal", "supplier", "overlord", "succubus", "platinum", "cracy", "crazy", "hemale", "oprah", "lamic", "ropes", "cables", "wires", "dirty", "messy", "cluttered", "chaotic", "disorganized", "disorderly", "untidy", "unorganized", "unorderly", "unsystematic", "disarranged", "disarrayed", "disheveled", "disordered", "jumbled", "muddled", "scattered", "shambolic", "sloppy", "unkept", "unruly"];
147
for (i in tags) {
148
if (lower.indexOf(tags[i]) != -1) {
149
console.log("Skipping image with tag: " + tags[i]);
150
console.debug("Skipping image:", data.imageURL);
151
return;
152
}
153
}
154
const landscape = window.innerWidth > window.innerHeight;
155
if (landscape && data.width > data.height) {
156
images.push(data.imageURL);
157
} else if (!landscape && data.width < data.height) {
158
images.push(data.imageURL);
159
}
160
};
161
eventSource.onerror = (event) => {
162
eventSource.close();
163
}
164
imageFeed.onerror = () => {
165
imageFeed.classList.add("hidden");
166
}
167
}
168
}
169
initES();
170
setInterval(() => {
171
if (images.length > 0) {
172
imageFeed.classList.remove("hidden");
173
imageFeed.src = images.shift();
174
} else if(imageFeed) {
175
initES();
176
}
177
}, 7000);
178
})();
179
</script>
180
</body>
181
</html>
@@ -28,43 +28,6 @@
28
28
29
29
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap");
30
30
31
.gradient {
32
position: absolute;
33
z-index: -1;
34
left: 50vw;
35
border-radius: 50%;
36
background: radial-gradient(circle at center, var(--accent), var(--gradient));
37
width: var(--size);
38
height: var(--size);
39
top: var(--top);
40
transform: translate(-50%, -50%);
41
filter: blur(var(--blur)) opacity(var(--opacity));
42
animation: zoom_gradient 6s infinite alternate;
43
display: none;
44
max-height: 100%;
45
transition: max-height 0.25s ease-in;
46
}
47
48
.gradient.hidden {
49
max-height: 0;
50
transition: max-height 0.15s ease-out;
51
}
52
53
@media only screen and (min-width: 40em) {
54
body .gradient{
55
display: block;
56
}
57
}
58
59
@keyframes zoom_gradient {
60
0% {
61
transform: translate(-50%, -50%) scale(1);
62
}
63
100% {
64
transform: translate(-50%, -50%) scale(1.2);
65
}
66
}
67
68
31
/* Body and text color */
69
32
body {
70
33
background: var(--background);
@@ -188,16 +151,6 @@
188
151
.hidden {
189
152
display: none;
190
153
}
191
192
/* Animation for the gradient circle */
193
@keyframes zoom_gradient {
194
0% {
195
transform: translate(-50%, -50%) scale(1);
196
}
197
100% {
198
transform: translate(-50%, -50%) scale(1.5);
199
}
200
}
201
154
</style>
202
155
<link rel="stylesheet" href="/static/css/all.min.css">
203
156
<script>
@@ -221,8 +174,7 @@
221
174
</script>
222
175
</head>
223
176
<body>
224
<iframe id="background"></iframe>
225
<img id="image-feed" class="hidden" alt="Image Feed">
177
<iframe id="background" src="/background"></iframe>
226
178
227
179
<!-- Gradient Background Circle -->
228
180
<div class="gradient"></div>
@@ -343,65 +295,23 @@
343
295
const today = new Date().toJSON().slice(0, 10);
344
296
const max = 6;
345
297
const cache_id = Math.floor(Math.random() * max);
346
let prompt;
347
if (cache_id % 2 == 0) {
348
prompt = `Today is ${new Date().toJSON().slice(0, 10)}.
349
Create a single-page HTML screensaver reflecting the current season (based on the date).
350
Avoid using any text.`;
351
} else {
352
prompt = `Create a single-page HTML screensaver. Avoid using any text.`;
353
}
354
const response = await fetch(`/backend-api/v2/create?prompt=${prompt}&filter_markdown=html&cache=${cache_id}`);
355
const text = await response.text()
356
background.src = `data:text/html;charset=utf-8,${encodeURIComponent(text)}`;
357
const gradient = document.querySelector('.gradient');
358
gradient.classList.add('hidden');
359
360
const url = "https://image.pollinations.ai/feed";
361
const imageFeed = document.getElementById("image-feed");
362
const images = []
363
let es = null;
364
function initES() {
365
if (es == null || es.readyState == EventSource.CLOSED) {
366
const eventSource = new EventSource(url);
367
eventSource.onmessage = (event) => {
368
const data = JSON.parse(event.data);
369
if (data.nsfw || !data.nologo || data.width < 512 || !data.imageURL || data.isChild || data.status != "end_generating") {
370
return;
371
}
372
const lower = data.prompt.toLowerCase();
373
const tags = ["nsfw", "timeline", "feet", "blood", "soap", "orally", "heel", "latex", "bathroom", "boobs", "charts", "gel", "logo", "infographic", "warts", " bra ", "prostitute", "curvy", "breasts", "written", "bodies", "naked", "classroom", "malone", "dirty", "shoes", "shower", "banner", "fat", "nipples", "couple", "sexual", "sandal", "supplier", "overlord", "succubus", "platinum", "cracy", "crazy", "hemale", "oprah", "lamic", "ropes", "cables", "wires", "dirty", "messy", "cluttered", "chaotic", "disorganized", "disorderly", "untidy", "unorganized", "unorderly", "unsystematic", "disarranged", "disarrayed", "disheveled", "disordered", "jumbled", "muddled", "scattered", "shambolic", "sloppy", "unkept", "unruly"];
374
for (i in tags) {
375
if (lower.indexOf(tags[i]) != -1) {
376
console.log("Skipping image with tag: " + tags[i]);
377
console.debug("Skipping image:", data.imageURL);
378
return;
379
}
380
}
381
const landscape = window.innerWidth > window.innerHeight;
382
if (landscape && data.width > data.height) {
383
images.push(data.imageURL);
384
} else if (!landscape && data.width < data.height) {
385
images.push(data.imageURL);
386
}
387
};
388
eventSource.onerror = (event) => {
389
eventSource.close();
390
}
391
imageFeed.onerror = () => {
392
imageFeed.classList.add("hidden");
393
}
298
if (cache_id > 3) {
299
let prompt;
300
if (cache_id % 2 == 0) {
301
prompt = `Today is ${new Date().toJSON().slice(0, 10)}.
302
Create a single-page HTML screensaver reflecting the current season (based on the date).
303
Avoid using any text.`;
304
} else {
305
prompt = `Create a single-page HTML screensaver. Avoid using any text.`;
394
306
}
395
}
396
initES();
397
setInterval(() => {
398
if (images.length > 0) {
399
imageFeed.classList.remove("hidden");
400
imageFeed.src = images.shift();
401
} else if(imageFeed) {
402
initES();
307
const response = await fetch(`/backend-api/v2/create?prompt=${prompt}&filter_markdown=html&cache=${cache_id}`);
308
const text = await response.text()
309
if (text) {
310
background.src = `data:text/html;charset=utf-8,${encodeURIComponent(text)}`;
311
const gradient = document.querySelector('.gradient');
312
gradient.classList.add('hidden');
403
313
}
404
}, 7000);
314
}
405
315
406
316
const container = document.querySelector('.container');
407
317
const button = document.querySelector('.slide-button');
@@ -27,9 +27,11 @@
27
27
28
28
<h2>QR Code</h2>
29
29
<div id="qrcode"></div>
30
<p id="qrcode-status"></p>
30
31
<button id="generateQRCode">Generate QR Code</button>
31
32
32
33
<script type="module">
34
const conversation_id = "{{conversation_id}}";
33
35
import QrScanner from 'https://cdn.jsdelivr.net/npm/qr-scanner/qr-scanner.min.js';
34
36
35
37
function generate_uuid() {
@@ -64,18 +66,28 @@
64
66
}
65
67
});
66
68
67
localStorage.getItem('device_id') || localStorage.setItem('device_id', generate_uuid());
69
document.getElementById('generateQRCode').addEventListener('click', () => {
70
const chat_id = generate_uuid();
68
71
69
const qrcode = new QRCode(document.getElementById("qrcode"), {
70
text: JSON.stringify({
71
date: Math.floor(Date.now() / 1000),
72
device_id: localStorage.getItem('device_id')
73
}),
74
width: 128,
75
height: 128,
76
colorDark: "#000000",
77
colorLight: "#ffffff",
78
correctLevel: QRCode.CorrectLevel.H
72
const url = `/backend-api/v2/chat/${chat_id}`;
73
fetch(url, {
74
method: 'POST',
75
headers: {'content-type': 'application/json'},
76
body: localStorage.getItem(`conversation:${conversation_id}`)
77
});
78
const share = `${new URL(window.location.href).origin}/chat/${encodeURI(chat_id)}/${encodeURI(conversation_id)}`;
79
document.getElementById('qrcode-status').innerText = share;
80
document.getElementById("qrcode").innerHTML = '';
81
const qrcode = new QRCode(
82
document.getElementById("qrcode"),
83
share,
84
{
85
width: 128,
86
height: 128,
87
colorDark: "#000000",
88
colorLight: "#ffffff",
89
correctLevel: QRCode.CorrectLevel.H
90
});
79
91
});
80
92
81
93
const switchButton = document.getElementById('switchCamera');
@@ -592,8 +592,8 @@ input-count .text {
592
592
display: none;
593
593
flex-direction: row;
594
594
height: 36px;
595
width: 120px;
596
margin-right: 130px;
595
width: 140px;
596
margin-right: 150px;
597
597
z-index: 1005;
598
598
padding: 10px;
599
599
margin-top: -4px;
@@ -439,6 +439,18 @@ const register_message_buttons = async () => {
439
439
});
440
440
});
441
441
442
chatBody.querySelectorAll(".message .fa-qrcode").forEach(async (el) => {
443
if (el.dataset.click) {
444
return
445
}
446
el.dataset.click = true;
447
const message_el = get_message_el(el);
448
el.addEventListener("click", async () => {
449
iframe.src = `/qrcode/${window.conversation_id}#${message_el.dataset.index}`;
450
iframe_container.classList.remove("hidden");
451
});
452
});
453
442
454
chatBody.querySelectorAll(".message .reasoning_title").forEach(async (el) => {
443
455
if (el.dataset.click) {
444
456
return
@@ -1446,6 +1458,7 @@ const load_conversation = async (conversation, scroll=true) => {
1446
1458
1447
1459
add_buttons.push(`<button class="options_button">
1448
1460
<div>
1461
<span><i class="fa-solid fa-qrcode"></i></span>
1449
1462
<span><i class="fa-brands fa-whatsapp"></i></span>
1450
1463
<span><i class="fa-solid fa-volume-high"></i></i></span>
1451
1464
<span><i class="fa-solid fa-print"></i></span>
@@ -2038,7 +2051,7 @@ chatPrompt.addEventListener("input", function() {
2038
2051
2039
2052
window.addEventListener('load', async function() {
2040
2053
if (!window.chat_id) {
2041
return;
2054
return await load_conversation(JSON.parse(appStorage.getItem(`conversation:${window.conversation_id}`)));
2042
2055
}
2043
2056
if (!window.conversation_id) {
2044
2057
window.conversation_id = window.chat_id;
@@ -2046,43 +2059,49 @@ window.addEventListener('load', async function() {
2046
2059
const response = await fetch(`/backend-api/v2/chat/${window.chat_id ? window.chat_id : window.conversation_id}`, {
2047
2060
headers: {'accept': 'application/json'},
2048
2061
});
2049
if (response.ok) {
2050
let conversation = await response.json();
2051
if (!window.conversation_id || conversation.id == window.conversation_id) {
2052
window.conversation_id = conversation.id;
2053
await load_conversation(conversation);
2054
appStorage.setItem(
2055
`conversation:${conversation.id}`,
2056
JSON.stringify(conversation)
2057
);
2058
let refreshOnHide = true;
2059
document.addEventListener("visibilitychange", () => {
2060
if (document.hidden) {
2061
refreshOnHide = false;
2062
} else {
2063
refreshOnHide = true;
2064
}
2062
if (!response.ok) {
2063
return await load_conversation(JSON.parse(appStorage.getItem(`conversation:${window.conversation_id}`)));
2064
}
2065
let conversation = await response.json();
2066
if (!window.conversation_id || conversation.id == window.conversation_id) {
2067
window.conversation_id = conversation.id;
2068
await load_conversation(conversation);
2069
appStorage.setItem(
2070
`conversation:${conversation.id}`,
2071
JSON.stringify(conversation)
2072
);
2073
let refreshOnHide = true;
2074
document.addEventListener("visibilitychange", () => {
2075
if (document.hidden) {
2076
refreshOnHide = false;
2077
} else {
2078
refreshOnHide = true;
2079
}
2080
});
2081
var refreshIntervalId = setInterval(async () => {
2082
if (!window.chat_id) {
2083
clearInterval(refreshIntervalId);
2084
return;
2085
}
2086
if (!refreshOnHide) {
2087
return;
2088
}
2089
const response = await fetch(`/backend-api/v2/chat/${window.chat_id}`, {
2090
headers: {'accept': 'application/json', 'if-none-match': conversation.updated},
2065
2091
});
2066
return setInterval(async () => {
2067
if (!refreshOnHide || !window.chat_id) {
2068
return;
2069
}
2070
const response = await fetch(`/backend-api/v2/chat/${window.chat_id}`, {
2071
headers: {'accept': 'application/json', 'if-none-match': conversation.updated},
2072
});
2073
if (response.status == 200) {
2074
const new_conversation = await response.json();
2075
if (conversation.id == window.conversation_id && new_conversation.updated != conversation.updated) {
2076
conversation = new_conversation;
2077
appStorage.setItem(
2078
`conversation:${conversation.id}`,
2079
JSON.stringify(conversation)
2080
);
2081
await load_conversation(conversation);
2082
}
2092
if (response.status == 200) {
2093
const new_conversation = await response.json();
2094
if (conversation.id == window.conversation_id && new_conversation.updated != conversation.updated) {
2095
conversation = new_conversation;
2096
appStorage.setItem(
2097
`conversation:${conversation.id}`,
2098
JSON.stringify(conversation)
2099
);
2100
await load_conversation(conversation);
2083
2101
}
2084
}, 5000);
2085
}
2102
}
2103
}, 5000);
2104
return;
2086
2105
}
2087
2106
await safe_load_conversation(window.conversation_id, false);
2088
2107
});
@@ -69,9 +69,9 @@ class Backend_Api(Api):
69
69
def home():
70
70
return render_template('home.html')
71
71
72
@app.route('/qrcode', methods=['GET'])
73
def qrcode():
74
return render_template('qrcode.html')
72
@app.route('/qrcode/<conversation_id>', methods=['GET'])
73
def qrcode(conversation_id: str):
74
return render_template('qrcode.html', conversation_id=conversation_id)
75
75
76
76
@app.route('/backend-api/v2/models', methods=['GET'])
77
77
def jsonify_models(**kwargs):
@@ -36,6 +36,10 @@ class Website:
36
36
'function': redirect_home,
37
37
'methods': ['GET', 'POST']
38
38
},
39
'/background': {
40
'function': self._background,
41
'methods': ['GET']
42
},
39
43
}
40
44
41
45
def _chat(self, conversation_id):
@@ -50,4 +54,7 @@ class Website:
50
54
return render_template('index.html', conversation_id=str(uuid.uuid4()))
51
55
52
56
def _settings(self):
53
return render_template('index.html', conversation_id=str(uuid.uuid4()))
57
return render_template('index.html', conversation_id=str(uuid.uuid4()))
58
59
def _background(self):
60
return render_template('background.html')