feat: Add slash command system with skill acknowledgments
- Reset conversational.py to pr-58 working AVE integration - Added TOOL_REGISTRY with all available slash commands - Added _handle_slash_command for skill activation - Slash commands show brief acknowledgment when used alone - Slash commands with args are passed to AI for handling - Added dropdown menu in ChatInterface for skill discovery - Menu positions above textarea - Menu shows filtered tools as user types
This commit is contained in:
@@ -114,6 +114,19 @@ TOOL_REGISTRY = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Skill emojis mapping
|
||||||
|
SKILL_EMOJIS = {
|
||||||
|
"backtest": "📊",
|
||||||
|
"simulate": "🎮",
|
||||||
|
"strategy": "📝",
|
||||||
|
"search": "🔍",
|
||||||
|
"trending": "📈",
|
||||||
|
"risk": "📉",
|
||||||
|
"token": "🪙",
|
||||||
|
"price": "💰",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_tool_registry() -> Dict[str, Any]:
|
def get_tool_registry() -> Dict[str, Any]:
|
||||||
"""Return the tool registry for slash command help."""
|
"""Return the tool registry for slash command help."""
|
||||||
return TOOL_REGISTRY
|
return TOOL_REGISTRY
|
||||||
@@ -125,7 +138,7 @@ def format_tools_list() -> str:
|
|||||||
|
|
||||||
for category in ["randebu", "ave"]:
|
for category in ["randebu", "ave"]:
|
||||||
tools = TOOL_REGISTRY.get(category, [])
|
tools = TOOL_REGISTRY.get(category, [])
|
||||||
if category == "randebu":
|
if category == "randedu":
|
||||||
message += "🤖 Randebu Built-in:\n"
|
message += "🤖 Randebu Built-in:\n"
|
||||||
else:
|
else:
|
||||||
message += "☁️ AVE Cloud Skills:\n"
|
message += "☁️ AVE Cloud Skills:\n"
|
||||||
@@ -140,6 +153,12 @@ def format_tools_list() -> str:
|
|||||||
return message
|
return message
|
||||||
|
|
||||||
|
|
||||||
|
def format_skill_acknowledgment(tool_name: str, description: str) -> str:
|
||||||
|
"""Format a brief acknowledgment when a skill is activated."""
|
||||||
|
emoji = SKILL_EMOJIS.get(tool_name.lower(), "✨")
|
||||||
|
return f"{emoji} **{tool_name}** loaded. Ready for *{description}*, ask me away!"
|
||||||
|
|
||||||
|
|
||||||
def format_tool_help(tool_name: str) -> str:
|
def format_tool_help(tool_name: str) -> str:
|
||||||
"""Format detailed help for a specific tool."""
|
"""Format detailed help for a specific tool."""
|
||||||
tool_name = tool_name.lstrip("/")
|
tool_name = tool_name.lstrip("/")
|
||||||
@@ -209,7 +228,7 @@ Your response must be valid JSON with exactly this structure:
|
|||||||
"actions": [{"type": "buy" | "sell" | "hold", "amount_percent": number, ...}],
|
"actions": [{"type": "buy" | "sell" | "hold", "amount_percent": number, ...}],
|
||||||
"risk_management": {"stop_loss_percent": number, "take_profit_percent": number}
|
"risk_management": {"stop_loss_percent": number, "take_profit_percent": number}
|
||||||
}
|
}
|
||||||
}
|
}"
|
||||||
|
|
||||||
Guidelines:
|
Guidelines:
|
||||||
- "thinking" should be detailed reasoning about the user's request
|
- "thinking" should be detailed reasoning about the user's request
|
||||||
@@ -337,6 +356,28 @@ TOOLS = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "get_trending",
|
||||||
|
"description": "Get trending tokens on a blockchain. Use when user asks what's trending, top tokens, or popular tokens right now.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"chain": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Blockchain chain (default: bsc)",
|
||||||
|
"default": "bsc",
|
||||||
|
},
|
||||||
|
"limit": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Number of trending tokens to return (default: 10, max: 50)",
|
||||||
|
"default": 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "function",
|
"type": "function",
|
||||||
"function": {
|
"function": {
|
||||||
@@ -405,6 +446,7 @@ You have access to tools:
|
|||||||
- get_token(address, chain): Get detailed information about a specific token. Use when user asks for token details.
|
- get_token(address, chain): Get detailed information about a specific token. Use when user asks for token details.
|
||||||
- get_price(token_ids): Get current price(s) for tokens. Use when user asks for token price.
|
- get_price(token_ids): Get current price(s) for tokens. Use when user asks for token price.
|
||||||
- get_risk(address, chain): Get risk analysis for a token. Use when user asks about token safety or honeypot analysis.
|
- get_risk(address, chain): Get risk analysis for a token. Use when user asks about token safety or honeypot analysis.
|
||||||
|
- get_trending(chain, limit): Get trending tokens on a blockchain. Use when user asks what's trending, top tokens, or popular tokens.
|
||||||
- run_backtest(token_address, timeframe, start_date, end_date): Run a backtest on historical data. Returns performance metrics. Use when user asks to backtest or check historical performance.
|
- run_backtest(token_address, timeframe, start_date, end_date): Run a backtest on historical data. Returns performance metrics. Use when user asks to backtest or check historical performance.
|
||||||
- manage_simulation(action, token_address, kline_interval): Manage trading simulations. Actions: 'start' (begin new), 'stop' (stop running), 'status' (check if running), 'results' (get current/latest results).
|
- manage_simulation(action, token_address, kline_interval): Manage trading simulations. Actions: 'start' (begin new), 'stop' (stop running), 'status' (check if running), 'results' (get current/latest results).
|
||||||
|
|
||||||
@@ -455,9 +497,31 @@ class ConversationalAgent:
|
|||||||
"success": True,
|
"success": True,
|
||||||
}
|
}
|
||||||
elif cmd.startswith("/"):
|
elif cmd.startswith("/"):
|
||||||
tool_name = cmd[1:]
|
# Check if it's a known skill (only look at first word after /)
|
||||||
|
parts = cmd[1:].split()
|
||||||
|
tool_name = parts[0].lower() if parts else ""
|
||||||
|
has_args = len(parts) > 1
|
||||||
|
|
||||||
|
for category in ["randebu", "ave"]:
|
||||||
|
for tool in TOOL_REGISTRY.get(category, []):
|
||||||
|
if tool["name"].lower() == tool_name:
|
||||||
|
# If no additional arguments, return skill acknowledgment
|
||||||
|
# If has arguments, return None to let AI handle it
|
||||||
|
if not has_args:
|
||||||
return {
|
return {
|
||||||
"response": format_tool_help(tool_name),
|
"response": format_skill_acknowledgment(
|
||||||
|
tool["name"], tool["description"]
|
||||||
|
),
|
||||||
|
"thinking": None,
|
||||||
|
"strategy_updated": False,
|
||||||
|
"strategy_needs_confirmation": False,
|
||||||
|
"success": True,
|
||||||
|
}
|
||||||
|
return None # Has args - let AI handle it
|
||||||
|
|
||||||
|
# Unknown skill
|
||||||
|
return {
|
||||||
|
"response": f"Unknown command '{tool_name}'. Type / to see available tools.",
|
||||||
"thinking": None,
|
"thinking": None,
|
||||||
"strategy_updated": False,
|
"strategy_updated": False,
|
||||||
"strategy_needs_confirmation": False,
|
"strategy_needs_confirmation": False,
|
||||||
@@ -487,7 +551,10 @@ class ConversationalAgent:
|
|||||||
try:
|
try:
|
||||||
# Handle slash commands
|
# Handle slash commands
|
||||||
if user_message.startswith("/"):
|
if user_message.startswith("/"):
|
||||||
return self._handle_slash_command(user_message)
|
result = self._handle_slash_command(user_message)
|
||||||
|
# If None returned, it means a skill was recognized but has args - let AI handle it
|
||||||
|
if result is not None:
|
||||||
|
return result
|
||||||
|
|
||||||
# Build messages array with system prompt and conversation history
|
# Build messages array with system prompt and conversation history
|
||||||
messages = [{"role": "system", "content": SYSTEM_PROMPT_WITH_TOOLS}]
|
messages = [{"role": "system", "content": SYSTEM_PROMPT_WITH_TOOLS}]
|
||||||
@@ -518,11 +585,11 @@ class ConversationalAgent:
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
result = resp.json()
|
result = resp.json() or {}
|
||||||
|
|
||||||
# Extract thinking from reasoning_content
|
# Extract thinking from reasoning_content
|
||||||
thinking = None
|
thinking = None
|
||||||
if "choices" in result and len(result["choices"]) > 0:
|
if result.get("choices") and len(result.get("choices", [])) > 0:
|
||||||
choice = result["choices"][0]
|
choice = result["choices"][0]
|
||||||
if "message" in choice:
|
if "message" in choice:
|
||||||
message = choice["message"]
|
message = choice["message"]
|
||||||
@@ -554,7 +621,12 @@ class ConversationalAgent:
|
|||||||
if code == 0:
|
if code == 0:
|
||||||
try:
|
try:
|
||||||
data = json.loads(output)
|
data = json.loads(output)
|
||||||
tokens = data.get("data", {}).get("tokens", [])
|
# Handle both dict with 'tokens' key and direct list
|
||||||
|
data_field = data.get("data", [])
|
||||||
|
if isinstance(data_field, list):
|
||||||
|
tokens = data_field
|
||||||
|
else:
|
||||||
|
tokens = data_field.get("tokens", [])
|
||||||
if tokens:
|
if tokens:
|
||||||
token_list = ""
|
token_list = ""
|
||||||
for t in tokens[:limit]:
|
for t in tokens[:limit]:
|
||||||
@@ -565,7 +637,11 @@ class ConversationalAgent:
|
|||||||
"token_price_change_24h", "N/A"
|
"token_price_change_24h", "N/A"
|
||||||
)
|
)
|
||||||
mc = t.get("market_cap", "N/A")
|
mc = t.get("market_cap", "N/A")
|
||||||
token_list += f"- **{symbol}** ({name}): `{addr}` - MC: ${mc:,.0f} - 24h: {price_change}%\n"
|
try:
|
||||||
|
mc_str = f"${float(mc):,.0f}"
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
mc_str = str(mc)
|
||||||
|
token_list += f"- **{symbol}** ({name}): `{addr}` - MC: {mc_str} - 24h: {price_change}%\n"
|
||||||
response_text = f"Here are the search results for '{keyword}' on BSC:\n\n{token_list}\nWould you like me to set up a strategy for any of these?"
|
response_text = f"Here are the search results for '{keyword}' on BSC:\n\n{token_list}\nWould you like me to set up a strategy for any of these?"
|
||||||
else:
|
else:
|
||||||
response_text = f"No tokens found for '{keyword}'. Try a different keyword."
|
response_text = f"No tokens found for '{keyword}'. Try a different keyword."
|
||||||
@@ -594,24 +670,51 @@ class ConversationalAgent:
|
|||||||
if code == 0:
|
if code == 0:
|
||||||
try:
|
try:
|
||||||
data = json.loads(output)
|
data = json.loads(output)
|
||||||
token_data = data.get("data", {})
|
data_field = data.get("data")
|
||||||
if token_data:
|
# Handle both dict and list responses
|
||||||
symbol = token_data.get("symbol", "N/A")
|
token_data = data_field if isinstance(data_field, dict) else {}
|
||||||
name = token_data.get("name", "N/A")
|
# Token details may be nested in 'token' key
|
||||||
price = token_data.get("price", "N/A")
|
token_info = token_data.get("token", token_data)
|
||||||
mc = token_data.get("market_cap", "N/A")
|
# Check if token has valid symbol/name (not None, not 'N/A')
|
||||||
vol = token_data.get("volume_24h", "N/A")
|
symbol = token_info.get("symbol") or token_data.get("symbol")
|
||||||
pairs = token_data.get("top_pairs", [])
|
name = token_info.get("name") or token_data.get("name")
|
||||||
|
if not symbol or symbol == "N/A" or not name or name == "N/A":
|
||||||
|
response_text = f"Token not found for {address}. Raw response: {output[:500]}"
|
||||||
|
else:
|
||||||
|
# Try different price field names
|
||||||
|
price = (token_info.get("current_price_usd")
|
||||||
|
or token_info.get("price_usd")
|
||||||
|
or token_info.get("price")
|
||||||
|
or token_data.get("price")
|
||||||
|
or "N/A")
|
||||||
|
mc = (token_info.get("market_cap")
|
||||||
|
or token_info.get("fdv")
|
||||||
|
or token_data.get("market_cap")
|
||||||
|
or "N/A")
|
||||||
|
vol = (token_info.get("tx_volume_u_24h")
|
||||||
|
or token_info.get("volume_24h")
|
||||||
|
or token_data.get("volume_24h")
|
||||||
|
or "N/A")
|
||||||
|
pairs = token_info.get("top_pairs") or token_data.get("top_pairs") or []
|
||||||
pairs_text = ""
|
pairs_text = ""
|
||||||
if pairs:
|
if pairs:
|
||||||
pairs_text = "\n**Top Pairs:**\n"
|
pairs_text = "\n**Top Pairs:**\n"
|
||||||
for p in pairs[:3]:
|
for p in pairs[:3]:
|
||||||
pairs_text += f"- {p.get('pair', 'N/A')}: ${p.get('liquidity', 'N/A'):,.0f} liquidity\n"
|
liq = p.get('liquidity', 'N/A')
|
||||||
response_text = f"**{symbol}** ({name})\n\nPrice: ${price}\nMarket Cap: ${mc:,.0f}\n24h Volume: ${vol:,.0f}{pairs_text}"
|
try:
|
||||||
else:
|
liq_str = f"${float(liq):,.0f}"
|
||||||
response_text = (
|
except (ValueError, TypeError):
|
||||||
f"Token not found: {address}"
|
liq_str = str(liq)
|
||||||
)
|
pairs_text += f"- {p.get('pair', 'N/A')}: {liq_str} liquidity\n"
|
||||||
|
try:
|
||||||
|
mc_str = f"${float(mc):,.0f}" if mc != "N/A" else "N/A"
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
mc_str = str(mc)
|
||||||
|
try:
|
||||||
|
vol_str = f"${float(vol):,.0f}" if vol != "N/A" else "N/A"
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
vol_str = str(vol)
|
||||||
|
response_text = f"**{symbol}** ({name})\n\nPrice: ${price}\nMarket Cap: {mc_str}\n24h Volume: {vol_str}{pairs_text}"
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
response_text = "Failed to parse token data."
|
response_text = "Failed to parse token data."
|
||||||
else:
|
else:
|
||||||
@@ -641,6 +744,9 @@ class ConversationalAgent:
|
|||||||
try:
|
try:
|
||||||
data = json.loads(output)
|
data = json.loads(output)
|
||||||
prices = data.get("data", {})
|
prices = data.get("data", {})
|
||||||
|
# Ensure prices is a dict
|
||||||
|
if not isinstance(prices, dict):
|
||||||
|
prices = {}
|
||||||
if prices:
|
if prices:
|
||||||
price_text = "**Token Prices:**\n"
|
price_text = "**Token Prices:**\n"
|
||||||
for (
|
for (
|
||||||
@@ -686,27 +792,53 @@ class ConversationalAgent:
|
|||||||
if code == 0:
|
if code == 0:
|
||||||
try:
|
try:
|
||||||
data = json.loads(output)
|
data = json.loads(output)
|
||||||
risk_data = data.get("data", {})
|
data_field = data.get("data")
|
||||||
|
# Handle both dict and list responses
|
||||||
|
risk_data = data_field if isinstance(data_field, dict) else {}
|
||||||
if risk_data:
|
if risk_data:
|
||||||
is_honeypot = risk_data.get(
|
is_honeypot = risk_data.get(
|
||||||
"is_honeypot", "unknown"
|
"is_honeypot", "unknown"
|
||||||
)
|
)
|
||||||
buy_tax = risk_data.get("buy_tax", "N/A")
|
buy_tax = risk_data.get("buy_tax", 0)
|
||||||
sell_tax = risk_data.get("sell_tax", "N/A")
|
sell_tax = risk_data.get("sell_tax", 0)
|
||||||
status = risk_data.get("status", "unknown")
|
status = risk_data.get("status", "unknown")
|
||||||
|
# Convert is_honeypot to string for comparison
|
||||||
|
if isinstance(is_honeypot, bool):
|
||||||
|
is_honeypot_str = str(is_honeypot).lower()
|
||||||
|
elif isinstance(is_honeypot, int):
|
||||||
|
if is_honeypot == 1:
|
||||||
|
is_honeypot_str = "true"
|
||||||
|
elif is_honeypot == 0:
|
||||||
|
is_honeypot_str = "false"
|
||||||
|
else:
|
||||||
|
is_honeypot_str = "unknown" # -1 or other means couldn't determine
|
||||||
|
else:
|
||||||
|
is_honeypot_str = str(is_honeypot).lower() if is_honeypot else "unknown"
|
||||||
|
|
||||||
|
# Format honeypot display value
|
||||||
|
if is_honeypot_str == "unknown":
|
||||||
|
honeypot_display = "Unknown (could not determine)"
|
||||||
|
else:
|
||||||
|
honeypot_display = is_honeypot_str
|
||||||
|
# Convert tax values to float for comparison
|
||||||
|
try:
|
||||||
|
buy_tax_val = float(buy_tax) if buy_tax not in (None, "N/A") else 0
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
buy_tax_val = 0
|
||||||
|
try:
|
||||||
|
sell_tax_val = float(sell_tax) if sell_tax not in (None, "N/A") else 0
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
sell_tax_val = 0
|
||||||
risk_text = (
|
risk_text = (
|
||||||
f"**Risk Analysis for {address}**\n\n"
|
f"**Risk Analysis for {address}**\n\n"
|
||||||
)
|
)
|
||||||
risk_text += f"- Status: {status}\n"
|
risk_text += f"- Status: {status}\n"
|
||||||
risk_text += f"- Honeypot: {is_honeypot}\n"
|
risk_text += f"- Honeypot: {honeypot_display}\n"
|
||||||
risk_text += f"- Buy Tax: {buy_tax}%\n"
|
risk_text += f"- Buy Tax: {buy_tax}%\n"
|
||||||
risk_text += f"- Sell Tax: {sell_tax}%\n"
|
risk_text += f"- Sell Tax: {sell_tax}%\n"
|
||||||
if is_honeypot.lower() == "true":
|
if is_honeypot_str == "true":
|
||||||
risk_text += "\n⚠️ **Warning: This token appears to be a honeypot. Do not buy!**"
|
risk_text += "\n⚠️ **Warning: This token appears to be a honeypot. Do not buy!**"
|
||||||
elif (
|
elif buy_tax_val > 10 or sell_tax_val > 10:
|
||||||
float(buy_tax or 0) > 10
|
|
||||||
or float(sell_tax or 0) > 10
|
|
||||||
):
|
|
||||||
risk_text += "\n⚠️ **Warning: High tax detected. Trade with caution!**"
|
risk_text += "\n⚠️ **Warning: High tax detected. Trade with caution!**"
|
||||||
else:
|
else:
|
||||||
risk_text += "\n✅ This token appears safe to trade."
|
risk_text += "\n✅ This token appears safe to trade."
|
||||||
@@ -728,6 +860,49 @@ class ConversationalAgent:
|
|||||||
"success": True,
|
"success": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
elif func_name == "get_trending":
|
||||||
|
chain = args.get("chain", "bsc")
|
||||||
|
limit = args.get("limit", 10)
|
||||||
|
|
||||||
|
code, output = self._call_ave_script(
|
||||||
|
"trending",
|
||||||
|
["--chain", chain, "--page-size", str(min(limit, 50))],
|
||||||
|
)
|
||||||
|
if code == 0:
|
||||||
|
try:
|
||||||
|
data = json.loads(output)
|
||||||
|
data_field = data.get("data")
|
||||||
|
# Handle both dict and list responses
|
||||||
|
tokens = data_field if isinstance(data_field, list) else data_field.get("tokens", [])
|
||||||
|
if tokens:
|
||||||
|
token_list = ""
|
||||||
|
for t in tokens[:limit]:
|
||||||
|
addr = t.get("token", "")
|
||||||
|
symbol = t.get("symbol", "")
|
||||||
|
name = t.get("name", "")
|
||||||
|
price_change = t.get("token_price_change_24h", "N/A")
|
||||||
|
mc = t.get("market_cap", "N/A")
|
||||||
|
try:
|
||||||
|
mc_str = f"${float(mc):,.0f}"
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
mc_str = str(mc)
|
||||||
|
token_list += f"- **{symbol}** ({name}): `{addr}` - MC: {mc_str} - 24h: {price_change}%\n"
|
||||||
|
response_text = f"🔥 Trending tokens on {chain.upper()}:\n\n{token_list}\nWould you like me to set up a strategy for any of these?"
|
||||||
|
else:
|
||||||
|
response_text = f"No trending tokens found on {chain.upper()}."
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
response_text = "Failed to parse trending data."
|
||||||
|
else:
|
||||||
|
response_text = f"Failed to get trending tokens: {output}"
|
||||||
|
|
||||||
|
return {
|
||||||
|
"response": response_text,
|
||||||
|
"thinking": thinking,
|
||||||
|
"strategy_updated": False,
|
||||||
|
"strategy_needs_confirmation": False,
|
||||||
|
"success": True,
|
||||||
|
}
|
||||||
|
|
||||||
elif func_name == "run_backtest":
|
elif func_name == "run_backtest":
|
||||||
token_address = args.get("token_address")
|
token_address = args.get("token_address")
|
||||||
timeframe = args.get("timeframe", "1d")
|
timeframe = args.get("timeframe", "1d")
|
||||||
@@ -823,7 +998,12 @@ class ConversationalAgent:
|
|||||||
if code == 0:
|
if code == 0:
|
||||||
try:
|
try:
|
||||||
data = json.loads(output)
|
data = json.loads(output)
|
||||||
tokens = data.get("data", {}).get("tokens", [])
|
# Handle both dict with 'tokens' key and direct list
|
||||||
|
data_field = data.get("data", [])
|
||||||
|
if isinstance(data_field, list):
|
||||||
|
tokens = data_field
|
||||||
|
else:
|
||||||
|
tokens = data_field.get("tokens", [])
|
||||||
if tokens:
|
if tokens:
|
||||||
token_list = ""
|
token_list = ""
|
||||||
for t in tokens[:limit]:
|
for t in tokens[:limit]:
|
||||||
@@ -834,7 +1014,11 @@ class ConversationalAgent:
|
|||||||
"token_price_change_24h", "N/A"
|
"token_price_change_24h", "N/A"
|
||||||
)
|
)
|
||||||
mc = t.get("market_cap", "N/A")
|
mc = t.get("market_cap", "N/A")
|
||||||
token_list += f"- **{symbol}** ({name}): `{addr}` - MC: ${mc:,.0f} - 24h: {price_change}%\n"
|
try:
|
||||||
|
mc_str = f"${float(mc):,.0f}"
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
mc_str = str(mc)
|
||||||
|
token_list += f"- **{symbol}** ({name}): `{addr}` - MC: {mc_str} - 24h: {price_change}%\n"
|
||||||
response_text = f"Here are the search results for '{keyword}' on BSC:\n\n{token_list}\nWould you like me to set up a strategy for any of these?"
|
response_text = f"Here are the search results for '{keyword}' on BSC:\n\n{token_list}\nWould you like me to set up a strategy for any of these?"
|
||||||
else:
|
else:
|
||||||
response_text = f"No tokens found for '{keyword}'. Try a different keyword."
|
response_text = f"No tokens found for '{keyword}'. Try a different keyword."
|
||||||
@@ -858,7 +1042,7 @@ class ConversationalAgent:
|
|||||||
if strategy_update:
|
if strategy_update:
|
||||||
# Extract token name from conditions
|
# Extract token name from conditions
|
||||||
token_name = None
|
token_name = None
|
||||||
for cond in strategy_update.get("conditions", []):
|
for cond in (strategy_update.get("conditions") or []):
|
||||||
if not cond.get("token_address") and cond.get("token"):
|
if not cond.get("token_address") and cond.get("token"):
|
||||||
token_name = cond.get("token")
|
token_name = cond.get("token")
|
||||||
strategy_needs_confirmation = True
|
strategy_needs_confirmation = True
|
||||||
@@ -873,7 +1057,12 @@ class ConversationalAgent:
|
|||||||
)
|
)
|
||||||
if code == 0:
|
if code == 0:
|
||||||
data = json.loads(output)
|
data = json.loads(output)
|
||||||
tokens = data.get("data", {}).get("tokens", [])
|
# Handle both dict with 'tokens' key and direct list
|
||||||
|
data_field = data.get("data", [])
|
||||||
|
if isinstance(data_field, list):
|
||||||
|
tokens = data_field
|
||||||
|
else:
|
||||||
|
tokens = data_field.get("tokens", [])
|
||||||
if tokens:
|
if tokens:
|
||||||
token_search_results = [
|
token_search_results = [
|
||||||
{
|
{
|
||||||
@@ -1296,7 +1485,7 @@ Would you like me to adjust the strategy parameters based on these results?"""
|
|||||||
|
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
repo_root = os.path.dirname(
|
repo_root = os.path.dirname(
|
||||||
os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
|
os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))))
|
||||||
)
|
)
|
||||||
ave_skill_path = os.path.join(
|
ave_skill_path = os.path.join(
|
||||||
repo_root, "ave-cloud-skill", "scripts", "ave_data_rest.py"
|
repo_root, "ave-cloud-skill", "scripts", "ave_data_rest.py"
|
||||||
@@ -1315,7 +1504,11 @@ Would you like me to adjust the strategy parameters based on these results?"""
|
|||||||
env=env,
|
env=env,
|
||||||
timeout=30,
|
timeout=30,
|
||||||
)
|
)
|
||||||
return result.returncode, result.stdout
|
# Include stderr in output for debugging
|
||||||
|
output = result.stdout
|
||||||
|
if result.returncode != 0 and result.stderr:
|
||||||
|
output = f"{output}\n{result.stderr}".strip()
|
||||||
|
return result.returncode, output
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
return 1, "Error: Command timed out"
|
return 1, "Error: Command timed out"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -57,9 +57,11 @@
|
|||||||
let expandedThinking: Record<string, boolean> = $state({});
|
let expandedThinking: Record<string, boolean> = $state({});
|
||||||
let showSlashMenu = $state(false);
|
let showSlashMenu = $state(false);
|
||||||
let slashMenuPosition = $state({ top: 0, left: 0 });
|
let slashMenuPosition = $state({ top: 0, left: 0 });
|
||||||
let filteredTools = $state<ToolItem[]>([]);
|
|
||||||
let selectedIndex = $state(0);
|
let selectedIndex = $state(0);
|
||||||
|
|
||||||
|
// Use $derived for filteredTools
|
||||||
|
let filteredTools = $derived(messageInput.startsWith('/') ? TOOLS.flatMap(t => t.tools).filter(tool => tool.name.toLowerCase().startsWith(messageInput.slice(1).toLowerCase()) || tool.command.toLowerCase().startsWith(messageInput.slice(1).toLowerCase())) : []);
|
||||||
|
|
||||||
function handleSend() {
|
function handleSend() {
|
||||||
if (!messageInput.trim()) return;
|
if (!messageInput.trim()) return;
|
||||||
showSlashMenu = false;
|
showSlashMenu = false;
|
||||||
@@ -95,18 +97,15 @@
|
|||||||
messageInput = value;
|
messageInput = value;
|
||||||
|
|
||||||
if (value.startsWith('/')) {
|
if (value.startsWith('/')) {
|
||||||
const query = value.slice(1).toLowerCase();
|
|
||||||
filteredTools = TOOLS.flatMap(t => t.tools).filter(tool =>
|
|
||||||
tool.name.toLowerCase().startsWith(query) ||
|
|
||||||
tool.command.toLowerCase().startsWith(query)
|
|
||||||
);
|
|
||||||
selectedIndex = 0;
|
selectedIndex = 0;
|
||||||
showSlashMenu = filteredTools.length > 0;
|
showSlashMenu = filteredTools.length > 0;
|
||||||
|
|
||||||
if (showSlashMenu) {
|
if (showSlashMenu) {
|
||||||
|
// Position menu above the textarea
|
||||||
const rect = target.getBoundingClientRect();
|
const rect = target.getBoundingClientRect();
|
||||||
|
const menuHeight = 300;
|
||||||
slashMenuPosition = {
|
slashMenuPosition = {
|
||||||
top: rect.top - 10,
|
top: Math.max(10, rect.top - menuHeight),
|
||||||
left: rect.left
|
left: rect.left
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -330,7 +329,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<textarea
|
<textarea
|
||||||
bind:value={messageInput}
|
value={messageInput}
|
||||||
oninput={handleInput}
|
oninput={handleInput}
|
||||||
onkeydown={handleKeydown}
|
onkeydown={handleKeydown}
|
||||||
placeholder="Describe your trading strategy... (or type / for commands)"
|
placeholder="Describe your trading strategy... (or type / for commands)"
|
||||||
|
|||||||
Reference in New Issue
Block a user