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

XFEstudio/gpt4free

Add DuckDuckGo Provider, Add SpeechRecognition to gui

24345bc0
Heiner Lohaus <hlohaus@users.noreply.github.com>
提交于

代码差异

4 个文件 +226 -37
Added g4f/Provider/DuckDuckGo.py +64 -0
@@ -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"]
Modified g4f/gui/client/index.html +20 -4
@@ -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>
Modified g4f/gui/client/static/css/style.css +31 -13
@@ -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 }
Modified g4f/gui/client/static/js/chat.v1.js +111 -20
@@ -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 }