Fix build docker image

This commit is contained in:
hlohaus 2025-05-17 17:35:35 +02:00
parent 082c152248
commit c0e323b543
6 changed files with 285 additions and 43 deletions

View file

@ -33,7 +33,7 @@ COPY docker/supervisor-api.conf /etc/supervisor/conf.d/api.conf
COPY docker/background.png /usr/share/images/fluxbox/ubuntu-light.png
# Add user, fix permissions
RUN chown "${SEL_UID}:${SEL_GID}" $HOME/.local
RUN chown "${SEL_UID}:${SEL_GID}" $HOME/.local /opt/venv/share
# Switch user
USER $SEL_UID

View file

@ -3,14 +3,16 @@ from __future__ import annotations
import os
import json
import time
import nodriver
import asyncio
from typing import Dict, Any, AsyncIterator
from ...typing import Messages, Cookies, AsyncResult
from ...typing import Messages, AsyncResult
from ...providers.response import JsonConversation, Reasoning, ImagePreview, ImageResponse, TitleGeneration, AuthResult, RequestLogin
from ...requests import StreamSession, get_args_from_nodriver, DEFAULT_HEADERS
from ...requests import StreamSession, get_nodriver, DEFAULT_HEADERS
from ...requests.raise_for_status import raise_for_status
from ..base_provider import AsyncAuthedProvider, ProviderModelMixin
from ..helper import format_prompt, get_cookies, get_last_user_message
from ..helper import format_prompt, get_last_user_message
class Conversation(JsonConversation):
def __init__(self,
@ -33,30 +35,40 @@ class Grok(AsyncAuthedProvider, ProviderModelMixin):
model_aliases = {"grok-3-r1": "grok-3-thinking"}
@classmethod
async def on_auth_async(cls, cookies: Cookies = None, proxy: str = None, **kwargs) -> AsyncIterator:
if cookies is None:
cookies = get_cookies(cls.cookie_domain, False, True, False)
if cookies is not None and "sso" in cookies:
yield AuthResult(
cookies=cookies,
impersonate="chrome",
proxy=proxy,
headers=DEFAULT_HEADERS
)
return
async def on_auth_async(cls, proxy: str = None, **kwargs) -> AsyncIterator:
auth_result = AuthResult(headers=DEFAULT_HEADERS, impersonate="chrome")
auth_result.headers["referer"] = cls.url + "/"
browser, stop_browser = await get_nodriver(proxy=proxy)
yield RequestLogin(cls.__name__, os.environ.get("G4F_LOGIN_URL") or "")
yield AuthResult(
**await get_args_from_nodriver(
cls.url,
proxy=proxy,
wait_for='[href="/chat#private"]'
)
)
try:
page = browser.main_tab
has_headers = False
def on_request(event: nodriver.cdp.network.RequestWillBeSent, page=None):
nonlocal has_headers
if event.request.url.startswith(cls.conversation_url + "/new"):
for key, value in event.request.headers.items():
auth_result.headers[key.lower()] = value
has_headers = True
await page.send(nodriver.cdp.network.enable())
page.add_handler(nodriver.cdp.network.RequestWillBeSent, on_request)
page = await browser.get(cls.url)
auth_result.headers["user-agent"] = await page.evaluate("window.navigator.userAgent", return_by_value=True)
while True:
if has_headers:
break
await asyncio.sleep(1)
auth_result.cookies = {}
for c in await page.send(nodriver.cdp.network.get_cookies([cls.url])):
auth_result.cookies[c.name] = c.value
await page.close()
finally:
stop_browser()
yield auth_result
@classmethod
async def _prepare_payload(cls, model: str, message: str) -> Dict[str, Any]:
return {
"temporary": False,
"temporary": True,
"modelName": "grok-latest" if model == "grok-2" else "grok-3",
"message": message,
"fileAttachments": [],
@ -83,7 +95,6 @@ class Grok(AsyncAuthedProvider, ProviderModelMixin):
model: str,
messages: Messages,
auth_result: AuthResult,
return_conversation: bool = True,
conversation: Conversation = None,
**kwargs
) -> AsyncResult:
@ -99,7 +110,6 @@ class Grok(AsyncAuthedProvider, ProviderModelMixin):
url = f"{cls.conversation_url}/{conversation_id}/responses"
async with session.post(url, json=payload) as response:
await raise_for_status(response)
thinking_duration = None
async for line in response.iter_lines():
if line:
@ -133,9 +143,7 @@ class Grok(AsyncAuthedProvider, ProviderModelMixin):
title = result.get("title", {}).get("newTitle", "")
if title:
yield TitleGeneration(title)
except json.JSONDecodeError:
continue
if return_conversation and conversation_id is not None:
yield Conversation(conversation_id)
# if conversation_id is not None:
# yield Conversation(conversation_id)

View file

@ -41,7 +41,7 @@ except NameError:
def add_chunk(content, chunk):
if content == "" and isinstance(chunk, (MediaResponse, AudioResponse)):
content = chunk
else:
elif not isinstance(chunk, Reasoning):
content = str(content) + str(chunk)
return content
@ -65,6 +65,7 @@ def iter_response(
stop: Optional[list[str]] = None
) -> ChatCompletionResponseType:
content = ""
reasoning_content = []
finish_reason = None
tool_calls = None
usage = None
@ -92,9 +93,8 @@ def iter_response(
elif isinstance(chunk, ProviderInfo):
provider = chunk
continue
elif isinstance(chunk, BaseConversation):
yield chunk
continue
elif isinstance(chunk, Reasoning):
reasoning_content.append(chunk)
elif isinstance(chunk, HiddenResponse):
continue
elif isinstance(chunk, Exception):
@ -141,7 +141,8 @@ def iter_response(
chat_completion = ChatCompletion.model_construct(
content, finish_reason, completion_id, int(time.time()), usage=usage,
**filter_none(tool_calls=[ToolCallModel.model_construct(**tool_call) for tool_call in tool_calls]) if tool_calls is not None else {},
conversation=None if conversation is None else conversation.get_dict()
conversation=None if conversation is None else conversation.get_dict(),
reasoning_content=reasoning_content if reasoning_content else None
)
if provider is not None:
chat_completion.provider = provider.name
@ -168,6 +169,7 @@ async def async_iter_response(
stop: Optional[list[str]] = None
) -> AsyncChatCompletionResponseType:
content = ""
reasoning_content = []
finish_reason = None
completion_id = ''.join(random.choices(string.ascii_letters + string.digits, k=28))
idx = 0
@ -193,6 +195,8 @@ async def async_iter_response(
elif isinstance(chunk, ProviderInfo):
provider = chunk
continue
elif isinstance(chunk, Reasoning) and not stream:
reasoning_content.append(chunk)
elif isinstance(chunk, HiddenResponse):
continue
elif isinstance(chunk, Exception):
@ -241,7 +245,8 @@ async def async_iter_response(
**filter_none(
tool_calls=[ToolCallModel.model_construct(**tool_call) for tool_call in tool_calls]
) if tool_calls is not None else {},
conversation=conversation
conversation=conversation,
reasoning_content=reasoning_content if reasoning_content else None
)
if provider is not None:
chat_completion.provider = provider.name

View file

@ -7,6 +7,7 @@ from time import time
from ..image import extract_data_uri
from ..image.copy_images import get_media_dir
from ..client.helper import filter_markdown
from ..providers.response import Reasoning
from .helper import filter_none
try:
@ -132,16 +133,21 @@ class ResponseMessageContent(BaseModel):
class ChatCompletionMessage(BaseModel):
role: str
content: str
reasoning_content: list[Reasoning] = None
tool_calls: list[ToolCallModel] = None
@classmethod
def model_construct(cls, content: str, tool_calls: list = None):
return super().model_construct(role="assistant", content=content, **filter_none(tool_calls=tool_calls))
def model_construct(cls, content: str, reasoning_content: list[Reasoning] = None, tool_calls: list = None):
return super().model_construct(role="assistant", content=content, **filter_none(tool_calls=tool_calls, reasoning_content=reasoning_content))
@field_serializer('content')
def serialize_content(self, content: str):
return str(content)
@field_serializer('reasoning_content')
def serialize_reasoning_content(self, reasoning_content: list):
return "".join([str(content) for content in reasoning_content]) if reasoning_content else None
def save(self, filepath: str, allowed_types = None):
if hasattr(self.content, "data"):
os.rename(self.content.data.replace("/media", get_media_dir()), filepath)
@ -184,7 +190,8 @@ class ChatCompletion(BaseModel):
created: int = None,
tool_calls: list[ToolCallModel] = None,
usage: UsageModel = None,
conversation: dict = None
conversation: dict = None,
reasoning_content: list[Reasoning] = None
):
return super().model_construct(
id=f"chatcmpl-{completion_id}" if completion_id else None,
@ -193,7 +200,7 @@ class ChatCompletion(BaseModel):
model=None,
provider=None,
choices=[ChatCompletionChoice.model_construct(
ChatCompletionMessage.model_construct(content, tool_calls),
ChatCompletionMessage.model_construct(content, reasoning_content, tool_calls),
finish_reason,
)],
**filter_none(usage=usage, conversation=conversation)
@ -245,16 +252,20 @@ class ClientResponse(BaseModel):
class ChatCompletionDelta(BaseModel):
role: str
content: Optional[str]
reasoning_content: Optional[str] = None
@classmethod
def model_construct(cls, content: Optional[str]):
if isinstance(content, Reasoning):
return super().model_construct(role="reasoning", content=content, reasoning_content=str(content))
return super().model_construct(role="assistant", content=content)
@field_serializer('content')
def serialize_content(self, content: Optional[str]):
if content is None:
return ""
if isinstance(content, Reasoning):
return None
return str(content)
class ChatCompletionDeltaChoice(BaseModel):

View file

@ -93,7 +93,10 @@ def secure_filename(filename: str) -> str:
unquote(filename).strip(),
flags=re.UNICODE
)
filename = filename[:100].strip(".,_-+")
max_length = 100
if len(filename.encode()) > len(filename):
max_length = 50
filename = filename[:max_length].strip(".,_+-")
return filename
def supports_filename(filename: str):

215
new.py Normal file
View file

@ -0,0 +1,215 @@
import json
import uuid
import g4f.debug
import requests
from g4f.client import Client
def upload_and_process(files_or_urls, bucket_id=None):
if bucket_id is None:
bucket_id = str(uuid.uuid4())
if isinstance(files_or_urls, list): # URLs
files = {'files': ('downloads.json', json.dumps(
files_or_urls), 'application/json')}
elif isinstance(files_or_urls, dict): # Files
files = files_or_urls
else:
raise ValueError(
"files_or_urls must be a list of URLs or a dictionary of files")
upload_response = requests.post(
f'http://localhost:8080/backend-api/v2/files/{bucket_id}', files=files)
if upload_response.status_code == 200:
upload_data = upload_response.json()
print(f"Upload successful. Bucket ID: {upload_data['bucket_id']}")
else:
print(
f"Upload failed: {upload_response.status_code} - {upload_response.text}")
response = requests.get(
f'http://localhost:8080/backend-api/v2/files/{bucket_id}', stream=True, headers={'Accept': 'text/event-stream'})
for line in response.iter_lines():
if line:
line = line.decode('utf-8')
if line.startswith('data:'):
try:
data = json.loads(line[5:]) # remove data: prefix
if "action" in data:
print(f"SSE Event: {data}")
elif "error" in data:
print(f"Error: {data['error']['message']}")
else:
# Assuming it's file content
print(f"File data received: {data}")
except json.JSONDecodeError as e:
print(f"Error decoding JSON: {e}")
else:
print(f"Unhandled SSE event: {line}")
response.close()
return bucket_id
# Example with URLs
# Enable debug mode
g4f.debug.logging = True
client = Client()
# Upload example file
files = {'files': ('demo.docx', open('demo.docx', 'rb'))}
bucket_id = upload_and_process(files)
# Send request with file:
response = client.chat.completions.create(
model='gpt-4o',
messages=[{"role": "user", "content": [
{"type": "text", "text": "Discribe this file."},
{"bucket_id": bucket_id}
]}],
)
print(response.choices[0].message.content)
exit()
# import asyncio
# from g4f.client import AsyncClient
# import g4f.Provider
# async def main():
# client = AsyncClient(provider=g4f.Provider.MarkItDown)
# # Transcribe a audio file
# with open("audio.wav", "rb") as audio_file:
# response = await client.chat.completions.create("", media=[audio_file])
# print(response.choices[0].message.content)
# if __name__ == "__main__":
# asyncio.run(main())
#exit()
import requests
# Open the audio file in binary mode
with open('demo.docx', 'rb') as audio_file:
# Make the POST request
response = requests.post('http://localhost:8080/api/markitdown', files={'file': audio_file})
# Check the response and print the transcription
if response.status_code == 200:
data = response.json()
print(data['text'])
else:
print(f"Error: {response.status_code}, {response.text}")
exit()
# from openai import OpenAI
# client = OpenAI(base_url="http://localhost:8080/v1", api_key="secret")
# with open("audio.wav", "rb") as file:
# transcript = client.audio.transcriptions.create(
# model="",
# extra_body={"provider": "MarkItDown"},
# file=file
# )
# print(transcript.text)
exit()
import asyncio
import time
from g4f import AsyncClient
from g4f.Provider import PollinationsAI
async def test():
client = AsyncClient()
response = client.chat.completions.create("guten tag", stream=True, provider=HarProvider)
async for chunk in response:
if chunk.choices[0].finish_reason == "stop":
break
print(chunk.choices[0].delta.content, end="", flush=True)
print()
asyncio.run(test())
time.sleep(1)
exit()
client = Client(provider=PollinationsAI)
response = client.media.generate("Hello", model="hypnosis-tracy")
response.data[0].save("hypnosis.mp3")
client = Client(provider=Gemini)
response = client.media.generate("Hello", model="gemini-audio")
response.data[0].save("gemini.ogx")
client = Client(provider=EdgeTTS)
response = client.media.generate("Hello", audio={"locale": "en-US"})
response.data[0].save("edge-tts.mp3")
exit()
import requests
import uuid
import json
def upload_and_process(files_or_urls, bucket_id=None):
if bucket_id is None:
bucket_id = str(uuid.uuid4())
if isinstance(files_or_urls, list): #URLs
files = {'files': ('downloads.json', json.dumps(files_or_urls), 'application/json')}
elif isinstance(files_or_urls, dict): #Files
files = files_or_urls
else:
raise ValueError("files_or_urls must be a list of URLs or a dictionary of files")
upload_response = requests.post(f'http://localhost:8080/v1/files/{bucket_id}', files=files)
if upload_response.status_code == 200:
upload_data = upload_response.json()
print(f"Upload successful. Bucket ID: {upload_data['bucket_id']}")
else:
print(f"Upload failed: {upload_response.status_code} - {upload_response.text}")
response = requests.get(f'http://localhost:8080/v1/files/{bucket_id}', stream=True, headers={'Accept': 'text/event-stream'})
for line in response.iter_lines():
if line:
line = line.decode('utf-8')
if line.startswith('data:'):
try:
data = json.loads(line[5:]) #remove data: prefix
if "action" in data:
print(f"SSE Event: {data}")
elif "error" in data:
print(f"Error: {data['error']['message']}")
else:
print(f"File data received: {data}") #Assuming it's file content
except json.JSONDecodeError as e:
print(f"Error decoding JSON: {e}")
else:
print(f"Unhandled SSE event: {line}")
response.close()
return bucket_id
# Example with URLs
#Example with files
#files = {'files': open('document.pdf', 'rb'), 'files': open('data.json', 'rb')}
#bucket_id = upload_and_process(files)
import asyncio
from g4f.client import Client
import g4f.debug
g4f.debug.logging = True
client = Client()
files = {'files': ('demo.docx', open('demo.docx', 'rb'))}
bucket_id = upload_and_process(files)
response = client.chat.completions.create(
[{"role": "user", "content": [
{"type": "text", "text": "Discribe this file."},
{"bucket_id": bucket_id}
]}],
"o1",
)
print(response.choices[0].message.content)