diff --git a/g4f/Provider/Blackbox.py b/g4f/Provider/Blackbox.py
index d8669884..10abded5 100644
--- a/g4f/Provider/Blackbox.py
+++ b/g4f/Provider/Blackbox.py
@@ -239,7 +239,7 @@ class Blackbox(AsyncGeneratorProvider, ProviderModelMixin):
yield ImageResponse(images=[image_url], alt=prompt)
return
- if conversation is None:
+ if conversation is None or not hasattr(conversation, "chat_id"):
conversation = Conversation(model)
conversation.validated_value = await cls.fetch_validated()
conversation.chat_id = cls.generate_chat_id()
diff --git a/g4f/Provider/DDG.py b/g4f/Provider/DDG.py
index da29652c..2ac786be 100644
--- a/g4f/Provider/DDG.py
+++ b/g4f/Provider/DDG.py
@@ -50,6 +50,8 @@ class DDG(AsyncGeneratorProvider, ProviderModelMixin):
@classmethod
def validate_model(cls, model: str) -> str:
"""Validates and returns the correct model name"""
+ if not model:
+ return cls.default_model
if model in cls.model_aliases:
model = cls.model_aliases[model]
if model not in cls.models:
diff --git a/g4f/Provider/needs_auth/Custom.py b/g4f/Provider/needs_auth/Custom.py
index ceb46973..2bd7c014 100644
--- a/g4f/Provider/needs_auth/Custom.py
+++ b/g4f/Provider/needs_auth/Custom.py
@@ -7,4 +7,8 @@ class Custom(OpenaiTemplate):
working = True
needs_auth = False
api_base = "http://localhost:8080/v1"
- sort_models = False
\ No newline at end of file
+ sort_models = False
+
+class Feature(Custom):
+ label = "Feature Provider"
+ working = False
\ No newline at end of file
diff --git a/g4f/Provider/needs_auth/__init__.py b/g4f/Provider/needs_auth/__init__.py
index 426c9874..348d9056 100644
--- a/g4f/Provider/needs_auth/__init__.py
+++ b/g4f/Provider/needs_auth/__init__.py
@@ -3,6 +3,7 @@ from .BingCreateImages import BingCreateImages
from .Cerebras import Cerebras
from .CopilotAccount import CopilotAccount
from .Custom import Custom
+from .Custom import Feature
from .DeepInfra import DeepInfra
from .DeepSeek import DeepSeek
from .Gemini import Gemini
diff --git a/g4f/gui/client/static/js/chat.v1.js b/g4f/gui/client/static/js/chat.v1.js
index 51d2af11..0b2596a9 100644
--- a/g4f/gui/client/static/js/chat.v1.js
+++ b/g4f/gui/client/static/js/chat.v1.js
@@ -937,15 +937,17 @@ const ask_gpt = async (message_id, message_index = -1, regenerate = false, provi
}
try {
let api_key;
- if (is_demo && provider != "Custom") {
+ if (is_demo && provider == "Feature") {
+ api_key = localStorage.getItem("user");
+ } else if (is_demo && provider != "Custom") {
api_key = localStorage.getItem("HuggingFace-api_key");
- if (!api_key) {
- location.href = "/";
- return;
- }
} else {
api_key = get_api_key_by_provider(provider);
}
+ if (is_demo && !api_key && provider != "Custom") {
+ location.href = "/";
+ return;
+ }
const input = imageInput && imageInput.files.length > 0 ? imageInput : cameraInput;
const files = input && input.files.length > 0 ? input.files : null;
const download_images = document.getElementById("download_images")?.checked;
@@ -1897,7 +1899,10 @@ async function on_api() {
location.href = "/";
return;
}
- providerSelect.innerHTML = '';
+ providerSelect.innerHTML = `
+
+
+ `;
providerSelect.selectedIndex = 0;
document.getElementById("pin").disabled = true;
document.getElementById("refine")?.parentElement.classList.add("hidden")
diff --git a/g4f/gui/server/backend_api.py b/g4f/gui/server/backend_api.py
index 639e3733..1baea671 100644
--- a/g4f/gui/server/backend_api.py
+++ b/g4f/gui/server/backend_api.py
@@ -134,7 +134,7 @@ class Backend_Api(Api):
else:
json_data = request.json
- if app.demo and json_data.get("provider") != "Custom":
+ if app.demo and json_data.get("provider") not in ["Custom", "Feature"]:
model = json_data.get("model")
if model != "default" and model in models.demo_models:
json_data["provider"] = random.choice(models.demo_models[model][1])
diff --git a/g4f/tools/run_tools.py b/g4f/tools/run_tools.py
index 7cc9e3e2..f82d8510 100644
--- a/g4f/tools/run_tools.py
+++ b/g4f/tools/run_tools.py
@@ -3,11 +3,14 @@ from __future__ import annotations
import re
import json
import asyncio
+from pathlib import Path
from typing import Optional, Callable, AsyncIterator
from ..typing import Messages
from ..providers.helper import filter_none
from ..providers.asyncio import to_async_iterator
+from ..providers.types import ProviderType
+from ..cookies import get_cookies_dir
from .web_search import do_search, get_search_message
from .files import read_bucket, get_bucket_dir
from .. import debug
@@ -27,7 +30,10 @@ def validate_arguments(data: dict) -> dict:
else:
return {}
-async def async_iter_run_tools(async_iter_callback, model, messages, tool_calls: Optional[list] = None, **kwargs):
+def get_api_key_file(cls) -> Path:
+ return Path(get_cookies_dir()) / f"api_key_{cls.parent if hasattr(cls, 'parent') else cls.__name__}.json"
+
+async def async_iter_run_tools(provider: ProviderType, model: str, messages, tool_calls: Optional[list] = None, **kwargs):
# Handle web_search from kwargs
web_search = kwargs.get('web_search')
if web_search:
@@ -40,6 +46,15 @@ async def async_iter_run_tools(async_iter_callback, model, messages, tool_calls:
# Keep web_search in kwargs for provider native support
pass
+ # Read api_key from config file
+ if provider.needs_auth and "api_key" not in kwargs:
+ auth_file = get_api_key_file(provider)
+ if auth_file.exists():
+ with auth_file.open("r") as f:
+ auth_result = json.load(f)
+ if "api_key" in auth_result:
+ kwargs["api_key"] = auth_result["api_key"]
+
if tool_calls is not None:
for tool in tool_calls:
if tool.get("type") == "function":
@@ -66,8 +81,8 @@ async def async_iter_run_tools(async_iter_callback, model, messages, tool_calls:
message["content"] = new_message_content
if has_bucket and isinstance(messages[-1]["content"], str):
messages[-1]["content"] += BUCKET_INSTRUCTIONS
-
- response = to_async_iterator(async_iter_callback(model=model, messages=messages, **kwargs))
+ create_function = provider.get_async_create_function()
+ response = to_async_iterator(create_function(model=model, messages=messages, **kwargs))
async for chunk in response:
yield chunk
@@ -91,6 +106,15 @@ def iter_run_tools(
# Keep web_search in kwargs for provider native support
pass
+ # Read api_key from config file
+ if provider is not None and provider.needs_auth and "api_key" not in kwargs:
+ auth_file = get_api_key_file(provider)
+ if auth_file.exists():
+ with auth_file.open("r") as f:
+ auth_result = json.load(f)
+ if "api_key" in auth_result:
+ kwargs["api_key"] = auth_result["api_key"]
+
if tool_calls is not None:
for tool in tool_calls:
if tool.get("type") == "function":