mirror of
https://github.com/arc53/DocsGPT.git
synced 2025-11-29 08:33:20 +00:00
Compare commits
2 Commits
fix-tool-n
...
tool-proxi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c740933782 | ||
|
|
6d3134c944 |
@@ -11,4 +11,7 @@ class AgentCreator:
|
||||
agent_class = cls.agents.get(type.lower())
|
||||
if not agent_class:
|
||||
raise ValueError(f"No agent class found for type {type}")
|
||||
config = kwargs.pop('config', None)
|
||||
if isinstance(config, dict) and 'proxy_id' in config and 'proxy_id' not in kwargs:
|
||||
kwargs['proxy_id'] = config['proxy_id']
|
||||
return agent_class(*args, **kwargs)
|
||||
|
||||
@@ -17,6 +17,7 @@ class BaseAgent:
|
||||
api_key,
|
||||
user_api_key=None,
|
||||
decoded_token=None,
|
||||
proxy_id=None,
|
||||
):
|
||||
self.endpoint = endpoint
|
||||
self.llm = LLMCreator.create_llm(
|
||||
@@ -30,6 +31,7 @@ class BaseAgent:
|
||||
self.tools = []
|
||||
self.tool_config = {}
|
||||
self.tool_calls = []
|
||||
self.proxy_id = proxy_id
|
||||
|
||||
def gen(self, *args, **kwargs) -> Generator[Dict, None, None]:
|
||||
raise NotImplementedError('Method "gen" must be implemented in the child class')
|
||||
@@ -41,6 +43,11 @@ class BaseAgent:
|
||||
user_tools = user_tools_collection.find({"user": user, "status": True})
|
||||
user_tools = list(user_tools)
|
||||
tools_by_id = {str(tool["_id"]): tool for tool in user_tools}
|
||||
if hasattr(self, 'proxy_id') and self.proxy_id:
|
||||
for tool_id, tool in tools_by_id.items():
|
||||
if 'config' not in tool:
|
||||
tool['config'] = {}
|
||||
tool['config']['proxy_id'] = self.proxy_id
|
||||
return tools_by_id
|
||||
|
||||
def _build_tool_parameters(self, action):
|
||||
@@ -126,6 +133,7 @@ class BaseAgent:
|
||||
"method": tool_data["config"]["actions"][action_name]["method"],
|
||||
"headers": headers,
|
||||
"query_params": query_params,
|
||||
"proxy_id": self.proxy_id,
|
||||
}
|
||||
if tool_data["name"] == "api_tool"
|
||||
else tool_data["config"]
|
||||
|
||||
@@ -18,9 +18,10 @@ class ClassicAgent(BaseAgent):
|
||||
prompt="",
|
||||
chat_history=None,
|
||||
decoded_token=None,
|
||||
proxy_id=None,
|
||||
):
|
||||
super().__init__(
|
||||
endpoint, llm_name, gpt_model, api_key, user_api_key, decoded_token
|
||||
endpoint, llm_name, gpt_model, api_key, user_api_key, decoded_token, proxy_id
|
||||
)
|
||||
self.user = decoded_token.get("sub")
|
||||
self.prompt = prompt
|
||||
|
||||
@@ -23,15 +23,43 @@ class APITool(Tool):
|
||||
)
|
||||
|
||||
def _make_api_call(self, url, method, headers, query_params, body):
|
||||
sanitized_headers = {}
|
||||
for key, value in headers.items():
|
||||
if isinstance(value, str):
|
||||
sanitized_value = value.encode('latin-1', errors='ignore').decode('latin-1')
|
||||
sanitized_headers[key] = sanitized_value
|
||||
else:
|
||||
sanitized_headers[key] = value
|
||||
|
||||
if query_params:
|
||||
url = f"{url}?{requests.compat.urlencode(query_params)}"
|
||||
if isinstance(body, dict):
|
||||
body = json.dumps(body)
|
||||
response = None
|
||||
try:
|
||||
print(f"Making API call: {method} {url} with body: {body}")
|
||||
if body == "{}":
|
||||
body = None
|
||||
response = requests.request(method, url, headers=headers, data=body)
|
||||
|
||||
proxy_id = self.config.get("proxy_id", None)
|
||||
request_kwargs = {
|
||||
'method': method,
|
||||
'url': url,
|
||||
'headers': sanitized_headers,
|
||||
'data': body
|
||||
}
|
||||
try:
|
||||
if proxy_id:
|
||||
from application.agents.tools.proxy_handler import apply_proxy_to_request
|
||||
response = apply_proxy_to_request(
|
||||
requests.request,
|
||||
proxy_id=proxy_id,
|
||||
**request_kwargs
|
||||
)
|
||||
else:
|
||||
response = requests.request(**request_kwargs)
|
||||
except ImportError:
|
||||
response = requests.request(**request_kwargs)
|
||||
response.raise_for_status()
|
||||
content_type = response.headers.get(
|
||||
"Content-Type", "application/json"
|
||||
|
||||
63
application/agents/tools/proxy_handler.py
Normal file
63
application/agents/tools/proxy_handler.py
Normal file
@@ -0,0 +1,63 @@
|
||||
import logging
|
||||
import requests
|
||||
from typing import Dict, Optional
|
||||
from bson.objectid import ObjectId
|
||||
|
||||
from application.core.mongo_db import MongoDB
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Get MongoDB connection
|
||||
mongo = MongoDB.get_client()
|
||||
db = mongo["docsgpt"]
|
||||
proxies_collection = db["proxies"]
|
||||
|
||||
def get_proxy_config(proxy_id: str) -> Optional[Dict[str, str]]:
|
||||
"""
|
||||
Retrieve proxy configuration from the database.
|
||||
|
||||
Args:
|
||||
proxy_id: The ID of the proxy configuration
|
||||
|
||||
Returns:
|
||||
A dictionary with proxy configuration or None if not found
|
||||
"""
|
||||
if not proxy_id or proxy_id == "none":
|
||||
return None
|
||||
|
||||
try:
|
||||
if ObjectId.is_valid(proxy_id):
|
||||
proxy_config = proxies_collection.find_one({"_id": ObjectId(proxy_id)})
|
||||
if proxy_config and "connection" in proxy_config:
|
||||
connection_str = proxy_config["connection"].strip()
|
||||
if connection_str:
|
||||
# Format proxy for requests library
|
||||
return {
|
||||
"http": connection_str,
|
||||
"https": connection_str
|
||||
}
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"Error retrieving proxy configuration: {e}")
|
||||
return None
|
||||
|
||||
def apply_proxy_to_request(request_func, proxy_id=None, **kwargs):
|
||||
"""
|
||||
Apply proxy configuration to a requests function if available.
|
||||
This is a minimal wrapper that doesn't change the function signature.
|
||||
|
||||
Args:
|
||||
request_func: The requests function to call (e.g., requests.get, requests.post)
|
||||
proxy_id: Optional proxy ID to use
|
||||
**kwargs: Arguments to pass to the request function
|
||||
|
||||
Returns:
|
||||
The response from the request
|
||||
"""
|
||||
if proxy_id:
|
||||
proxy_config = get_proxy_config(proxy_id)
|
||||
if proxy_config:
|
||||
kwargs['proxies'] = proxy_config
|
||||
logger.info(f"Using proxy for request")
|
||||
|
||||
return request_func(**kwargs)
|
||||
@@ -335,6 +335,9 @@ class Stream(Resource):
|
||||
"prompt_id": fields.String(
|
||||
required=False, default="default", description="Prompt ID"
|
||||
),
|
||||
"proxy_id": fields.String(
|
||||
required=False, description="Proxy ID to use for API calls"
|
||||
),
|
||||
"chunks": fields.Integer(
|
||||
required=False, default=2, description="Number of chunks"
|
||||
),
|
||||
@@ -376,6 +379,7 @@ class Stream(Resource):
|
||||
)
|
||||
conversation_id = data.get("conversation_id")
|
||||
prompt_id = data.get("prompt_id", "default")
|
||||
proxy_id = data.get("proxy_id", None)
|
||||
|
||||
index = data.get("index", None)
|
||||
chunks = int(data.get("chunks", 2))
|
||||
@@ -386,6 +390,7 @@ class Stream(Resource):
|
||||
data_key = get_data_from_api_key(data["api_key"])
|
||||
chunks = int(data_key.get("chunks", 2))
|
||||
prompt_id = data_key.get("prompt_id", "default")
|
||||
proxy_id = data_key.get("proxy_id", None)
|
||||
source = {"active_docs": data_key.get("source")}
|
||||
retriever_name = data_key.get("retriever", retriever_name)
|
||||
user_api_key = data["api_key"]
|
||||
@@ -422,6 +427,7 @@ class Stream(Resource):
|
||||
api_key=settings.API_KEY,
|
||||
user_api_key=user_api_key,
|
||||
prompt=prompt,
|
||||
proxy_id=proxy_id,
|
||||
chat_history=history,
|
||||
decoded_token=decoded_token,
|
||||
)
|
||||
@@ -496,6 +502,9 @@ class Answer(Resource):
|
||||
"prompt_id": fields.String(
|
||||
required=False, default="default", description="Prompt ID"
|
||||
),
|
||||
"proxy_id": fields.String(
|
||||
required=False, description="Proxy ID to use for API calls"
|
||||
),
|
||||
"chunks": fields.Integer(
|
||||
required=False, default=2, description="Number of chunks"
|
||||
),
|
||||
@@ -527,6 +536,7 @@ class Answer(Resource):
|
||||
)
|
||||
conversation_id = data.get("conversation_id")
|
||||
prompt_id = data.get("prompt_id", "default")
|
||||
proxy_id = data.get("proxy_id", None)
|
||||
chunks = int(data.get("chunks", 2))
|
||||
token_limit = data.get("token_limit", settings.DEFAULT_MAX_HISTORY)
|
||||
retriever_name = data.get("retriever", "classic")
|
||||
@@ -535,6 +545,7 @@ class Answer(Resource):
|
||||
data_key = get_data_from_api_key(data["api_key"])
|
||||
chunks = int(data_key.get("chunks", 2))
|
||||
prompt_id = data_key.get("prompt_id", "default")
|
||||
proxy_id = data_key.get("proxy_id", None)
|
||||
source = {"active_docs": data_key.get("source")}
|
||||
retriever_name = data_key.get("retriever", retriever_name)
|
||||
user_api_key = data["api_key"]
|
||||
@@ -569,6 +580,7 @@ class Answer(Resource):
|
||||
api_key=settings.API_KEY,
|
||||
user_api_key=user_api_key,
|
||||
prompt=prompt,
|
||||
proxy_id=proxy_id,
|
||||
chat_history=history,
|
||||
decoded_token=decoded_token,
|
||||
)
|
||||
|
||||
@@ -27,6 +27,7 @@ db = mongo["docsgpt"]
|
||||
conversations_collection = db["conversations"]
|
||||
sources_collection = db["sources"]
|
||||
prompts_collection = db["prompts"]
|
||||
proxies_collection = db["proxies"]
|
||||
feedback_collection = db["feedback"]
|
||||
api_key_collection = db["api_keys"]
|
||||
token_usage_collection = db["token_usage"]
|
||||
@@ -919,6 +920,183 @@ class UpdatePrompt(Resource):
|
||||
|
||||
return make_response(jsonify({"success": True}), 200)
|
||||
|
||||
@user_ns.route("/api/get_proxies")
|
||||
class GetProxies(Resource):
|
||||
@api.doc(description="Get all proxies for the user")
|
||||
def get(self):
|
||||
decoded_token = request.decoded_token
|
||||
if not decoded_token:
|
||||
return make_response(jsonify({"success": False}), 401)
|
||||
user = decoded_token.get("sub")
|
||||
try:
|
||||
proxies = proxies_collection.find({"user": user})
|
||||
list_proxies = [
|
||||
{"id": "none", "name": "None", "type": "public"},
|
||||
]
|
||||
|
||||
for proxy in proxies:
|
||||
list_proxies.append(
|
||||
{
|
||||
"id": str(proxy["_id"]),
|
||||
"name": proxy["name"],
|
||||
"type": "private",
|
||||
}
|
||||
)
|
||||
except Exception as err:
|
||||
current_app.logger.error(f"Error retrieving proxies: {err}")
|
||||
return make_response(jsonify({"success": False}), 400)
|
||||
|
||||
return make_response(jsonify(list_proxies), 200)
|
||||
|
||||
|
||||
@user_ns.route("/api/get_single_proxy")
|
||||
class GetSingleProxy(Resource):
|
||||
@api.doc(params={"id": "ID of the proxy"}, description="Get a single proxy by ID")
|
||||
def get(self):
|
||||
decoded_token = request.decoded_token
|
||||
if not decoded_token:
|
||||
return make_response(jsonify({"success": False}), 401)
|
||||
user = decoded_token.get("sub")
|
||||
proxy_id = request.args.get("id")
|
||||
if not proxy_id:
|
||||
return make_response(
|
||||
jsonify({"success": False, "message": "ID is required"}), 400
|
||||
)
|
||||
|
||||
try:
|
||||
if proxy_id == "none":
|
||||
return make_response(jsonify({"connection": ""}), 200)
|
||||
|
||||
proxy = proxies_collection.find_one(
|
||||
{"_id": ObjectId(proxy_id), "user": user}
|
||||
)
|
||||
if not proxy:
|
||||
return make_response(jsonify({"status": "not found"}), 404)
|
||||
except Exception as err:
|
||||
current_app.logger.error(f"Error retrieving proxy: {err}")
|
||||
return make_response(jsonify({"success": False}), 400)
|
||||
|
||||
return make_response(jsonify({"connection": proxy["connection"]}), 200)
|
||||
|
||||
|
||||
@user_ns.route("/api/create_proxy")
|
||||
class CreateProxy(Resource):
|
||||
create_proxy_model = api.model(
|
||||
"CreateProxyModel",
|
||||
{
|
||||
"connection": fields.String(
|
||||
required=True, description="Connection string of the proxy"
|
||||
),
|
||||
"name": fields.String(required=True, description="Name of the proxy"),
|
||||
},
|
||||
)
|
||||
|
||||
@api.expect(create_proxy_model)
|
||||
@api.doc(description="Create a new proxy")
|
||||
def post(self):
|
||||
decoded_token = request.decoded_token
|
||||
if not decoded_token:
|
||||
return make_response(jsonify({"success": False}), 401)
|
||||
data = request.get_json()
|
||||
required_fields = ["connection", "name"]
|
||||
missing_fields = check_required_fields(data, required_fields)
|
||||
if missing_fields:
|
||||
return missing_fields
|
||||
|
||||
user = decoded_token.get("sub")
|
||||
try:
|
||||
resp = proxies_collection.insert_one(
|
||||
{
|
||||
"name": data["name"],
|
||||
"connection": data["connection"],
|
||||
"user": user,
|
||||
}
|
||||
)
|
||||
new_id = str(resp.inserted_id)
|
||||
except Exception as err:
|
||||
current_app.logger.error(f"Error creating proxy: {err}")
|
||||
return make_response(jsonify({"success": False}), 400)
|
||||
|
||||
return make_response(jsonify({"id": new_id}), 200)
|
||||
|
||||
|
||||
@user_ns.route("/api/delete_proxy")
|
||||
class DeleteProxy(Resource):
|
||||
delete_proxy_model = api.model(
|
||||
"DeleteProxyModel",
|
||||
{"id": fields.String(required=True, description="Proxy ID to delete")},
|
||||
)
|
||||
|
||||
@api.expect(delete_proxy_model)
|
||||
@api.doc(description="Delete a proxy by ID")
|
||||
def post(self):
|
||||
decoded_token = request.decoded_token
|
||||
if not decoded_token:
|
||||
return make_response(jsonify({"success": False}), 401)
|
||||
user = decoded_token.get("sub")
|
||||
data = request.get_json()
|
||||
required_fields = ["id"]
|
||||
missing_fields = check_required_fields(data, required_fields)
|
||||
if missing_fields:
|
||||
return missing_fields
|
||||
|
||||
try:
|
||||
# Don't allow deleting the 'none' proxy
|
||||
if data["id"] == "none":
|
||||
return make_response(jsonify({"success": False, "message": "Cannot delete default proxy"}), 400)
|
||||
|
||||
result = proxies_collection.delete_one({"_id": ObjectId(data["id"]), "user": user})
|
||||
if result.deleted_count == 0:
|
||||
return make_response(jsonify({"success": False, "message": "Proxy not found"}), 404)
|
||||
except Exception as err:
|
||||
current_app.logger.error(f"Error deleting proxy: {err}")
|
||||
return make_response(jsonify({"success": False}), 400)
|
||||
|
||||
return make_response(jsonify({"success": True}), 200)
|
||||
|
||||
|
||||
@user_ns.route("/api/update_proxy")
|
||||
class UpdateProxy(Resource):
|
||||
update_proxy_model = api.model(
|
||||
"UpdateProxyModel",
|
||||
{
|
||||
"id": fields.String(required=True, description="Proxy ID to update"),
|
||||
"name": fields.String(required=True, description="New name of the proxy"),
|
||||
"connection": fields.String(
|
||||
required=True, description="New connection string of the proxy"
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
@api.expect(update_proxy_model)
|
||||
@api.doc(description="Update an existing proxy")
|
||||
def post(self):
|
||||
decoded_token = request.decoded_token
|
||||
if not decoded_token:
|
||||
return make_response(jsonify({"success": False}), 401)
|
||||
user = decoded_token.get("sub")
|
||||
data = request.get_json()
|
||||
required_fields = ["id", "name", "connection"]
|
||||
missing_fields = check_required_fields(data, required_fields)
|
||||
if missing_fields:
|
||||
return missing_fields
|
||||
|
||||
try:
|
||||
# Don't allow updating the 'none' proxy
|
||||
if data["id"] == "none":
|
||||
return make_response(jsonify({"success": False, "message": "Cannot update default proxy"}), 400)
|
||||
|
||||
result = proxies_collection.update_one(
|
||||
{"_id": ObjectId(data["id"]), "user": user},
|
||||
{"$set": {"name": data["name"], "connection": data["connection"]}},
|
||||
)
|
||||
if result.modified_count == 0:
|
||||
return make_response(jsonify({"success": False, "message": "Proxy not found"}), 404)
|
||||
except Exception as err:
|
||||
current_app.logger.error(f"Error updating proxy: {err}")
|
||||
return make_response(jsonify({"success": False}), 400)
|
||||
|
||||
return make_response(jsonify({"success": True}), 200)
|
||||
|
||||
@user_ns.route("/api/get_api_keys")
|
||||
class GetApiKeys(Resource):
|
||||
|
||||
@@ -14,6 +14,7 @@ esutils==1.0.1
|
||||
Flask==3.1.0
|
||||
faiss-cpu==1.9.0.post1
|
||||
flask-restx==1.3.0
|
||||
gevent==24.11.1
|
||||
google-genai==1.3.0
|
||||
google-generativeai==0.8.3
|
||||
gTTS==2.5.4
|
||||
@@ -35,7 +36,7 @@ langchain-community==0.3.19
|
||||
langchain-core==0.3.45
|
||||
langchain-openai==0.3.8
|
||||
langchain-text-splitters==0.3.6
|
||||
langsmith==0.3.19
|
||||
langsmith==0.3.15
|
||||
lazy-object-proxy==1.10.0
|
||||
lxml==5.3.1
|
||||
markupsafe==3.0.2
|
||||
@@ -65,7 +66,7 @@ py==1.11.0
|
||||
pydantic==2.10.6
|
||||
pydantic-core==2.27.2
|
||||
pydantic-settings==2.7.1
|
||||
pymongo==4.11.3
|
||||
pymongo==4.10.1
|
||||
pypdf==5.2.0
|
||||
python-dateutil==2.9.0.post0
|
||||
python-dotenv==1.0.1
|
||||
|
||||
@@ -35,7 +35,7 @@ services:
|
||||
|
||||
worker:
|
||||
build: ../application
|
||||
command: celery -A application.app.celery worker -l INFO -B
|
||||
command: celery -A application.app.celery worker -l INFO --pool=gevent -B
|
||||
environment:
|
||||
- API_KEY=$API_KEY
|
||||
- EMBEDDINGS_KEY=$API_KEY
|
||||
|
||||
@@ -13,6 +13,11 @@ const endpoints = {
|
||||
DELETE_PROMPT: '/api/delete_prompt',
|
||||
UPDATE_PROMPT: '/api/update_prompt',
|
||||
SINGLE_PROMPT: (id: string) => `/api/get_single_prompt?id=${id}`,
|
||||
PROXIES: '/api/get_proxies',
|
||||
CREATE_PROXY: '/api/create_proxy',
|
||||
DELETE_PROXY: '/api/delete_proxy',
|
||||
UPDATE_PROXY: '/api/update_proxy',
|
||||
SINGLE_PROXY: (id: string) => `/api/get_single_proxy?id=${id}`,
|
||||
DELETE_PATH: (docPath: string) => `/api/delete_old?source_id=${docPath}`,
|
||||
TASK_STATUS: (task_id: string) => `/api/task_status?task_id=${task_id}`,
|
||||
MESSAGE_ANALYTICS: '/api/get_message_analytics',
|
||||
|
||||
@@ -27,6 +27,16 @@ const userService = {
|
||||
apiClient.post(endpoints.USER.UPDATE_PROMPT, data, token),
|
||||
getSinglePrompt: (id: string, token: string | null): Promise<any> =>
|
||||
apiClient.get(endpoints.USER.SINGLE_PROMPT(id), token),
|
||||
getProxies: (token: string | null): Promise<any> =>
|
||||
apiClient.get(endpoints.USER.PROXIES, token),
|
||||
createProxy: (data: any, token: string | null): Promise<any> =>
|
||||
apiClient.post(endpoints.USER.CREATE_PROXY, data, token),
|
||||
deleteProxy: (data: any, token: string | null): Promise<any> =>
|
||||
apiClient.post(endpoints.USER.DELETE_PROXY, data, token),
|
||||
updateProxy: (data: any, token: string | null): Promise<any> =>
|
||||
apiClient.post(endpoints.USER.UPDATE_PROXY, data, token),
|
||||
getSingleProxy: (id: string, token: string | null): Promise<any> =>
|
||||
apiClient.get(endpoints.USER.SINGLE_PROXY(id), token),
|
||||
deletePath: (docPath: string, token: string | null): Promise<any> =>
|
||||
apiClient.get(endpoints.USER.DELETE_PATH(docPath), token),
|
||||
getTaskStatus: (task_id: string, token: string | null): Promise<any> =>
|
||||
|
||||
@@ -11,6 +11,7 @@ export function handleFetchAnswer(
|
||||
history: Array<any> = [],
|
||||
conversationId: string | null,
|
||||
promptId: string | null,
|
||||
proxyId: string | null,
|
||||
chunks: string,
|
||||
token_limit: number,
|
||||
): Promise<
|
||||
@@ -44,6 +45,7 @@ export function handleFetchAnswer(
|
||||
history: JSON.stringify(history),
|
||||
conversation_id: conversationId,
|
||||
prompt_id: promptId,
|
||||
proxy_id: proxyId,
|
||||
chunks: chunks,
|
||||
token_limit: token_limit,
|
||||
isNoneDoc: selectedDocs === null,
|
||||
@@ -82,6 +84,7 @@ export function handleFetchAnswerSteaming(
|
||||
history: Array<any> = [],
|
||||
conversationId: string | null,
|
||||
promptId: string | null,
|
||||
proxyId: string | null,
|
||||
chunks: string,
|
||||
token_limit: number,
|
||||
onEvent: (event: MessageEvent) => void,
|
||||
@@ -99,6 +102,7 @@ export function handleFetchAnswerSteaming(
|
||||
history: JSON.stringify(history),
|
||||
conversation_id: conversationId,
|
||||
prompt_id: promptId,
|
||||
proxy_id: proxyId,
|
||||
chunks: chunks,
|
||||
token_limit: token_limit,
|
||||
isNoneDoc: selectedDocs === null,
|
||||
|
||||
@@ -43,6 +43,7 @@ export interface RetrievalPayload {
|
||||
history: string;
|
||||
conversation_id: string | null;
|
||||
prompt_id?: string | null;
|
||||
proxy_id?: string | null;
|
||||
chunks: string;
|
||||
token_limit: number;
|
||||
isNoneDoc: boolean;
|
||||
|
||||
@@ -47,6 +47,7 @@ export const fetchAnswer = createAsyncThunk<
|
||||
state.conversation.queries,
|
||||
state.conversation.conversationId,
|
||||
state.preference.prompt.id,
|
||||
state.preference.proxy?.id ?? null,
|
||||
state.preference.chunks,
|
||||
state.preference.token_limit,
|
||||
(event) => {
|
||||
@@ -120,6 +121,7 @@ export const fetchAnswer = createAsyncThunk<
|
||||
state.conversation.queries,
|
||||
state.conversation.conversationId,
|
||||
state.preference.prompt.id,
|
||||
state.preference.proxy?.id ?? null,
|
||||
state.preference.chunks,
|
||||
state.preference.token_limit,
|
||||
);
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"selectLanguage": "Select Language",
|
||||
"chunks": "Chunks processed per query",
|
||||
"prompt": "Active Prompt",
|
||||
"proxy": "Active Proxy",
|
||||
"deleteAllLabel": "Delete All Conversations",
|
||||
"deleteAllBtn": "Delete All",
|
||||
"addNew": "Add New",
|
||||
@@ -205,6 +206,14 @@
|
||||
"promptText": "Prompt Text",
|
||||
"save": "Save",
|
||||
"nameExists": "Name already exists"
|
||||
},
|
||||
"proxies": {
|
||||
"addProxy": "Add Proxy",
|
||||
"addDescription": "Add your custom proxy to query tools and save it to DocsGPT",
|
||||
"editProxy": "Edit Proxy",
|
||||
"proxyName": "Proxy Name",
|
||||
"proxyProtocol": "Proxy Protocol",
|
||||
"connectionString": "Connection String"
|
||||
}
|
||||
},
|
||||
"sharedConv": {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import Input from '../components/Input';
|
||||
import { ActiveState } from '../models/misc';
|
||||
@@ -19,7 +18,13 @@ export default function JWTModal({
|
||||
if (modalState !== 'ACTIVE') return null;
|
||||
|
||||
return (
|
||||
<WrapperModal className="p-4" isPerformingTask={true} close={() => {}}>
|
||||
<WrapperModal
|
||||
className="p-4"
|
||||
isPerformingTask={true}
|
||||
close={() => {
|
||||
/* Modal close handler */
|
||||
}}
|
||||
>
|
||||
<div className="mb-6">
|
||||
<span className="text-lg text-jet dark:text-bright-gray">
|
||||
Add JWT Token
|
||||
|
||||
279
frontend/src/preferences/ProxiesModal.tsx
Normal file
279
frontend/src/preferences/ProxiesModal.tsx
Normal file
@@ -0,0 +1,279 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Input from '../components/Input';
|
||||
import WrapperModal from '../modals/WrapperModal';
|
||||
import { ActiveState } from '../models/misc';
|
||||
|
||||
function AddProxy({
|
||||
setModalState,
|
||||
handleAddProxy,
|
||||
newProxyName,
|
||||
setNewProxyName,
|
||||
newProxyConnection,
|
||||
setNewProxyConnection,
|
||||
disableSave,
|
||||
}: {
|
||||
setModalState: (state: ActiveState) => void;
|
||||
handleAddProxy?: () => void;
|
||||
newProxyName: string;
|
||||
setNewProxyName: (name: string) => void;
|
||||
newProxyConnection: string;
|
||||
setNewProxyConnection: (content: string) => void;
|
||||
disableSave: boolean;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p className="mb-1 text-xl text-jet dark:text-bright-gray">
|
||||
{t('modals.proxies.addProxy')}
|
||||
</p>
|
||||
<p className="mb-7 text-xs text-[#747474] dark:text-[#7F7F82]">
|
||||
{t('modals.proxies.addDescription')}
|
||||
</p>
|
||||
<div>
|
||||
<Input
|
||||
placeholder={t('modals.proxies.proxyName')}
|
||||
type="text"
|
||||
className="mb-4"
|
||||
value={newProxyName}
|
||||
onChange={(e) => setNewProxyName(e.target.value)}
|
||||
labelBgClassName="bg-white dark:bg-[#26272E]"
|
||||
borderVariant="thin"
|
||||
/>
|
||||
<Input
|
||||
placeholder={t('modals.proxies.proxyProtocol')}
|
||||
type="text"
|
||||
className="mb-4 opacity-70 cursor-not-allowed"
|
||||
value="HTTP/S"
|
||||
onChange={() => {
|
||||
/* Protocol field is read-only */
|
||||
}}
|
||||
labelBgClassName="bg-white dark:bg-[#26272E]"
|
||||
borderVariant="thin"
|
||||
disabled={true}
|
||||
/>
|
||||
<Input
|
||||
placeholder={t('modals.proxies.connectionString')}
|
||||
type="text"
|
||||
className="mb-4"
|
||||
value={newProxyConnection}
|
||||
onChange={(e) => setNewProxyConnection(e.target.value)}
|
||||
labelBgClassName="bg-white dark:bg-[#26272E]"
|
||||
borderVariant="thin"
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-6 flex flex-row-reverse">
|
||||
<button
|
||||
onClick={handleAddProxy}
|
||||
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-violets-are-blue disabled:hover:bg-purple-30"
|
||||
disabled={disableSave}
|
||||
title={
|
||||
disableSave && newProxyName ? t('modals.prompts.nameExists') : ''
|
||||
}
|
||||
>
|
||||
{t('modals.prompts.save')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function EditProxy({
|
||||
setModalState,
|
||||
handleEditProxy,
|
||||
editProxyName,
|
||||
setEditProxyName,
|
||||
editProxyConnection,
|
||||
setEditProxyConnection,
|
||||
currentProxyEdit,
|
||||
disableSave,
|
||||
}: {
|
||||
setModalState: (state: ActiveState) => void;
|
||||
handleEditProxy?: (id: string, type: string) => void;
|
||||
editProxyName: string;
|
||||
setEditProxyName: (name: string) => void;
|
||||
editProxyConnection: string;
|
||||
setEditProxyConnection: (content: string) => void;
|
||||
currentProxyEdit: { name: string; id: string; type: string };
|
||||
disableSave: boolean;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="">
|
||||
<p className="mb-1 text-xl text-jet dark:text-bright-gray">
|
||||
{t('modals.proxies.editProxy')}
|
||||
</p>
|
||||
<p className="mb-7 text-xs text-[#747474] dark:text-[#7F7F82]">
|
||||
{t('modals.proxies.addDescription')}
|
||||
</p>
|
||||
<div>
|
||||
<Input
|
||||
placeholder={t('modals.proxies.proxyName')}
|
||||
type="text"
|
||||
className="mb-4"
|
||||
value={editProxyName}
|
||||
onChange={(e) => setEditProxyName(e.target.value)}
|
||||
labelBgClassName="bg-white dark:bg-charleston-green-2"
|
||||
borderVariant="thin"
|
||||
/>
|
||||
<Input
|
||||
placeholder={t('modals.proxies.proxyProtocol')}
|
||||
type="text"
|
||||
className="mb-4 opacity-70 cursor-not-allowed"
|
||||
value="HTTP/S"
|
||||
onChange={() => {
|
||||
/* Protocol field is read-only */
|
||||
}}
|
||||
labelBgClassName="bg-white dark:bg-charleston-green-2"
|
||||
borderVariant="thin"
|
||||
disabled={true}
|
||||
/>
|
||||
<Input
|
||||
placeholder={t('modals.proxies.connectionString')}
|
||||
type="text"
|
||||
className="mb-4"
|
||||
value={editProxyConnection}
|
||||
onChange={(e) => setEditProxyConnection(e.target.value)}
|
||||
labelBgClassName="bg-white dark:bg-charleston-green-2"
|
||||
borderVariant="thin"
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-6 flex flex-row-reverse gap-4">
|
||||
<button
|
||||
className={`rounded-3xl bg-purple-30 disabled:hover:bg-purple-30 hover:bg-violets-are-blue px-5 py-2 text-sm text-white transition-all ${
|
||||
currentProxyEdit.type === 'public'
|
||||
? 'cursor-not-allowed opacity-50'
|
||||
: ''
|
||||
}`}
|
||||
onClick={() => {
|
||||
handleEditProxy &&
|
||||
handleEditProxy(currentProxyEdit.id, currentProxyEdit.type);
|
||||
}}
|
||||
disabled={currentProxyEdit.type === 'public' || disableSave}
|
||||
title={
|
||||
disableSave && editProxyName ? t('modals.prompts.nameExists') : ''
|
||||
}
|
||||
>
|
||||
{t('modals.prompts.save')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ProxiesModal({
|
||||
existingProxies,
|
||||
modalState,
|
||||
setModalState,
|
||||
type,
|
||||
newProxyName,
|
||||
setNewProxyName,
|
||||
newProxyConnection,
|
||||
setNewProxyConnection,
|
||||
editProxyName,
|
||||
setEditProxyName,
|
||||
editProxyConnection,
|
||||
setEditProxyConnection,
|
||||
currentProxyEdit,
|
||||
handleAddProxy,
|
||||
handleEditProxy,
|
||||
}: {
|
||||
existingProxies: { name: string; id: string; type: string }[];
|
||||
modalState: ActiveState;
|
||||
setModalState: (state: ActiveState) => void;
|
||||
type: 'ADD' | 'EDIT';
|
||||
newProxyName: string;
|
||||
setNewProxyName: (name: string) => void;
|
||||
newProxyConnection: string;
|
||||
setNewProxyConnection: (content: string) => void;
|
||||
editProxyName: string;
|
||||
setEditProxyName: (name: string) => void;
|
||||
editProxyConnection: string;
|
||||
setEditProxyConnection: (content: string) => void;
|
||||
currentProxyEdit: { id: string; name: string; type: string };
|
||||
handleAddProxy?: () => void;
|
||||
handleEditProxy?: (id: string, type: string) => void;
|
||||
}) {
|
||||
const [disableSave, setDisableSave] = React.useState(true);
|
||||
const { t } = useTranslation();
|
||||
|
||||
React.useEffect(() => {
|
||||
// Check if fields are filled to enable/disable save button
|
||||
if (type === 'ADD') {
|
||||
const nameExists = existingProxies.some(
|
||||
(proxy) => proxy.name.toLowerCase() === newProxyName.toLowerCase(),
|
||||
);
|
||||
setDisableSave(
|
||||
newProxyName === '' || newProxyConnection === '' || nameExists,
|
||||
);
|
||||
} else {
|
||||
const nameExists = existingProxies.some(
|
||||
(proxy) =>
|
||||
proxy.name.toLowerCase() === editProxyName.toLowerCase() &&
|
||||
proxy.id !== currentProxyEdit.id,
|
||||
);
|
||||
setDisableSave(
|
||||
editProxyName === '' || editProxyConnection === '' || nameExists,
|
||||
);
|
||||
}
|
||||
}, [
|
||||
newProxyName,
|
||||
newProxyConnection,
|
||||
editProxyName,
|
||||
editProxyConnection,
|
||||
type,
|
||||
existingProxies,
|
||||
currentProxyEdit,
|
||||
]);
|
||||
|
||||
let view;
|
||||
|
||||
if (type === 'ADD') {
|
||||
view = (
|
||||
<AddProxy
|
||||
setModalState={setModalState}
|
||||
handleAddProxy={handleAddProxy}
|
||||
newProxyName={newProxyName}
|
||||
setNewProxyName={setNewProxyName}
|
||||
newProxyConnection={newProxyConnection}
|
||||
setNewProxyConnection={setNewProxyConnection}
|
||||
disableSave={disableSave}
|
||||
/>
|
||||
);
|
||||
} else if (type === 'EDIT') {
|
||||
view = (
|
||||
<EditProxy
|
||||
setModalState={setModalState}
|
||||
handleEditProxy={handleEditProxy}
|
||||
editProxyName={editProxyName}
|
||||
setEditProxyName={setEditProxyName}
|
||||
editProxyConnection={editProxyConnection}
|
||||
setEditProxyConnection={setEditProxyConnection}
|
||||
currentProxyEdit={currentProxyEdit}
|
||||
disableSave={disableSave}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
view = <></>;
|
||||
}
|
||||
|
||||
return modalState === 'ACTIVE' ? (
|
||||
<WrapperModal
|
||||
close={() => {
|
||||
setModalState('INACTIVE');
|
||||
if (type === 'ADD') {
|
||||
setNewProxyName('');
|
||||
setNewProxyConnection('');
|
||||
}
|
||||
}}
|
||||
className="sm:w-[512px] mt-24"
|
||||
>
|
||||
{view}
|
||||
</WrapperModal>
|
||||
) : null;
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import { ActiveState, Doc } from '../models/misc';
|
||||
export interface Preference {
|
||||
apiKey: string;
|
||||
prompt: { name: string; id: string; type: string };
|
||||
proxy: { name: string; id: string; type: string } | null;
|
||||
chunks: string;
|
||||
token_limit: number;
|
||||
selectedDocs: Doc | null;
|
||||
@@ -27,6 +28,7 @@ export interface Preference {
|
||||
const initialState: Preference = {
|
||||
apiKey: 'xxx',
|
||||
prompt: { name: 'default', id: 'default', type: 'public' },
|
||||
proxy: null,
|
||||
chunks: '2',
|
||||
token_limit: 2000,
|
||||
selectedDocs: {
|
||||
@@ -73,6 +75,9 @@ export const prefSlice = createSlice({
|
||||
setPrompt: (state, action) => {
|
||||
state.prompt = action.payload;
|
||||
},
|
||||
setProxy: (state, action) => {
|
||||
state.proxy = action.payload;
|
||||
},
|
||||
setChunks: (state, action) => {
|
||||
state.chunks = action.payload;
|
||||
},
|
||||
@@ -92,6 +97,7 @@ export const {
|
||||
setConversations,
|
||||
setToken,
|
||||
setPrompt,
|
||||
setProxy,
|
||||
setChunks,
|
||||
setTokenLimit,
|
||||
setModalStateDeleteConv,
|
||||
@@ -126,6 +132,16 @@ prefListenerMiddleware.startListening({
|
||||
},
|
||||
});
|
||||
|
||||
prefListenerMiddleware.startListening({
|
||||
matcher: isAnyOf(setProxy),
|
||||
effect: (action, listenerApi) => {
|
||||
localStorage.setItem(
|
||||
'DocsGPTProxy',
|
||||
JSON.stringify((listenerApi.getState() as RootState).preference.proxy),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
prefListenerMiddleware.startListening({
|
||||
matcher: isAnyOf(setChunks),
|
||||
effect: (action, listenerApi) => {
|
||||
@@ -165,6 +181,7 @@ export const selectConversationId = (state: RootState) =>
|
||||
state.conversation.conversationId;
|
||||
export const selectToken = (state: RootState) => state.preference.token;
|
||||
export const selectPrompt = (state: RootState) => state.preference.prompt;
|
||||
export const selectProxy = (state: RootState) => state.preference.proxy;
|
||||
export const selectChunks = (state: RootState) => state.preference.chunks;
|
||||
export const selectTokenLimit = (state: RootState) =>
|
||||
state.preference.token_limit;
|
||||
|
||||
@@ -8,14 +8,17 @@ import { useDarkTheme } from '../hooks';
|
||||
import {
|
||||
selectChunks,
|
||||
selectPrompt,
|
||||
selectProxy,
|
||||
selectToken,
|
||||
selectTokenLimit,
|
||||
setChunks,
|
||||
setModalStateDeleteConv,
|
||||
setPrompt,
|
||||
setProxy,
|
||||
setTokenLimit,
|
||||
} from '../preferences/preferenceSlice';
|
||||
import Prompts from './Prompts';
|
||||
import Proxies from './Proxies';
|
||||
|
||||
export default function General() {
|
||||
const {
|
||||
@@ -48,6 +51,9 @@ export default function General() {
|
||||
const [prompts, setPrompts] = React.useState<
|
||||
{ name: string; id: string; type: string }[]
|
||||
>([]);
|
||||
const [proxies, setProxies] = React.useState<
|
||||
{ name: string; id: string; type: string }[]
|
||||
>([]);
|
||||
const selectedChunks = useSelector(selectChunks);
|
||||
const selectedTokenLimit = useSelector(selectTokenLimit);
|
||||
const [isDarkTheme, toggleTheme] = useDarkTheme();
|
||||
@@ -62,6 +68,44 @@ export default function General() {
|
||||
: languageOptions[0],
|
||||
);
|
||||
const selectedPrompt = useSelector(selectPrompt);
|
||||
const selectedProxy = useSelector(selectProxy);
|
||||
|
||||
React.useEffect(() => {
|
||||
// Set default proxy state first (only if no stored preference exists)
|
||||
const storedProxy = localStorage.getItem('DocsGPTProxy');
|
||||
if (!storedProxy) {
|
||||
const noneProxy = { name: 'None', id: 'none', type: 'public' };
|
||||
dispatch(setProxy(noneProxy));
|
||||
} else {
|
||||
try {
|
||||
const parsedProxy = JSON.parse(storedProxy);
|
||||
dispatch(setProxy(parsedProxy));
|
||||
} catch (e) {
|
||||
console.error('Error parsing stored proxy', e);
|
||||
// Fallback to None if parsing fails
|
||||
dispatch(setProxy({ name: 'None', id: 'none', type: 'public' }));
|
||||
}
|
||||
}
|
||||
// Fetch available proxies
|
||||
const handleFetchProxies = async () => {
|
||||
try {
|
||||
const response = await userService.getProxies(token);
|
||||
if (!response.ok) {
|
||||
console.warn('Proxies API not implemented yet or failed to fetch');
|
||||
return;
|
||||
}
|
||||
const proxiesData = await response.json();
|
||||
if (proxiesData && Array.isArray(proxiesData)) {
|
||||
// Filter out 'none' as we add it separately in the component
|
||||
const filteredProxies = proxiesData.filter((p) => p.id !== 'none');
|
||||
setProxies(filteredProxies);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching proxies:', error);
|
||||
}
|
||||
};
|
||||
handleFetchProxies();
|
||||
}, [token, dispatch]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleFetchPrompts = async () => {
|
||||
@@ -77,12 +121,13 @@ export default function General() {
|
||||
}
|
||||
};
|
||||
handleFetchPrompts();
|
||||
}, []);
|
||||
}, [token]);
|
||||
|
||||
React.useEffect(() => {
|
||||
localStorage.setItem('docsgpt-locale', selectedLanguage?.value as string);
|
||||
changeLanguage(selectedLanguage?.value);
|
||||
}, [selectedLanguage, changeLanguage]);
|
||||
|
||||
return (
|
||||
<div className="mt-12 flex flex-col gap-4">
|
||||
{' '}
|
||||
@@ -171,6 +216,16 @@ export default function General() {
|
||||
setPrompts={setPrompts}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<Proxies
|
||||
proxies={proxies}
|
||||
selectedProxy={selectedProxy}
|
||||
onSelectProxy={(name, id, type) =>
|
||||
dispatch(setProxy({ name: name, id: id, type: type }))
|
||||
}
|
||||
setProxies={setProxies}
|
||||
/>
|
||||
</div>
|
||||
<hr className="border-t w-[calc(min(665px,100%))] my-4 border-silver dark:border-silver/40" />
|
||||
<div className="flex flex-col gap-2">
|
||||
<button
|
||||
|
||||
316
frontend/src/settings/Proxies.tsx
Normal file
316
frontend/src/settings/Proxies.tsx
Normal file
@@ -0,0 +1,316 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import userService from '../api/services/userService';
|
||||
import Dropdown from '../components/Dropdown';
|
||||
import { ActiveState } from '../models/misc';
|
||||
import ProxiesModal from '../preferences/ProxiesModal';
|
||||
import { selectToken } from '../preferences/preferenceSlice';
|
||||
|
||||
export interface ProxyProps {
|
||||
proxies: { name: string; id: string; type: string }[];
|
||||
selectedProxy: {
|
||||
name: string;
|
||||
id: string;
|
||||
type: string;
|
||||
} | null;
|
||||
onSelectProxy: (name: string, id: string, type: string) => void;
|
||||
setProxies: React.Dispatch<
|
||||
React.SetStateAction<{ name: string; id: string; type: string }[]>
|
||||
>;
|
||||
}
|
||||
|
||||
export default function Proxies({
|
||||
proxies,
|
||||
selectedProxy,
|
||||
onSelectProxy,
|
||||
setProxies,
|
||||
}: ProxyProps) {
|
||||
const handleSelectProxy = ({
|
||||
name,
|
||||
id,
|
||||
type,
|
||||
}: {
|
||||
name: string;
|
||||
id: string;
|
||||
type: string;
|
||||
}) => {
|
||||
setEditProxyName(name);
|
||||
onSelectProxy(name, id, type);
|
||||
};
|
||||
const token = useSelector(selectToken);
|
||||
const [newProxyName, setNewProxyName] = React.useState('');
|
||||
const [newProxyConnection, setNewProxyConnection] = React.useState('');
|
||||
const [editProxyName, setEditProxyName] = React.useState('');
|
||||
const [editProxyConnection, setEditProxyConnection] = React.useState('');
|
||||
const [currentProxyEdit, setCurrentProxyEdit] = React.useState({
|
||||
id: '',
|
||||
name: '',
|
||||
type: '',
|
||||
});
|
||||
const [modalType, setModalType] = React.useState<'ADD' | 'EDIT'>('ADD');
|
||||
const [modalState, setModalState] = React.useState<ActiveState>('INACTIVE');
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleAddProxy = async () => {
|
||||
try {
|
||||
const response = await userService.createProxy(
|
||||
{
|
||||
name: newProxyName,
|
||||
connection: newProxyConnection,
|
||||
},
|
||||
token,
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to add proxy');
|
||||
}
|
||||
const newProxy = await response.json();
|
||||
const newProxyObject = {
|
||||
name: newProxyName,
|
||||
id: newProxy.id,
|
||||
type: 'private',
|
||||
};
|
||||
console.log(
|
||||
'Before selecting new proxy:',
|
||||
newProxyName,
|
||||
newProxy.id,
|
||||
'private',
|
||||
);
|
||||
if (setProxies) {
|
||||
const updatedProxies = [...proxies, newProxyObject];
|
||||
setProxies(updatedProxies);
|
||||
console.log('Updated proxies list:', updatedProxies);
|
||||
}
|
||||
setModalState('INACTIVE');
|
||||
onSelectProxy(newProxyName, newProxy.id, 'private');
|
||||
setNewProxyName('');
|
||||
setNewProxyConnection('');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
// Fallback to just adding to the local state if API doesn't exist yet
|
||||
const newId = `proxy_${Date.now()}`;
|
||||
if (setProxies) {
|
||||
// Store connection string in localStorage for local fallback
|
||||
localStorage.setItem(`proxy_connection_${newId}`, newProxyConnection);
|
||||
setProxies([
|
||||
...proxies,
|
||||
{ name: newProxyName, id: newId, type: 'private' },
|
||||
]);
|
||||
}
|
||||
setModalState('INACTIVE');
|
||||
onSelectProxy(newProxyName, newId, 'private');
|
||||
setNewProxyName('');
|
||||
setNewProxyConnection('');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteProxy = (id: string) => {
|
||||
// We don't delete the "none" proxy
|
||||
if (id === 'none') return;
|
||||
userService
|
||||
.deleteProxy({ id }, token)
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
// Remove from local state after successful deletion
|
||||
setProxies(proxies.filter((proxy) => proxy.id !== id));
|
||||
// Also remove any locally stored connection string
|
||||
localStorage.removeItem(`proxy_connection_${id}`);
|
||||
// If we deleted the currently selected proxy, switch to "None"
|
||||
if (selectedProxy && selectedProxy.id === id) {
|
||||
onSelectProxy('None', 'none', 'public');
|
||||
}
|
||||
} else {
|
||||
console.warn('Failed to delete proxy');
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
};
|
||||
|
||||
const handleFetchProxyConnection = async (id: string) => {
|
||||
try {
|
||||
// We don't need to fetch connection for the "none" proxy
|
||||
if (id === 'none') {
|
||||
setEditProxyConnection('');
|
||||
return;
|
||||
}
|
||||
// Check if this is a locally stored proxy (for API fallback)
|
||||
const localConnection = localStorage.getItem(`proxy_connection_${id}`);
|
||||
if (localConnection) {
|
||||
setEditProxyConnection(localConnection);
|
||||
return;
|
||||
}
|
||||
// Otherwise proceed with API call
|
||||
const response = await userService.getSingleProxy(id, token);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch proxy connection');
|
||||
}
|
||||
const proxyData = await response.json();
|
||||
setEditProxyConnection(proxyData.connection);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
// Set empty string instead of a placeholder
|
||||
setEditProxyConnection('');
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveChanges = (id: string, type: string) => {
|
||||
userService
|
||||
.updateProxy(
|
||||
{
|
||||
id: id,
|
||||
name: editProxyName,
|
||||
connection: editProxyConnection,
|
||||
},
|
||||
token,
|
||||
)
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
// If API doesn't exist yet, just handle locally
|
||||
console.warn('API not implemented yet');
|
||||
// Store connection string in localStorage
|
||||
localStorage.setItem(`proxy_connection_${id}`, editProxyConnection);
|
||||
}
|
||||
if (setProxies) {
|
||||
const existingProxyIndex = proxies.findIndex(
|
||||
(proxy) => proxy.id === id,
|
||||
);
|
||||
if (existingProxyIndex === -1) {
|
||||
setProxies([
|
||||
...proxies,
|
||||
{ name: editProxyName, id: id, type: type },
|
||||
]);
|
||||
} else {
|
||||
const updatedProxies = [...proxies];
|
||||
updatedProxies[existingProxyIndex] = {
|
||||
name: editProxyName,
|
||||
id: id,
|
||||
type: type,
|
||||
};
|
||||
setProxies(updatedProxies);
|
||||
}
|
||||
}
|
||||
setModalState('INACTIVE');
|
||||
onSelectProxy(editProxyName, id, type);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
// Handle locally if API fails
|
||||
// Store connection string in localStorage
|
||||
localStorage.setItem(`proxy_connection_${id}`, editProxyConnection);
|
||||
if (setProxies) {
|
||||
const existingProxyIndex = proxies.findIndex(
|
||||
(proxy) => proxy.id === id,
|
||||
);
|
||||
if (existingProxyIndex !== -1) {
|
||||
const updatedProxies = [...proxies];
|
||||
updatedProxies[existingProxyIndex] = {
|
||||
name: editProxyName,
|
||||
id: id,
|
||||
type: type,
|
||||
};
|
||||
setProxies(updatedProxies);
|
||||
}
|
||||
}
|
||||
setModalState('INACTIVE');
|
||||
onSelectProxy(editProxyName, id, type);
|
||||
});
|
||||
};
|
||||
|
||||
// Split proxies into 'None' and custom proxies
|
||||
const customProxies = proxies.filter(
|
||||
(p) => p.id !== 'none' && p.name !== 'None',
|
||||
);
|
||||
|
||||
// Create options array with None first
|
||||
const noneProxy = { name: 'None', id: 'none', type: 'public' };
|
||||
const allProxies = [noneProxy, ...customProxies];
|
||||
|
||||
// Ensure valid selectedProxy or default to None
|
||||
const finalSelectedProxy =
|
||||
selectedProxy && selectedProxy.id !== 'from-url'
|
||||
? selectedProxy
|
||||
: noneProxy;
|
||||
|
||||
// Check if the current proxy is the None proxy
|
||||
const isNoneSelected =
|
||||
!finalSelectedProxy ||
|
||||
finalSelectedProxy.id === 'none' ||
|
||||
finalSelectedProxy.name === 'None';
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<p className="font-medium dark:text-bright-gray">
|
||||
{t('settings.general.proxy')}
|
||||
</p>
|
||||
<div className="flex flex-row justify-start items-baseline gap-6">
|
||||
<Dropdown
|
||||
options={allProxies}
|
||||
selectedValue={finalSelectedProxy.name}
|
||||
placeholder="None"
|
||||
onSelect={handleSelectProxy}
|
||||
size="w-56"
|
||||
rounded="3xl"
|
||||
border="border"
|
||||
showEdit={!isNoneSelected}
|
||||
showDelete={!isNoneSelected}
|
||||
onEdit={({
|
||||
id,
|
||||
name,
|
||||
type,
|
||||
}: {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
}) => {
|
||||
setModalType('EDIT');
|
||||
setEditProxyName(name);
|
||||
handleFetchProxyConnection(id);
|
||||
setCurrentProxyEdit({ id: id, name: name, type: type });
|
||||
setModalState('ACTIVE');
|
||||
}}
|
||||
onDelete={(id: string) => {
|
||||
handleDeleteProxy(id);
|
||||
}}
|
||||
/>
|
||||
|
||||
<button
|
||||
className="rounded-3xl w-20 h-10 text-sm border border-solid border-violets-are-blue text-violets-are-blue transition-colors hover:text-white hover:bg-violets-are-blue"
|
||||
onClick={() => {
|
||||
setModalType('ADD');
|
||||
setNewProxyName('');
|
||||
setNewProxyConnection('');
|
||||
setModalState('ACTIVE');
|
||||
}}
|
||||
>
|
||||
{t('settings.general.add')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{modalState === 'ACTIVE' && (
|
||||
<ProxiesModal
|
||||
existingProxies={proxies}
|
||||
type={modalType}
|
||||
modalState={modalState}
|
||||
setModalState={setModalState}
|
||||
newProxyName={newProxyName}
|
||||
setNewProxyName={setNewProxyName}
|
||||
newProxyConnection={newProxyConnection}
|
||||
setNewProxyConnection={setNewProxyConnection}
|
||||
editProxyName={editProxyName}
|
||||
setEditProxyName={setEditProxyName}
|
||||
editProxyConnection={editProxyConnection}
|
||||
setEditProxyConnection={setEditProxyConnection}
|
||||
currentProxyEdit={currentProxyEdit}
|
||||
handleAddProxy={handleAddProxy}
|
||||
handleEditProxy={handleSaveChanges}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user