Compare commits
21 Commits
ac5e9d8b81
...
feat/conve
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
765e390b9b | ||
| 21ce282cae | |||
|
|
4fa9b0456a | ||
| af9900d0ba | |||
|
|
b3ab004447 | ||
| d394bc0857 | |||
|
|
dfa806ab53 | ||
| 3493775b7f | |||
|
|
82645dfb3b | ||
| c17fa243a1 | |||
|
|
a55ed9cc04 | ||
| d1408b74b4 | |||
|
|
4197475eed | ||
| 87bac8894a | |||
|
|
bef4479675 | ||
| 75970c57e3 | |||
|
|
f23044465a | ||
| a6e4d28aa7 | |||
|
|
8693946cb8 | ||
| a2f549c056 | |||
|
|
ad6e57655d |
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from ..db.schemas import (
|
|||||||
)
|
)
|
||||||
from ..db.models import Bot, BotConversation, User
|
from ..db.models import Bot, BotConversation, User
|
||||||
from ..services.ai_agent.crew import get_trading_crew
|
from ..services.ai_agent.crew import get_trading_crew
|
||||||
|
from ..services.ai_agent.conversational import get_conversational_agent
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
MAX_BOTS_PER_USER = 3
|
MAX_BOTS_PER_USER = 3
|
||||||
@@ -183,69 +184,45 @@ def chat(
|
|||||||
.order_by(BotConversation.created_at)
|
.order_by(BotConversation.created_at)
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
history_for_crew = [
|
history_for_agent = [
|
||||||
{"role": conv.role, "content": conv.content}
|
{"role": conv.role, "content": conv.content}
|
||||||
for conv in conversation_history[-10:]
|
for conv in conversation_history[-10:]
|
||||||
]
|
]
|
||||||
|
|
||||||
user_message = request.message
|
user_message = request.message
|
||||||
if request.strategy_config:
|
|
||||||
crew = get_trading_crew()
|
|
||||||
result = crew.chat(user_message, history_for_crew)
|
|
||||||
|
|
||||||
assistant_content = result.get("response", "I couldn't process your request.")
|
# Use ConversationalAgent for natural chat with tool-calling
|
||||||
if result.get("success") and result.get("strategy_config"):
|
agent = get_conversational_agent(bot_id=bot_id)
|
||||||
bot.strategy_config = result["strategy_config"]
|
result = agent.chat(user_message, history_for_agent)
|
||||||
db.commit()
|
|
||||||
|
|
||||||
db_conversation = BotConversation(
|
assistant_content = result.get("response", "I couldn't process your request.")
|
||||||
bot_id=bot_id,
|
|
||||||
role="user",
|
|
||||||
content=user_message,
|
|
||||||
)
|
|
||||||
db.add(db_conversation)
|
|
||||||
|
|
||||||
db_assistant = BotConversation(
|
# Save conversation
|
||||||
bot_id=bot_id,
|
db_conversation = BotConversation(
|
||||||
role="assistant",
|
bot_id=bot_id,
|
||||||
content=assistant_content,
|
role="user",
|
||||||
)
|
content=user_message,
|
||||||
db.add(db_assistant)
|
)
|
||||||
db.commit()
|
db.add(db_conversation)
|
||||||
db.refresh(db_assistant)
|
|
||||||
|
|
||||||
return BotChatResponse(
|
db_assistant = BotConversation(
|
||||||
response=assistant_content,
|
bot_id=bot_id,
|
||||||
strategy_config=result.get("strategy_config"),
|
role="assistant",
|
||||||
success=result.get("success", False),
|
content=assistant_content,
|
||||||
)
|
)
|
||||||
else:
|
db.add(db_assistant)
|
||||||
crew = get_trading_crew()
|
db.commit()
|
||||||
result = crew.chat(user_message, history_for_crew)
|
db.refresh(db_assistant)
|
||||||
|
|
||||||
assistant_content = result.get("response", "I couldn't process your request.")
|
# If strategy was updated via tool, refresh bot data
|
||||||
|
if result.get("strategy_updated"):
|
||||||
|
db.refresh(bot)
|
||||||
|
|
||||||
db_conversation = BotConversation(
|
return BotChatResponse(
|
||||||
bot_id=bot_id,
|
response=assistant_content,
|
||||||
role="user",
|
strategy_config=bot.strategy_config if result.get("strategy_updated") else None,
|
||||||
content=user_message,
|
success=result.get("success", False),
|
||||||
)
|
)
|
||||||
db.add(db_conversation)
|
|
||||||
|
|
||||||
db_assistant = BotConversation(
|
|
||||||
bot_id=bot_id,
|
|
||||||
role="assistant",
|
|
||||||
content=assistant_content,
|
|
||||||
)
|
|
||||||
db.add(db_assistant)
|
|
||||||
db.commit()
|
|
||||||
db.refresh(db_assistant)
|
|
||||||
|
|
||||||
return BotChatResponse(
|
|
||||||
response=assistant_content,
|
|
||||||
strategy_config=result.get("strategy_config"),
|
|
||||||
success=result.get("success", False),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{bot_id}/history", response_model=List[BotConversationResponse])
|
@router.get("/{bot_id}/history", response_model=List[BotConversationResponse])
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
|||||||
167
src/backend/app/services/ai_agent/conversational.py
Normal file
167
src/backend/app/services/ai_agent/conversational.py
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
"""
|
||||||
|
Conversational Trading Agent
|
||||||
|
|
||||||
|
This agent can:
|
||||||
|
1. Have normal conversations with users
|
||||||
|
2. Update trading strategies when user provides specific instructions
|
||||||
|
|
||||||
|
Uses CrewAI's tool-calling capabilities for structured updates.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import List, Optional, Dict, Any
|
||||||
|
from crewai import Agent, LLM
|
||||||
|
from crewai.tools import tool
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from ...core.config import get_settings
|
||||||
|
from ...db.models import Bot
|
||||||
|
|
||||||
|
|
||||||
|
# Tool definitions
|
||||||
|
@tool
|
||||||
|
def get_current_strategy(bot_id: str) -> str:
|
||||||
|
"""Get the current trading strategy configuration for a bot.
|
||||||
|
|
||||||
|
Use this tool to check the current strategy before making changes.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot_id: The ID of the bot to get strategy for
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON string with current strategy configuration
|
||||||
|
"""
|
||||||
|
from ...core.database import get_db
|
||||||
|
from ...db.models import Bot
|
||||||
|
|
||||||
|
db = next(get_db())
|
||||||
|
bot = db.query(Bot).filter(Bot.id == bot_id).first()
|
||||||
|
if not bot:
|
||||||
|
return '{"error": "Bot not found"}'
|
||||||
|
return str(bot.strategy_config)
|
||||||
|
|
||||||
|
|
||||||
|
@tool
|
||||||
|
def update_trading_strategy(
|
||||||
|
bot_id: str,
|
||||||
|
conditions: List[Dict],
|
||||||
|
actions: List[Dict],
|
||||||
|
risk_management: Optional[Dict] = None
|
||||||
|
) -> str:
|
||||||
|
"""Update the trading strategy configuration for a bot.
|
||||||
|
|
||||||
|
Call this tool when the user provides specific trading parameters like:
|
||||||
|
- Buy/sell conditions (price drops, price rises, etc.)
|
||||||
|
- Take profit percentages
|
||||||
|
- Stop loss percentages
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot_id: The ID of the bot to update
|
||||||
|
conditions: List of trigger conditions (e.g., [{"type": "price_drop", "token": "PEPE", "threshold": 5}])
|
||||||
|
actions: List of actions to take (e.g., [{"type": "buy", "amount_percent": 50}])
|
||||||
|
risk_management: Optional risk settings (e.g., {"stop_loss_percent": 10, "take_profit_percent": 50})
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Confirmation message with updated strategy
|
||||||
|
"""
|
||||||
|
from ...core.database import get_db
|
||||||
|
from ...db.models import Bot
|
||||||
|
|
||||||
|
db = next(get_db())
|
||||||
|
bot = db.query(Bot).filter(Bot.id == bot_id).first()
|
||||||
|
if not bot:
|
||||||
|
return '{"error": "Bot not found"}'
|
||||||
|
|
||||||
|
new_config = {
|
||||||
|
"conditions": conditions,
|
||||||
|
"actions": actions,
|
||||||
|
}
|
||||||
|
if risk_management:
|
||||||
|
new_config["risk_management"] = risk_management
|
||||||
|
|
||||||
|
bot.strategy_config = new_config
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
return f'Successfully updated trading strategy. New config: {new_config}'
|
||||||
|
|
||||||
|
|
||||||
|
SYSTEM_PROMPT = """You are a helpful AI trading assistant. You can:
|
||||||
|
|
||||||
|
1. Have normal conversations - answer questions about trading, tokens, strategies, etc.
|
||||||
|
2. Help users configure their trading bots when they provide specific parameters
|
||||||
|
|
||||||
|
When a user asks general questions, just answer conversationally.
|
||||||
|
When a user provides specific trading parameters (like percentages, tokens, conditions),
|
||||||
|
use the update_trading_strategy tool to save their configuration.
|
||||||
|
|
||||||
|
Example conversations:
|
||||||
|
- User: "What is this?" → Answer conversationally about the trading bot platform
|
||||||
|
- User: "I want take profit at 200%" → Use update_trading_strategy with that parameter
|
||||||
|
- User: "Alert me when PEPE drops 5%" → Use update_trading_strategy with that condition
|
||||||
|
|
||||||
|
Be friendly, helpful, and clear in your responses."""
|
||||||
|
|
||||||
|
|
||||||
|
class ConversationalAgent:
|
||||||
|
def __init__(self, api_key: str, model: str = "MiniMax-M2.7", bot_id: str = None):
|
||||||
|
self.api_key = api_key
|
||||||
|
self.model = model
|
||||||
|
self.bot_id = bot_id
|
||||||
|
self.llm = LLM(
|
||||||
|
model=model,
|
||||||
|
api_key=api_key,
|
||||||
|
api_base="https://api.minimax.io/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create agent with tools
|
||||||
|
self.agent = Agent(
|
||||||
|
role="Trading Assistant",
|
||||||
|
goal="Help users with trading strategies and general questions",
|
||||||
|
backstory=SYSTEM_PROMPT,
|
||||||
|
tools=[get_current_strategy, update_trading_strategy],
|
||||||
|
llm=self.llm,
|
||||||
|
verbose=True,
|
||||||
|
allow_delegation=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def chat(self, user_message: str, conversation_history: List[Dict] = None) -> Dict[str, Any]:
|
||||||
|
"""Process a user message and return a response.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_message: The user's message
|
||||||
|
conversation_history: Optional list of previous messages
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict with 'response' (the assistant's reply) and 'strategy_updated' (bool)
|
||||||
|
"""
|
||||||
|
# Execute agent using kickoff
|
||||||
|
try:
|
||||||
|
result = self.agent.kickoff(user_message)
|
||||||
|
|
||||||
|
# Check if strategy was updated
|
||||||
|
result_str = str(result)
|
||||||
|
strategy_updated = "update_trading_strategy" in result_str or \
|
||||||
|
"Successfully updated" in result_str
|
||||||
|
|
||||||
|
return {
|
||||||
|
"response": result_str,
|
||||||
|
"strategy_updated": strategy_updated,
|
||||||
|
"success": True
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"response": f"I encountered an error: {str(e)}. Please try again.",
|
||||||
|
"strategy_updated": False,
|
||||||
|
"success": False
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_conversational_agent(api_key: str = None, model: str = None, bot_id: str = None) -> ConversationalAgent:
|
||||||
|
"""Get or create a ConversationalAgent instance."""
|
||||||
|
if api_key is None:
|
||||||
|
settings = get_settings()
|
||||||
|
api_key = settings.MINIMAX_API_KEY
|
||||||
|
if model is None:
|
||||||
|
settings = get_settings()
|
||||||
|
model = settings.MINIMAX_MODEL
|
||||||
|
|
||||||
|
return ConversationalAgent(api_key=api_key, model=model, bot_id=bot_id)
|
||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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]);
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user