mirror of
https://github.com/Pythagora-io/gpt-pilot.git
synced 2026-01-10 13:37:55 -05:00
Many fixes and improvements
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
from urllib.parse import urljoin
|
||||
|
||||
import httpx
|
||||
|
||||
from core.agents.base import BaseAgent
|
||||
from core.agents.convo import AgentConvo
|
||||
from core.agents.git import GitMixin
|
||||
from core.agents.mixins import FileDiffMixin
|
||||
from core.agents.response import AgentResponse
|
||||
from core.config import FRONTEND_AGENT_NAME
|
||||
from core.config import FRONTEND_AGENT_NAME, SWAGGER_EMBEDDINGS_API
|
||||
from core.llm.parser import DescriptiveCodeBlockParser
|
||||
from core.log import get_logger
|
||||
from core.telemetry import telemetry
|
||||
@@ -25,6 +29,8 @@ class Frontend(FileDiffMixin, GitMixin, BaseAgent):
|
||||
async def run(self) -> AgentResponse:
|
||||
if not self.current_state.epics[0]["messages"]:
|
||||
finished = await self.start_frontend()
|
||||
elif len(self.next_state.epics[-1].get("file_paths_to_remove_mock")) > 0:
|
||||
finished = await self.remove_mock()
|
||||
elif not self.next_state.epics[-1].get("fe_iteration_done"):
|
||||
finished = await self.continue_frontend()
|
||||
else:
|
||||
@@ -50,6 +56,7 @@ class Frontend(FileDiffMixin, GitMixin, BaseAgent):
|
||||
if self.state_manager.template is not None
|
||||
else self.next_state.epics[0]["description"],
|
||||
user_feedback=None,
|
||||
first_time_build = True
|
||||
)
|
||||
response = await llm(convo, parser=DescriptiveCodeBlockParser())
|
||||
response_blocks = response.blocks
|
||||
@@ -140,11 +147,36 @@ class Frontend(FileDiffMixin, GitMixin, BaseAgent):
|
||||
await self.send_message("Implementing the changes you suggested...")
|
||||
|
||||
llm = self.get_llm(FRONTEND_AGENT_NAME, stream_output=True)
|
||||
|
||||
convo = AgentConvo(self).template(
|
||||
"is_relevant_for_docs_search",
|
||||
user_feedback=answer.text,
|
||||
)
|
||||
|
||||
response = await llm(convo)
|
||||
|
||||
relevant_api_documentation = None
|
||||
|
||||
if response == "yes":
|
||||
try:
|
||||
url = urljoin(SWAGGER_EMBEDDINGS_API, "search")
|
||||
async with httpx.AsyncClient(transport=httpx.AsyncHTTPTransport(retries=3)) as client:
|
||||
resp = await client.post(
|
||||
url,
|
||||
json={"text": answer.text, "project_id": str(self.state_manager.project.id), "user_id": "1"},
|
||||
)
|
||||
relevant_api_documentation = "\n".join(item["content"] for item in resp.json())
|
||||
except Exception as e:
|
||||
log.warning(f"Failed to fetch from RAG service: {e}", exc_info=True)
|
||||
|
||||
convo = AgentConvo(self).template(
|
||||
"build_frontend",
|
||||
description=self.current_state.epics[0]["description"],
|
||||
user_feedback=answer.text,
|
||||
relevant_api_documentation=relevant_api_documentation,
|
||||
first_time_build=False,
|
||||
)
|
||||
|
||||
response = await llm(convo, parser=DescriptiveCodeBlockParser())
|
||||
|
||||
await self.process_response(response.blocks)
|
||||
@@ -187,7 +219,7 @@ class Frontend(FileDiffMixin, GitMixin, BaseAgent):
|
||||
|
||||
return AgentResponse.done(self)
|
||||
|
||||
async def process_response(self, response_blocks: list) -> AgentResponse:
|
||||
async def process_response(self, response_blocks: list, removed_mock: bool = False) -> list[str]:
|
||||
"""
|
||||
Processes the response blocks from the LLM.
|
||||
|
||||
@@ -216,6 +248,12 @@ class Frontend(FileDiffMixin, GitMixin, BaseAgent):
|
||||
await self.ui.generate_diff(
|
||||
file_path, old_content, new_content, n_new_lines, n_del_lines, source=self.ui_source
|
||||
)
|
||||
if not removed_mock:
|
||||
if "client/src/api" in file_path:
|
||||
if not self.next_state.epics[-1].get("file_paths_to_remove_mock"):
|
||||
self.next_state.epics[-1]["file_paths_to_remove_mock"] = []
|
||||
self.next_state.epics[-1]["file_paths_to_remove_mock"].append(file_path)
|
||||
|
||||
await self.state_manager.save_file(file_path, new_content)
|
||||
|
||||
elif "command:" in last_line:
|
||||
@@ -234,6 +272,47 @@ class Frontend(FileDiffMixin, GitMixin, BaseAgent):
|
||||
|
||||
return AgentResponse.done(self)
|
||||
|
||||
async def remove_mock(self) -> bool:
|
||||
"""
|
||||
Remove mock API from the backend and replace it with api endpoints defined in the external documentation
|
||||
"""
|
||||
new_file_paths = self.current_state.epics[-1]["file_paths_to_remove_mock"]
|
||||
llm = self.get_llm(FRONTEND_AGENT_NAME)
|
||||
|
||||
for file_path in new_file_paths:
|
||||
old_content = self.current_state.get_file_content_by_path(file_path)
|
||||
|
||||
convo = AgentConvo(self).template("create_rag_query", file_content=old_content)
|
||||
topics = await llm(convo)
|
||||
|
||||
if topics != "None":
|
||||
try:
|
||||
url = urljoin(SWAGGER_EMBEDDINGS_API, "search")
|
||||
async with httpx.AsyncClient(transport=httpx.AsyncHTTPTransport(retries=3)) as client:
|
||||
resp = await client.post(
|
||||
url, json={"text": topics, "project_id": str(self.state_manager.project.id), "user_id": "1"}
|
||||
)
|
||||
resp_json = resp.json()
|
||||
relevant_api_documentation = "\n".join(item["content"] for item in resp_json)
|
||||
|
||||
convo = AgentConvo(self).template(
|
||||
"remove_mock",
|
||||
relevant_api_documentation=relevant_api_documentation,
|
||||
file_content=old_content,
|
||||
file_path=file_path,
|
||||
lines=len(old_content.splitlines()),
|
||||
)
|
||||
|
||||
response = await llm(convo, parser=DescriptiveCodeBlockParser())
|
||||
response_blocks = response.blocks
|
||||
convo.assistant(response.original_response)
|
||||
await self.process_response(response_blocks, removed_mock=True)
|
||||
self.next_state.epics[-1]["file_paths_to_remove_mock"].remove(file_path)
|
||||
except Exception as e:
|
||||
log.warning(f"Failed to fetch from RAG service: {e}", exc_info=True)
|
||||
|
||||
return False
|
||||
|
||||
async def set_app_details(self):
|
||||
"""
|
||||
Sets the app details.
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import json
|
||||
import secrets
|
||||
from json import JSONDecodeError
|
||||
from urllib.parse import urljoin
|
||||
from uuid import uuid4
|
||||
|
||||
import httpx
|
||||
import yaml
|
||||
|
||||
from core.agents.base import BaseAgent
|
||||
from core.agents.response import AgentResponse
|
||||
from core.config import SWAGGER_EMBEDDINGS_API
|
||||
from core.log import get_logger
|
||||
from core.templates.registry import PROJECT_TEMPLATES
|
||||
from core.ui.base import ProjectStage
|
||||
@@ -18,31 +21,29 @@ class Wizard(BaseAgent):
|
||||
agent_type = "wizard"
|
||||
display_name = "Wizard"
|
||||
|
||||
# class LoginType(Enum):
|
||||
# API_KEY = 0
|
||||
# HTTP_AUTH = 1
|
||||
# OAUTH2 = 2
|
||||
# OPENID = 3
|
||||
|
||||
def get_auth_methods(self, docs: str) -> dict[str, any]:
|
||||
def load_docs(self, docs: str) -> dict[str, any]:
|
||||
try:
|
||||
content = json.loads(docs)
|
||||
return json.loads(docs)
|
||||
except JSONDecodeError:
|
||||
try:
|
||||
content = yaml.safe_load(docs)
|
||||
return yaml.safe_load(docs)
|
||||
except Exception as e:
|
||||
log.error(f"An error occurred: {str(e)}")
|
||||
return {}
|
||||
|
||||
def get_auth_methods(self, docs: dict[str, any]) -> dict[str, any]:
|
||||
auth_methods = {}
|
||||
if "components" in content and "securitySchemes" in content["components"]:
|
||||
auth_methods["types"] = [details["type"] for details in content["components"]["securitySchemes"].values()]
|
||||
if "components" in docs and "securitySchemes" in docs["components"]:
|
||||
auth_methods["types"] = [details["type"] for details in docs["components"]["securitySchemes"].values()]
|
||||
auth_methods["api_version"] = 3
|
||||
auth_methods["external_api_url"] = docs.get("servers", [{}])[0].get("url", "")
|
||||
|
||||
elif "securityDefinitions" in content:
|
||||
auth_methods["types"] = [details["type"] for details in content["securityDefinitions"].values()]
|
||||
elif "securityDefinitions" in docs:
|
||||
auth_methods["types"] = [details["type"] for details in docs["securityDefinitions"].values()]
|
||||
auth_methods["api_version"] = 2
|
||||
|
||||
auth_methods["external_api_url"] = (
|
||||
"https://" + docs.get("host", "api.example.com") + docs.get("basePath", "")
|
||||
)
|
||||
return auth_methods
|
||||
|
||||
def create_custom_buttons(self, auth_methods):
|
||||
@@ -85,10 +86,32 @@ class Wizard(BaseAgent):
|
||||
allow_empty=False,
|
||||
verbose=True,
|
||||
)
|
||||
auth_methods = self.get_auth_methods(docs.text.strip())
|
||||
self.next_state.knowledge_base["docs"] = json.loads(docs.text.strip())
|
||||
content = self.load_docs(docs.text.strip())
|
||||
auth_methods = self.get_auth_methods(content)
|
||||
|
||||
self.next_state.knowledge_base["docs"] = content
|
||||
|
||||
self.next_state.knowledge_base["docs"]["api_version"] = auth_methods["api_version"]
|
||||
|
||||
self.next_state.knowledge_base["docs"]["external_api_url"] = auth_methods["external_api_url"]
|
||||
|
||||
try:
|
||||
url = urljoin(SWAGGER_EMBEDDINGS_API, "upload")
|
||||
async with httpx.AsyncClient(
|
||||
transport=httpx.AsyncHTTPTransport(retries=3), timeout=httpx.Timeout(30.0, connect=60.0)
|
||||
) as client:
|
||||
await client.post(
|
||||
url,
|
||||
json={
|
||||
"text": docs.text.strip(),
|
||||
"project_id": str(self.state_manager.project.id),
|
||||
"user_id": "1",
|
||||
},
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
log.warning(f"Failed to fetch from RAG service: {e}", exc_info=True)
|
||||
|
||||
break
|
||||
except Exception as e:
|
||||
log.debug(f"An error occurred: {str(e)}")
|
||||
@@ -128,9 +151,9 @@ class Wizard(BaseAgent):
|
||||
allow_empty=False,
|
||||
verbose=True,
|
||||
)
|
||||
# self.next_state.knowledge_base["api_key"] = api_key.text.strip()
|
||||
options["auth_type"] = "api_key"
|
||||
options["api_key"] = api_key.text.strip()
|
||||
options["external_api_url"] = self.next_state.knowledge_base["docs"]["external_api_url"]
|
||||
elif auth_type_question.button == "basic":
|
||||
raise NotImplementedError()
|
||||
elif auth_type_question.button == "bearer":
|
||||
@@ -140,7 +163,6 @@ class Wizard(BaseAgent):
|
||||
elif auth_type_question.button == "oauth2":
|
||||
raise NotImplementedError()
|
||||
|
||||
# elif self.state_manager.project.project_type == "node":
|
||||
else:
|
||||
auth_needed = await self.ask_question(
|
||||
"Do you need authentication in your app (login, register, etc.)?",
|
||||
@@ -152,8 +174,7 @@ class Wizard(BaseAgent):
|
||||
default="no",
|
||||
)
|
||||
options = {
|
||||
"auth": auth_needed.button
|
||||
== "yes", # todo fix tests, search for "auth", also options.auth in templates
|
||||
"auth": auth_needed.button == "yes",
|
||||
"auth_type": "login",
|
||||
"jwt_secret": secrets.token_hex(32),
|
||||
"refresh_token_secret": secrets.token_hex(32),
|
||||
|
||||
@@ -50,6 +50,7 @@ FRONTEND_AGENT_NAME = "Frontend"
|
||||
|
||||
# Endpoint for the external documentation
|
||||
EXTERNAL_DOCUMENTATION_API = "http://docs-pythagora-io-439719575.us-east-1.elb.amazonaws.com"
|
||||
SWAGGER_EMBEDDINGS_API = "http://localhost:8000"
|
||||
|
||||
|
||||
class _StrictModel(BaseModel):
|
||||
|
||||
@@ -30,7 +30,7 @@ Whenever you add an API request from the frontend, make sure to wrap the request
|
||||
{% endif %}
|
||||
|
||||
** IMPORTANT - current implementation **
|
||||
Pay close attention to the currently implemented files, and DO NOT tell me to implementat something that is already implemented. Similarly, do not change the current implementation if you think it is working correctly. It is not necessary for you to change files - you can leave the files as they are and just tell me that they are correctly implemented.
|
||||
Pay close attention to the currently implemented files, and DO NOT tell me to implement something that is already implemented. Similarly, do not change the current implementation if you think it is working correctly. It is not necessary for you to change files - you can leave the files as they are and just tell me that they are correctly implemented.
|
||||
~~END_OF_DEVELOPMENT_INSTRUCTIONS~~
|
||||
|
||||
~~DEVELOPMENT_PLAN~~
|
||||
|
||||
@@ -27,7 +27,10 @@ IMPORTANT: Text needs to be readable and in positive typography space - this is
|
||||
You must create all code for all pages of this website. If this is a some sort of a dashboard, put the navigation in the sidebar.
|
||||
|
||||
**IMPORTANT**
|
||||
{% if first_time_build %}
|
||||
Make sure to implement all functionality (button clicks, form submissions, etc.) and use mock data for all interactions to make the app look and feel real. **ALL MOCK DATA MUST** be in the `api/` folder and it **MUST NOT** ever be hardcoded in the components.
|
||||
{% endif %}
|
||||
|
||||
The body content should not overlap with the header navigation bar or footer navigation bar or the side navigation bar.
|
||||
|
||||
{% if user_feedback %}
|
||||
@@ -42,3 +45,11 @@ Now, start by writing all code that's needed to get the frontend built for this
|
||||
|
||||
IMPORTANT: When suggesting/making changes in the file you must provide full content of the file! Do not use placeholders, or comments, or truncation in any way, but instead provide the full content of the file even the parts that are unchanged!
|
||||
When you want to run a command you must put `command:` before the command and then the command itself like shown in the examples in system prompt. NEVER run `npm run start` or `npm run dev` commands, user will run them after you provide the code. The user is using {{ os }}, so the commands must run on that operating system
|
||||
|
||||
{% if relevant_api_documentation is defined %}
|
||||
|
||||
Here is relevant API documentation you need to consult and follow as close as possible.
|
||||
|
||||
{{ relevant_api_documentation }}
|
||||
|
||||
{% endif %}
|
||||
6
core/prompts/frontend/create_rag_query.prompt
Normal file
6
core/prompts/frontend/create_rag_query.prompt
Normal file
@@ -0,0 +1,6 @@
|
||||
{{ file_content }}
|
||||
|
||||
I have external documentation in Swagger Open API format.
|
||||
Create a comma separated list of short words topics of the description of the file so that I can search for the relevant parts of the documentation.
|
||||
Use up to 5 words max.
|
||||
If the file does not need external API documentation, just return "None" and nothing else, no explanation needed.
|
||||
4
core/prompts/frontend/is_relevant_for_docs_search.prompt
Normal file
4
core/prompts/frontend/is_relevant_for_docs_search.prompt
Normal file
@@ -0,0 +1,4 @@
|
||||
{{ user_feedback }}
|
||||
|
||||
Does this prompt require taking a look at the API documentation? For example, you would look at API documentation if you need to implement or edit an API request or response.
|
||||
Reply with a yes or a no, nothing else.
|
||||
17
core/prompts/frontend/remove_mock.prompt
Normal file
17
core/prompts/frontend/remove_mock.prompt
Normal file
@@ -0,0 +1,17 @@
|
||||
Now you need to remove mocked data from the file and replace it with real API requests.
|
||||
Replace only mocked data, do not change any other part of the file.
|
||||
|
||||
{% if relevant_api_documentation is defined %}
|
||||
Here is external documentation that you will need to properly implement real API requests:
|
||||
~~~START_OF_DOCUMENTATION~~~
|
||||
{{ relevant_api_documentation }}
|
||||
~~~END_OF_DOCUMENTATION~~~
|
||||
|
||||
IMPORTANT: Do not implement backend server logic for this, as this is already available in the external API!
|
||||
{% endif %}
|
||||
|
||||
This is the file that you need to change:
|
||||
**`{{ file_path }}`** ({{ lines }} lines of code):
|
||||
```
|
||||
{{ file_content }}
|
||||
```
|
||||
@@ -36,8 +36,6 @@ NEVER run `npm run start` or `npm run dev` commands, user will run them after yo
|
||||
|
||||
IMPORTANT: The order of the actions is very important. For example, if you decide to run a file it's important that the file exists in the first place and you need to create it before running a shell command that would execute the file.
|
||||
|
||||
IMPORTANT: Make sure to implement all functionality (button clicks, form submissions, etc.) and use MOCK DATA for all interactions to make the app look and feel real. MOCK DATA should be used for all interactions.
|
||||
|
||||
IMPORTANT: Put full path of file you are editing! Mostly you will work with files inside "client/" folder so don't forget to put it in file path, for example DO `client/src/App.tsx` instead of `src/App.tsx`.
|
||||
|
||||
{% include "partials/file_naming.prompt" %}
|
||||
|
||||
@@ -7,6 +7,7 @@ This app has 2 parts:
|
||||
* Client-side routing using `react-router-dom` with page components defined in `client/src/pages/` and other components in `client/src/components`
|
||||
* It is running on port 5173 and this port should be used for user testing when possible
|
||||
* All requests to the backend need to go to an endpoint that starts with `/api/` (e.g. `/api/companies`)
|
||||
* Server proxy configuration is already configured and should not be changed in any way!
|
||||
* Implememented pages:
|
||||
* Home - home (index) page (`/`){% if options.auth %}
|
||||
* Login - login page (`/login/`) - on login, stores the auth tokens to `accessToken` and `refreshToken` variables in local storage
|
||||
|
||||
@@ -2,9 +2,21 @@ import axios, { AxiosRequestConfig, AxiosError } from 'axios';
|
||||
|
||||
{% if options.auth_type == "api_key" %}
|
||||
const API_KEY = import.meta.env.VITE_API_KEY;
|
||||
const EXTERNAL_API_URL = import.meta.env.VITE_EXTERNAL_API_URL;
|
||||
{% endif %}
|
||||
|
||||
const api = axios.create({
|
||||
// Create two axios instances
|
||||
const localApi = axios.create({
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
validateStatus: (status) => {
|
||||
return status >= 200 && status < 300;
|
||||
},
|
||||
});
|
||||
|
||||
const externalApi = axios.create({
|
||||
baseURL: EXTERNAL_API_URL,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
@@ -16,81 +28,107 @@ const api = axios.create({
|
||||
let accessToken: string | null = null;
|
||||
|
||||
{% if options.auth %}
|
||||
// Axios request interceptor: Attach access token and API key to headers
|
||||
api.interceptors.request.use(
|
||||
(config: AxiosRequestConfig): AxiosRequestConfig => {
|
||||
// Check if the request is not for login or register
|
||||
const isAuthEndpoint = (url: string): boolean => {
|
||||
return url.includes("/api/auth");
|
||||
};
|
||||
|
||||
const getApiInstance = (url: string) => {
|
||||
return isAuthEndpoint(url) ? localApi : externalApi;
|
||||
};
|
||||
|
||||
// Interceptor for both API instances
|
||||
const setupInterceptors = (apiInstance: typeof axios) => {
|
||||
apiInstance.interceptors.request.use(
|
||||
(config: AxiosRequestConfig): AxiosRequestConfig => {
|
||||
{% if options.auth_type == "api_key" %}
|
||||
const isAuthEndpoint = config.url?.includes('/login') || config.url?.includes('/register');
|
||||
|
||||
if (!isAuthEndpoint) {
|
||||
// Add API key for non-auth endpoints
|
||||
if (config.headers && API_KEY) {
|
||||
config.headers['api_key'] = API_KEY; // or whatever header name your API expects
|
||||
}
|
||||
|
||||
if (!isAuthEndpoint(config.url || '')) {
|
||||
config.baseURL = EXTERNAL_API_URL;
|
||||
if (config.headers && API_KEY) {
|
||||
config.headers['api_key'] = API_KEY;
|
||||
}
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
// Add authorization token if available
|
||||
if (!accessToken) {
|
||||
accessToken = localStorage.getItem('accessToken');
|
||||
}
|
||||
if (accessToken && config.headers) {
|
||||
config.headers.Authorization = `Bearer ${accessToken}`;
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
},
|
||||
(error: AxiosError): Promise<AxiosError> => Promise.reject(error)
|
||||
);
|
||||
return config;
|
||||
},
|
||||
(error: AxiosError): Promise<AxiosError> => Promise.reject(error)
|
||||
);
|
||||
|
||||
// Axios response interceptor: Handle 401 errors
|
||||
api.interceptors.response.use(
|
||||
(response) => response, // If the response is successful, return it
|
||||
async (error: AxiosError): Promise<any> => {
|
||||
const originalRequest = error.config as AxiosRequestConfig & { _retry?: boolean };
|
||||
apiInstance.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error: AxiosError): Promise<any> => {
|
||||
const originalRequest = error.config as AxiosRequestConfig & { _retry?: boolean };
|
||||
|
||||
// If the error is due to an expired access token
|
||||
if ([401, 403].includes(error.response?.status) && !originalRequest._retry) {
|
||||
originalRequest._retry = true; // Mark the request as retried
|
||||
if ([401, 403].includes(error.response?.status) && !originalRequest._retry) {
|
||||
originalRequest._retry = true;
|
||||
|
||||
try {
|
||||
// Attempt to refresh the token
|
||||
const { data } = await axios.post(`/api/auth/refresh`, {
|
||||
refreshToken: localStorage.getItem('refreshToken'),
|
||||
});
|
||||
accessToken = data.data.accessToken;
|
||||
localStorage.setItem('accessToken', accessToken);
|
||||
localStorage.setItem('refreshToken', data.data.refreshToken);
|
||||
try {
|
||||
if (isAuthEndpoint(originalRequest.url || '')) {
|
||||
const { data } = await localApi.post(`/api/auth/refresh`, {
|
||||
refreshToken: localStorage.getItem('refreshToken'),
|
||||
});
|
||||
accessToken = data.data.accessToken;
|
||||
localStorage.setItem('accessToken', accessToken);
|
||||
localStorage.setItem('refreshToken', data.data.refreshToken);
|
||||
}
|
||||
|
||||
// Retry the original request with the new token
|
||||
if (originalRequest.headers) {
|
||||
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
|
||||
// Ensure API key is still present in retry
|
||||
|
||||
{% if options.auth_type == "api_key" %}
|
||||
if (API_KEY) {
|
||||
originalRequest.headers['api_key'] = API_KEY;
|
||||
if (originalRequest.headers) {
|
||||
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
|
||||
{% if options.auth_type == "api_key" %}
|
||||
if (!isAuthEndpoint(originalRequest.url || '') && API_KEY) {
|
||||
originalRequest.headers['api_key'] = API_KEY;
|
||||
}
|
||||
{% endif %}
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
return getApiInstance(originalRequest.url || '')(originalRequest);
|
||||
} catch (err) {
|
||||
localStorage.removeItem('refreshToken');
|
||||
localStorage.removeItem('accessToken');
|
||||
accessToken = null;
|
||||
window.location.href = '/login';
|
||||
return Promise.reject(err);
|
||||
}
|
||||
return api(originalRequest);
|
||||
} catch (err) {
|
||||
// If refresh fails, clear tokens and redirect to login
|
||||
localStorage.removeItem('refreshToken');
|
||||
localStorage.removeItem('accessToken');
|
||||
accessToken = null;
|
||||
window.location.href = '/login'; // Redirect to login page
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(error); // Pass other errors through
|
||||
}
|
||||
);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Setup interceptors for both API instances
|
||||
setupInterceptors(localApi);
|
||||
setupInterceptors(externalApi);
|
||||
{% endif %}
|
||||
|
||||
// Export a wrapper function that chooses the appropriate API instance
|
||||
const api = {
|
||||
request: (config: AxiosRequestConfig) => {
|
||||
const apiInstance = getApiInstance(config.url || '');
|
||||
return apiInstance(config);
|
||||
},
|
||||
get: (url: string, config?: AxiosRequestConfig) => {
|
||||
const apiInstance = getApiInstance(url);
|
||||
return apiInstance.get(url, config);
|
||||
},
|
||||
post: (url: string, data?: any, config?: AxiosRequestConfig) => {
|
||||
const apiInstance = getApiInstance(url);
|
||||
return apiInstance.post(url, data, config);
|
||||
},
|
||||
put: (url: string, data?: any, config?: AxiosRequestConfig) => {
|
||||
const apiInstance = getApiInstance(url);
|
||||
return apiInstance.put(url, data, config);
|
||||
},
|
||||
delete: (url: string, config?: AxiosRequestConfig) => {
|
||||
const apiInstance = getApiInstance(url);
|
||||
return apiInstance.delete(url, config);
|
||||
},
|
||||
};
|
||||
|
||||
export default api;
|
||||
|
||||
@@ -83,7 +83,7 @@ class ViteReactProjectTemplate(BaseProjectTemplate):
|
||||
"client/tsconfig.app.json": "TypeScript configuration for application code.",
|
||||
"client/tsconfig.json": "Main TypeScript configuration with project references.",
|
||||
"client/tsconfig.node.json": "TypeScript configuration for Node.js environment.",
|
||||
"client/vite.config.ts": "Vite build tool configuration with React plugin and aliases.",
|
||||
"client/vite.config.ts": "Vite build tool configuration with server proxy configuration.",
|
||||
"client/tailwind.config.js": "Tailwind CSS configuration with theme customizations, including enabling dark mode, specifying the content files that Tailwind should scan for class names, and extending the default theme with custom values for border radius, colors, keyframes, and animations. The configuration also includes a plugin for animations, specifically 'tailwindcss-animate', which allows for additional animation utilities to be used in the project.",
|
||||
"server/.env": "This file is a configuration file in the form of a .env file. It contains environment variables used by the application, such as the port to listen on, the MongoDB database URL, and the session secret string.",
|
||||
"server/server.js": "This `server.js` file sets up an Express server with MongoDB database connection, session management using connect-mongo, templating engine EJS, static file serving, authentication routes, error handling, and request logging. [References: dotenv, mongoose, express, express-session, connect-mongo, ./routes/authRoutes]",
|
||||
|
||||
Reference in New Issue
Block a user