返回提交历史
Added
g4f/Provider/DuckDuckGo.py
+64
-0
Modified
g4f/gui/client/index.html
+20
-4
Modified
g4f/gui/client/static/css/style.css
+31
-13
Modified
g4f/gui/client/static/js/chat.v1.js
+111
-20
XFEstudio/gpt4free
Add DuckDuckGo Provider, Add SpeechRecognition to gui
24345bc0
代码差异
4 个文件
+226
-37
@@ -0,0 +1,64 @@
1
from __future__ import annotations
2
3
import json
4
import aiohttp
5
6
from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
7
from ..typing import AsyncResult, Messages
8
from ..requests.raise_for_status import raise_for_status
9
10
class DuckDuckGo(AsyncGeneratorProvider, ProviderModelMixin):
11
url = "https://duckduckgo.com/duckchat"
12
working = True
13
supports_gpt_35_turbo = True
14
supports_message_history = True
15
16
default_model = "gpt-3.5-turbo-0125"
17
models = ["gpt-3.5-turbo-0125", "claude-instant-1.2"]
18
model_aliases = {"gpt-3.5-turbo": "gpt-3.5-turbo-0125"}
19
20
status_url = "https://duckduckgo.com/duckchat/v1/status"
21
chat_url = "https://duckduckgo.com/duckchat/v1/chat"
22
user_agent = 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0'
23
headers = {
24
'User-Agent': user_agent,
25
'Accept': 'text/event-stream',
26
'Accept-Language': 'de,en-US;q=0.7,en;q=0.3',
27
'Accept-Encoding': 'gzip, deflate, br',
28
'Referer': 'https://duckduckgo.com/',
29
'Content-Type': 'application/json',
30
'Origin': 'https://duckduckgo.com',
31
'Connection': 'keep-alive',
32
'Cookie': 'dcm=1',
33
'Sec-Fetch-Dest': 'empty',
34
'Sec-Fetch-Mode': 'cors',
35
'Sec-Fetch-Site': 'same-origin',
36
'Pragma': 'no-cache',
37
'TE': 'trailers'
38
}
39
40
@classmethod
41
async def create_async_generator(
42
cls,
43
model: str,
44
messages: Messages,
45
**kwargs
46
) -> AsyncResult:
47
async with aiohttp.ClientSession(headers=cls.headers) as session:
48
async with session.get(cls.status_url, headers={"x-vqd-accept": "1"}) as response:
49
await raise_for_status(response)
50
vqd_4 = response.headers.get("x-vqd-4")
51
payload = {
52
'model': cls.get_model(model),
53
'messages': messages
54
}
55
async with session.post(cls.chat_url, json=payload, headers={"x-vqd-4": vqd_4}) as response:
56
await raise_for_status(response)
57
async for line in response.content:
58
if line.startswith(b"data: "):
59
chunk = line[6:]
60
if chunk.startswith(b"[DONE]"):
61
break
62
data = json.loads(chunk)
63
if "message" in data:
64
yield data["message"]
@@ -49,7 +49,7 @@
49
49
<body>
50
50
<div class="gradient"></div>
51
51
<div class="row">
52
<div class="box conversations hidden">
52
<div class="box conversations">
53
53
<div class="top">
54
54
<button class="new_convo" onclick="new_conversation()">
55
55
<i class="fa-regular fa-plus"></i>
@@ -61,9 +61,14 @@
61
61
<i class="fa-solid fa-toolbox"></i>
62
62
<span>Open Settings</span>
63
63
</button>
64
<div class="info">
65
<i class="fa-brands fa-discord"></i>
66
<span class="convo-title">discord ~ <a href="https://discord.gg/XfybzPXPH5">discord.gg/XfybzPXPH5</a>
67
</span>
68
</div>
64
69
<div class="info">
65
70
<i class="fa-brands fa-github"></i>
66
<span class="convo-title">github ~ <a href="https://github.com/xtekky/gpt4free">@gpt4free</a>
71
<span class="convo-title">github ~ <a href="https://github.com/xtekky/gpt4free">@xtekky/gpt4free</a>
67
72
</span>
68
73
</div>
69
74
<div class="info">
@@ -73,6 +78,7 @@
73
78
</div>
74
79
</div>
75
80
<div class="settings hidden">
81
<div class="paper">
76
82
<div class="field">
77
83
<span class="label">Web Access</span>
78
84
<input type="checkbox" id="switch" />
@@ -91,9 +97,16 @@
91
97
<div class="field">
92
98
<span class="label">Auto continue</span>
93
99
<input id="auto_continue" type="checkbox" name="auto_continue" checked/>
94
<label for="auto_continue" class="toogle" title="Continue long responses in OpenaiChat"></label>
100
<label for="auto_continue" class="toogle" title="Continue large responses in OpenaiChat"></label>
101
</div>
102
<div class="field box">
103
<label for="message-input-height" class="label" title="">Input max. grow height</label>
104
<input type="number" id="message-input-height" value="200"/>
105
</div>
106
<div class="field box">
107
<label for="recognition-language" class="label" title="">Speech recognition lang</label>
108
<input type="text" id="recognition-language" value="" placeholder="navigator.language"/>
95
109
</div>
96
<div class="paper">
97
110
<div class="field box">
98
111
<label for="OpenaiChat-api_key" class="label" title="">OpenaiChat: api_key</label>
99
112
<textarea id="OpenaiChat-api_key" name="OpenaiChat[api_key]" placeholder="..."></textarea>
@@ -170,6 +183,9 @@
170
183
<input type="file" id="file" name="file" accept="text/plain, text/html, text/xml, application/json, text/javascript, .sh, .py, .php, .css, .yaml, .sql, .log, .csv, .twig, .md" required/>
171
184
<i class="fa-solid fa-paperclip"></i>
172
185
</label>
186
<label class="micro-label" for="micro">
187
<i class="fa-solid fa-microphone-slash"></i>
188
</label>
173
189
<div id="send-button">
174
190
<i class="fa-solid fa-paper-plane-top"></i>
175
191
</div>
@@ -88,6 +88,8 @@ body {
88
88
background: var(--colour-1);
89
89
color: var(--colour-3);
90
90
height: 100vh;
91
max-width: 1600px;
92
margin: auto;
91
93
}
92
94
93
95
.row {
@@ -106,10 +108,6 @@ body {
106
108
border: 1px solid var(--blur-border);
107
109
}
108
110
109
.hidden {
110
display: none;
111
}
112
113
111
.conversations {
114
112
max-width: 280px;
115
113
padding: var(--section-gap);
@@ -138,8 +136,7 @@ body {
138
136
}
139
137
140
138
.conversation .user-input {
141
max-height: 200px;
142
margin-bottom: 10px;
139
margin-bottom: 4px;
143
140
}
144
141
145
142
.conversation .user-input input {
@@ -504,7 +501,8 @@ body {
504
501
display: none;
505
502
}
506
503
507
.file-label {
504
.file-label,
505
.micro-label {
508
506
cursor: pointer;
509
507
position: absolute;
510
508
top: 10px;
@@ -512,7 +510,8 @@ body {
512
510
}
513
511
514
512
.file-label:has(> input:valid),
515
.file-label.selected {
513
.file-label.selected,
514
.micro-label.recognition {
516
515
color: var(--accent);
517
516
}
518
517
@@ -520,11 +519,12 @@ label[for="image"] {
520
519
top: 32px;
521
520
}
522
521
523
label[for="camera"] {
522
label[for="micro"] {
524
523
top: 54px;
525
524
}
526
525
527
526
label[for="camera"] {
527
top: 74px;
528
528
display: none;
529
529
}
530
530
@@ -597,7 +597,7 @@ label[for="camera"] {
597
597
align-items: center;
598
598
justify-content: left;
599
599
width: 100%;
600
margin-bottom: 2px;
600
margin-bottom: 4px;
601
601
}
602
602
603
603
.field {
@@ -668,7 +668,7 @@ select {
668
668
display: flex;
669
669
flex-direction: column;
670
670
gap: 10px;
671
margin: 4px;
671
margin: 4px 0;
672
672
}
673
673
674
674
.bottom_buttons button {
@@ -841,7 +841,7 @@ a:-webkit-any-link {
841
841
color: var(--colour-3);
842
842
843
843
resize: vertical;
844
max-height: 150px;
844
max-height: 200px;
845
845
min-height: 80px;
846
846
}
847
847
@@ -1080,6 +1080,19 @@ a:-webkit-any-link {
1080
1080
padding: var(--inner-gap) 0;
1081
1081
}
1082
1082
1083
.settings input {
1084
background-color: transparent;
1085
padding: 2px;
1086
border: none;
1087
font-size: 15px;
1088
width: 100%;
1089
color: var(--colour-3);
1090
}
1091
1092
.settings input:focus {
1093
outline: none;
1094
}
1095
1083
1096
.settings .label {
1084
1097
font-size: 15px;
1085
1098
padding: var(--inner-gap) 0;
@@ -1100,7 +1113,7 @@ a:-webkit-any-link {
1100
1113
border-radius: 5px;
1101
1114
}
1102
1115
::-webkit-scrollbar-thumb:hover {
1103
background: var(--accent)
1116
background: var(--accent);
1104
1117
}
1105
1118
1106
1119
.hljs {
@@ -1114,8 +1127,13 @@ a:-webkit-any-link {
1114
1127
#message-input {
1115
1128
height: 82px;
1116
1129
margin-left: 20px;
1130
max-height: 200px;
1117
1131
}
1118
1132
1119
1133
#message-input::-webkit-scrollbar {
1120
1134
width: 5px;
1135
}
1136
1137
.hidden {
1138
display: none;
1121
1139
}
@@ -10,6 +10,7 @@ const sendButton = document.getElementById("send-button");
10
10
const imageInput = document.getElementById("image");
11
11
const cameraInput = document.getElementById("camera");
12
12
const fileInput = document.getElementById("file");
13
const microLabel = document.querySelector(".micro-label")
13
14
const inputCount = document.getElementById("input-count")
14
15
const providerSelect = document.getElementById("provider");
15
16
const modelSelect = document.getElementById("model");
@@ -81,7 +82,7 @@ const register_message_buttons = async () => {
81
82
if (!("click" in el.dataset)) {
82
83
el.dataset.click = "true";
83
84
el.addEventListener("click", async () => {
84
const message_el = el.parentElement.parentElement;
85
const message_el = el.parentElement.parentElement.parentElement;
85
86
const copyText = await get_message(window.conversation_id, message_el.dataset.index);
86
87
navigator.clipboard.writeText(copyText);
87
88
el.classList.add("clicked");
@@ -552,8 +553,10 @@ async function save_system_message() {
552
553
return;
553
554
}
554
555
const conversation = await get_conversation(window.conversation_id);
555
conversation.system = systemPrompt?.value;
556
await save_conversation(window.conversation_id, conversation);
556
if (conversation) {
557
conversation.system = systemPrompt?.value;
558
await save_conversation(window.conversation_id, conversation);
559
}
557
560
}
558
561
559
562
const hide_last_message = async (conversation_id) => {
@@ -586,8 +589,9 @@ const remove_message = async (conversation_id, index) => {
586
589
};
587
590
588
591
const get_message = async (conversation_id, index) => {
589
const conversation = await get_conversation(conversation_id);
590
return conversation.items[index]["content"];
592
const messages = await get_messages(conversation_id);
593
if (index in messages)
594
return messages[index]["content"];
591
595
};
592
596
593
597
const add_message = async (conversation_id, role, content, provider) => {
@@ -707,18 +711,27 @@ function open_settings() {
707
711
708
712
const register_settings_storage = async () => {
709
713
optionElements.forEach((element) => {
710
element.addEventListener('change', async (event) => {
711
switch (event.target.type) {
712
case "checkbox":
713
appStorage.setItem(element.id, event.target.checked);
714
break;
715
case "select-one":
716
appStorage.setItem(element.id, event.target.selectedIndex);
717
break;
718
default:
719
console.warn("Unresolved element type");
720
}
721
});
714
if (element.type == "textarea") {
715
element.addEventListener('input', async (event) => {
716
appStorage.setItem(element.id, element.value);
717
});
718
} else {
719
element.addEventListener('change', async (event) => {
720
switch (element.type) {
721
case "checkbox":
722
appStorage.setItem(element.id, element.checked);
723
break;
724
case "select-one":
725
appStorage.setItem(element.id, element.selectedIndex);
726
break;
727
case "text":
728
appStorage.setItem(element.id, element.value);
729
break;
730
default:
731
console.warn("Unresolved element type");
732
}
733
});
734
}
722
735
});
723
736
}
724
737
@@ -735,6 +748,10 @@ const load_settings_storage = async () => {
735
748
case "select-one":
736
749
element.selectedIndex = parseInt(value);
737
750
break;
751
case "text":
752
case "textarea":
753
element.value = value;
754
break;
738
755
default:
739
756
console.warn("Unresolved element type");
740
757
}
@@ -831,7 +848,7 @@ systemPrompt.addEventListener("focus", function() {
831
848
countFocus = systemPrompt;
832
849
count_input();
833
850
});
834
systemPrompt.addEventListener("blur", function() {
851
systemPrompt.addEventListener("input", function() {
835
852
countFocus = messageInput;
836
853
count_input();
837
854
});
@@ -911,6 +928,15 @@ async function on_api() {
911
928
systemPrompt.classList.remove("hidden");
912
929
}
913
930
});
931
const messageInputHeight = document.getElementById("message-input-height");
932
if (messageInputHeight) {
933
if (messageInputHeight.value) {
934
messageInput.style.maxHeight = `${messageInputHeight.value}px`;
935
}
936
messageInputHeight.addEventListener('change', async () => {
937
messageInput.style.maxHeight = `${messageInputHeight.value}px`;
938
});
939
}
914
940
}
915
941
916
942
async function load_version() {
@@ -980,7 +1006,7 @@ fileInput.addEventListener('change', async (event) => {
980
1006
}
981
1007
});
982
1008
983
systemPrompt?.addEventListener("blur", async () => {
1009
systemPrompt?.addEventListener("input", async () => {
984
1010
await save_system_message();
985
1011
});
986
1012
@@ -1092,7 +1118,7 @@ function save_storage() {
1092
1118
}
1093
1119
}
1094
1120
data = JSON.stringify(data, null, 4);
1095
const blob = new Blob([data], {type: 'text/csv'});
1121
const blob = new Blob([data], {type: 'application/json'});
1096
1122
if(window.navigator.msSaveOrOpenBlob) {
1097
1123
window.navigator.msSaveBlob(blob, filename);
1098
1124
} else{
@@ -1103,4 +1129,69 @@ function save_storage() {
1103
1129
elem.click();
1104
1130
document.body.removeChild(elem);
1105
1131
}
1132
}
1133
1134
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
1135
if (SpeechRecognition) {
1136
const mircoIcon = microLabel.querySelector("i");
1137
mircoIcon.classList.add("fa-microphone");
1138
mircoIcon.classList.remove("fa-microphone-slash");
1139
1140
const recognition = new SpeechRecognition();
1141
recognition.continuous = true;
1142
recognition.interimResults = true;
1143
recognition.maxAlternatives = 1;
1144
1145
function may_stop() {
1146
if (microLabel.classList.contains("recognition")) {
1147
recognition.stop();
1148
}
1149
}
1150
1151
let startValue;
1152
let timeoutHandle;
1153
recognition.onstart = function() {
1154
microLabel.classList.add("recognition");
1155
startValue = messageInput.value;
1156
timeoutHandle = window.setTimeout(may_stop, 8000);
1157
};
1158
recognition.onend = function() {
1159
microLabel.classList.remove("recognition");
1160
};
1161
recognition.onresult = function(event) {
1162
if (!event.results) {
1163
return;
1164
}
1165
window.clearTimeout(timeoutHandle);
1166
let notFinal = "";
1167
event.results.forEach((result) => {
1168
const newText = result[0].transcript;
1169
if (newText) {
1170
if (result.isFinal) {
1171
messageInput.value = `${startValue ? startValue+"\n" : ""}${newText.trim()}`;
1172
startValue = messageInput.value;
1173
notFinal = "";
1174
messageInput.focus();
1175
} else {
1176
notFinal += newText;
1177
messageInput.value = `${startValue ? startValue+"\n" : ""}${notFinal.trim()}`;
1178
}
1179
messageInput.style.height = messageInput.scrollHeight + "px";
1180
messageInput.scrollTop = messageInput.scrollHeight;
1181
}
1182
});
1183
window.clearTimeout(timeoutHandle);
1184
timeoutHandle = window.setTimeout(may_stop, notFinal ? 5000 : 8000);
1185
};
1186
1187
microLabel.addEventListener("click", () => {
1188
if (microLabel.classList.contains("recognition")) {
1189
window.clearTimeout(timeoutHandle);
1190
recognition.stop();
1191
} else {
1192
const lang = document.getElementById("recognition-language")?.value || navigator.language;
1193
recognition.lang = lang;
1194
recognition.start();
1195
}
1196
});
1106
1197
}