Compare commits

..

23 Commits

Author SHA1 Message Date
21ce282cae Merge pull request 'fix: add fallback UUID generator for crypto.randomUUID compatibility' (#50) from fix/crypto-randomuuid-fallback into main 2026-04-10 06:26:49 +02:00
shokollm
4fa9b0456a fix: add fallback UUID generator for crypto.randomUUID compatibility
crypto.randomUUID() is not available in all environments (e.g., older browsers,
non-secure contexts). Added a fallback UUID v4 implementation.
2026-04-10 04:19:45 +00:00
af9900d0ba Merge pull request 'fix: add timeout for chat requests and improve error handling' (#49) from fix/chat-timeout-handling into main 2026-04-10 06:15:45 +02:00
shokollm
b3ab004447 fix: add timeout for chat requests and improve error handling
Changes:
1. Add 30-second timeout for chat API requests using AbortController
2. User's message now shows immediately before API response (already done in previous PR)
3. Differentiate between timeout errors and other errors in error messages
4. API client now accepts optional signal parameter for abort support
2026-04-10 04:09:30 +00:00
d394bc0857 Merge pull request 'fix: display user messages in chat interface' (#48) from fix/display-user-messages into main 2026-04-10 06:04:23 +02:00
shokollm
dfa806ab53 fix: add user's message to frontend chat store when sending
Previously, only the assistant's response was added to the frontend store.
Now both user and assistant messages are stored, so the conversation
displays correctly in the chat interface.
2026-04-10 04:00:51 +00:00
3493775b7f Merge pull request 'fix: update MiniMaxConnector default model to MiniMax-M2.7' (#47) from fix/minimax-connector-model into main 2026-04-10 06:00:36 +02:00
shokollm
82645dfb3b fix: update MiniMaxConnector default model to MiniMax-M2.7 2026-04-10 03:53:26 +00:00
c17fa243a1 Merge pull request 'fix: use MiniMax text/chatcompletion_v2 endpoint' (#46) from fix/minimax-endpoint-v2 into main 2026-04-10 05:44:25 +02:00
shokollm
a55ed9cc04 fix: use MiniMax text/chatcompletion_v2 endpoint instead of chat/completions
The /v1/chat/completions endpoint returns 529 (overloaded) while
/v1/text/chatcompletion_v2 works reliably.
2026-04-10 03:42:20 +00:00
d1408b74b4 Merge pull request 'fix: properly configure MiniMax API endpoint and CrewAI LLM' (#45) from fix/minimax-api-endpoint-v2 into main 2026-04-10 05:31:13 +02:00
shokollm
4197475eed fix: properly configure CrewAI LLM with MiniMax api_base
- Use CrewAI's LLM class directly with api_base parameter instead of custom subclass
- Remove broken MiniMaxLLM inheritance from LLM
- Update agent creation to use LLM(model, api_key, api_base) pattern

The issue was that inheriting from CrewAI's LLM class caused the api_base
to be set to None. Now we use CrewAI's LLM directly with the correct parameters.

Fixes #43
2026-04-10 03:19:51 +00:00
87bac8894a Merge pull request 'fix: update MiniMax API endpoint to api.minimax.io' (#44) from fix/minimax-api-endpoint into main 2026-04-10 05:10:17 +02:00
shokollm
bef4479675 fix: update MiniMax API endpoint and default model
Changes:
1. Updated API endpoint from api.minimax.chat to api.minimax.io
2. Changed default model from MiniMax-Text-01 to MiniMax-M2.7
   (MiniMax-Text-01 is not available for all API key plans)
3. Updated .env.example with correct default model

MiniMax API docs: https://platform.minimax.io/docs/api-reference/text-anthropic-api

Fixes #43
2026-04-10 03:07:02 +00:00
75970c57e3 Merge pull request 'feat: return access token on user registration' (#42) from feat/41-return-token-on-register into main 2026-04-10 03:31:15 +02:00
shokollm
f23044465a feat: return access token on user registration
After successful registration, the backend now returns an access token
(along with token_type) so the frontend can:
- Store the token in localStorage
- Fetch the user profile
- Redirect to dashboard

Fixes #41
2026-04-10 01:28:01 +00:00
a6e4d28aa7 Merge pull request 'fix: add bcrypt version constraint for passlib compatibility' (#40) from fix/bcrypt-compatibility into main 2026-04-10 02:55:36 +02:00
shokollm
8693946cb8 fix: add bcrypt version constraint for passlib compatibility
bcrypt 5.0.0 is incompatible with passlib 1.7.x - passlib tries to
access bcrypt.__about__.__version__ which was removed in bcrypt 5.x.

Constrain bcrypt to >=4.0,<5.0 to maintain compatibility.
2026-04-10 00:55:18 +00:00
a2f549c056 Merge pull request 'fix: correct import paths in ai_agent module' (#39) from fix/ai-agent-imports into main 2026-04-09 17:32:21 +02:00
shokollm
ad6e57655d fix: correct import paths in ai_agent module
- Fix relative import path in crew.py (from ..core to ...core)
- Update __init__.py exports to match actual class names
- Remove incorrect CrewAgent and LLMConnector exports
2026-04-09 15:27:09 +00:00
ac5e9d8b81 Merge pull request 'fix: add error logging to simulate engine to prevent silent failures' (#38) from fix/issue-30 into main 2026-04-09 12:19:36 +02:00
shokollm
81f3342365 fix: add error logging to simulate engine to prevent silent failures
Errors during price fetching are now logged and stored in an errors list,
allowing users to see error count/warnings in simulation results.

Acceptance Criteria:
- [x] Errors are logged (not silently swallowed)
- [x] User can see error count/warnings in simulation results
- [x] Simulation completes even if some price fetches fail (graceful degradation)
2026-04-09 10:16:22 +00:00
6adad0701d Merge pull request 'fix: consolidate AveCloudClient to single implementation' (#37) from fix/issue-29 into main 2026-04-09 12:11:59 +02:00
10 changed files with 66 additions and 29 deletions

View File

@@ -32,7 +32,7 @@ MINIMAX_API_KEY=your-minimax-api-key
# MiniMax model to use # MiniMax model to use
# Common options: MiniMax-Text-01, MiniMax-M2.1 # Common options: MiniMax-Text-01, MiniMax-M2.1
MINIMAX_MODEL=MiniMax-Text-01 MINIMAX_MODEL=MiniMax-M2.7
# ============================================================================= # =============================================================================
# AVE CLOUD API # AVE CLOUD API

View File

@@ -58,7 +58,7 @@ def get_current_user(
@router.post( @router.post(
"/register", response_model=UserResponse, status_code=status.HTTP_201_CREATED "/register", response_model=Token, status_code=status.HTTP_201_CREATED
) )
def register(user: UserCreate, db: Session = Depends(get_db)): def register(user: UserCreate, db: Session = Depends(get_db)):
existing_user = db.query(User).filter(User.email == user.email).first() existing_user = db.query(User).filter(User.email == user.email).first()
@@ -75,7 +75,10 @@ def register(user: UserCreate, db: Session = Depends(get_db)):
db.add(db_user) db.add(db_user)
db.commit() db.commit()
db.refresh(db_user) db.refresh(db_user)
return db_user
# Generate and return access token so frontend can proceed immediately
access_token = create_access_token(data={"sub": db_user.id})
return Token(access_token=access_token, token_type="bearer")
@router.post("/login", response_model=Token) @router.post("/login", response_model=Token)

View File

@@ -1,4 +1,4 @@
from .crew import CrewAgent from .crew import TradingCrew, get_trading_crew
from .llm_connector import LLMConnector from .llm_connector import MiniMaxLLM, MiniMaxConnector
__all__ = ["CrewAgent", "LLMConnector"] __all__ = ["TradingCrew", "get_trading_crew", "MiniMaxLLM", "MiniMaxConnector"]

View File

@@ -1,7 +1,7 @@
from typing import List, Optional, Dict, Any from typing import List, Optional, Dict, Any
from crewai import Agent, Task, Crew from crewai import Agent, Task, Crew, LLM
from .llm_connector import MiniMaxConnector, MiniMaxLLM from .llm_connector import MiniMaxConnector
from ..core.config import get_settings from ...core.config import get_settings
class StrategyValidator: class StrategyValidator:
@@ -120,7 +120,7 @@ class StrategyExplainer:
def create_trading_designer_agent( def create_trading_designer_agent(
api_key: str, model: str = "MiniMax-Text-01" api_key: str, model: str = "MiniMax-M2.7"
) -> Agent: ) -> Agent:
connector = MiniMaxConnector(api_key=api_key, model=model) connector = MiniMaxConnector(api_key=api_key, model=model)
@@ -141,13 +141,13 @@ def create_trading_designer_agent(
role="Trading Strategy Designer", role="Trading Strategy Designer",
goal="Convert natural language trading requests into precise strategy configurations", goal="Convert natural language trading requests into precise strategy configurations",
backstory=system_prompt, backstory=system_prompt,
llm=MiniMaxLLM(api_key=api_key, model=model), llm=LLM(model=model, api_key=api_key, api_base="https://api.minimax.io/v1"),
verbose=True, verbose=True,
) )
def create_strategy_validator_agent( def create_strategy_validator_agent(
api_key: str, model: str = "MiniMax-Text-01" api_key: str, model: str = "MiniMax-M2.7"
) -> Agent: ) -> Agent:
return Agent( return Agent(
role="Strategy Validator", role="Strategy Validator",
@@ -155,13 +155,13 @@ def create_strategy_validator_agent(
backstory="""You are a meticulous strategy validator with expertise in trading systems. backstory="""You are a meticulous strategy validator with expertise in trading systems.
You check that all required parameters are present, values are reasonable, and the You check that all required parameters are present, values are reasonable, and the
strategy makes logical sense. You never approve strategies with missing or invalid data.""", strategy makes logical sense. You never approve strategies with missing or invalid data.""",
llm=MiniMaxLLM(api_key=api_key, model=model), llm=LLM(model=model, api_key=api_key, api_base="https://api.minimax.io/v1"),
verbose=True, verbose=True,
) )
def create_strategy_explainer_agent( def create_strategy_explainer_agent(
api_key: str, model: str = "MiniMax-Text-01" api_key: str, model: str = "MiniMax-M2.7"
) -> Agent: ) -> Agent:
return Agent( return Agent(
role="Strategy Explainer", role="Strategy Explainer",
@@ -169,13 +169,13 @@ def create_strategy_explainer_agent(
backstory="""You are a patient trading strategy explainer. You translate complex backstory="""You are a patient trading strategy explainer. You translate complex
strategy configurations into easy-to-understand language. You help users understand strategy configurations into easy-to-understand language. You help users understand
exactly what their strategies will do when triggered.""", exactly what their strategies will do when triggered.""",
llm=MiniMaxLLM(api_key=api_key, model=model), llm=LLM(model=model, api_key=api_key, api_base="https://api.minimax.io/v1"),
verbose=True, verbose=True,
) )
class TradingCrew: class TradingCrew:
def __init__(self, api_key: str, model: str = "MiniMax-Text-01"): def __init__(self, api_key: str, model: str = "MiniMax-M2.7"):
self.api_key = api_key self.api_key = api_key
self.model = model self.model = model
self.validator = StrategyValidator() self.validator = StrategyValidator()

View File

@@ -1,14 +1,12 @@
from typing import Optional, List, Dict, Any from typing import Optional, List, Dict, Any
import httpx import httpx
from crewai import LLM
class MiniMaxLLM(LLM): class MiniMaxLLM:
def __init__(self, api_key: str, model: str = "MiniMax-Text-01", **kwargs): def __init__(self, api_key: str, model: str = "MiniMax-M2.7", **kwargs):
super().__init__(**kwargs)
self.api_key = api_key self.api_key = api_key
self.model = model self.model = model
self.base_url = "https://api.minimax.chat/v1" self.base_url = "https://api.minimax.io/v1"
def _call(self, messages: List[Dict[str, str]], **kwargs) -> str: def _call(self, messages: List[Dict[str, str]], **kwargs) -> str:
headers = { headers = {
@@ -23,7 +21,7 @@ class MiniMaxLLM(LLM):
} }
with httpx.Client(timeout=60.0) as client: with httpx.Client(timeout=60.0) as client:
response = client.post( response = client.post(
f"{self.base_url}/chat/completions", f"{self.base_url}/text/chatcompletion_v2",
headers=headers, headers=headers,
json=payload, json=payload,
) )
@@ -35,7 +33,7 @@ class MiniMaxLLM(LLM):
class MiniMaxConnector: class MiniMaxConnector:
def __init__(self, api_key: str, model: str = "MiniMax-Text-01"): def __init__(self, api_key: str, model: str = "MiniMax-M2.7"):
self.api_key = api_key self.api_key = api_key
self.model = model self.model = model

View File

@@ -1,9 +1,12 @@
import uuid import uuid
import asyncio import asyncio
import logging
from datetime import datetime from datetime import datetime
from typing import Dict, Any, List, Optional from typing import Dict, Any, List, Optional
from ..ave.client import AveCloudClient from ..ave.client import AveCloudClient
logger = logging.getLogger(__name__)
class SimulateEngine: class SimulateEngine:
def __init__(self, config: Dict[str, Any]): def __init__(self, config: Dict[str, Any]):
@@ -38,6 +41,7 @@ class SimulateEngine:
self.entry_time: Optional[int] = None self.entry_time: Optional[int] = None
self.current_balance: float = config.get("initial_balance", 10000.0) self.current_balance: float = config.get("initial_balance", 10000.0)
self.trades: List[Dict[str, Any]] = [] self.trades: List[Dict[str, Any]] = []
self.errors: List[str] = []
async def run(self) -> Dict[str, Any]: async def run(self) -> Dict[str, Any]:
self.running = True self.running = True
@@ -74,7 +78,9 @@ class SimulateEngine:
self.last_volume = current_volume self.last_volume = current_volume
except Exception as e: except Exception as e:
pass logger.warning(f"Failed to get price for {token_id}: {e}")
self.errors.append(f"Price fetch failed for {token_id}: {str(e)}")
continue
for _ in range(self.check_interval): for _ in range(self.check_interval):
if not self.running: if not self.running:
@@ -92,6 +98,8 @@ class SimulateEngine:
self.results = self.results or {} self.results = self.results or {}
self.results["total_signals"] = len(self.signals) self.results["total_signals"] = len(self.signals)
self.results["total_errors"] = len(self.errors)
self.results["errors"] = self.errors
self.results["signals"] = self.signals self.results["signals"] = self.signals
self.results["started_at"] = self.started_at self.results["started_at"] = self.started_at
self.results["ended_at"] = datetime.utcnow() self.results["ended_at"] = datetime.utcnow()

View File

@@ -6,6 +6,7 @@ pydantic-settings>=2.1.0
email-validator>=2.0.0 email-validator>=2.0.0
python-jose[cryptography]>=3.3.0 python-jose[cryptography]>=3.3.0
passlib[bcrypt]>=1.7.4 passlib[bcrypt]>=1.7.4
bcrypt>=4.0,<5.0 # Required for passlib compatibility
crewai>=0.1.0 crewai>=0.1.0
anthropic>=0.18.0 anthropic>=0.18.0
httpx>=0.26.0 httpx>=0.26.0

View File

@@ -104,11 +104,12 @@ export const api = {
} }
}, },
async chat(id: string, message: string): Promise<BotChatResponse> { async chat(id: string, message: string, signal?: AbortSignal): Promise<BotChatResponse> {
const response = await fetch(`${API_URL}/bots/${id}/chat`, { const response = await fetch(`${API_URL}/bots/${id}/chat`, {
method: 'POST', method: 'POST',
headers: getAuthHeaders(), headers: getAuthHeaders(),
body: JSON.stringify({ message } as BotChatRequest) body: JSON.stringify({ message } as BotChatRequest),
signal
}); });
return handleResponse<BotChatResponse>(response); return handleResponse<BotChatResponse>(response);
}, },

View File

@@ -8,12 +8,25 @@ export interface ChatMessage {
timestamp: Date; timestamp: Date;
} }
// Fallback UUID generator for environments where crypto.randomUUID is not available
function generateId(): string {
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
return crypto.randomUUID();
}
// Fallback: simple UUID v4 implementation
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = (Math.random() * 16) | 0;
const v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
export const chatStore = writable<ChatMessage[]>([]); export const chatStore = writable<ChatMessage[]>([]);
export function addMessage(message: Omit<ChatMessage, 'id' | 'timestamp'>) { export function addMessage(message: Omit<ChatMessage, 'id' | 'timestamp'>) {
const newMessage: ChatMessage = { const newMessage: ChatMessage = {
...message, ...message,
id: crypto.randomUUID(), id: generateId(),
timestamp: new Date() timestamp: new Date()
}; };
chatStore.update(messages => [...messages, newMessage]); chatStore.update(messages => [...messages, newMessage]);

View File

@@ -44,8 +44,17 @@
isSending = true; isSending = true;
// Add user's message immediately so it shows even before API response
addMessage({ role: 'user', content: message });
try { try {
const response = await api.bots.chat(botId, message); // Add timeout to prevent hanging requests
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30000);
const response = await api.bots.chat(botId, message, controller.signal);
clearTimeout(timeoutId);
addMessage({ role: 'assistant', content: response.response }); addMessage({ role: 'assistant', content: response.response });
if (response.strategy_config) { if (response.strategy_config) {
@@ -53,7 +62,11 @@
setCurrentBot(bot); setCurrentBot(bot);
} }
} catch (e) { } catch (e) {
addMessage({ role: 'assistant', content: 'Sorry, I encountered an error. Please try again.' }); if (e instanceof Error && e.name === 'AbortError') {
addMessage({ role: 'assistant', content: 'Request timed out. Please try again.' });
} else {
addMessage({ role: 'assistant', content: 'Sorry, I encountered an error. Please try again.' });
}
} finally { } finally {
isSending = false; isSending = false;
} }