Compare commits

..

18 Commits

Author SHA1 Message Date
shokollm
2d125ede22 fix: Also fix price_change field in AI tool execution path
There was duplicate code handling search_tokens in the AI tool calling section.
2026-04-14 01:43:30 +00:00
shokollm
7a64632a63 fix: Correct price_change field fallback logic
Was returning 'N/A' incorrectly when token_price_change_24h was missing.
Now properly checks: price_change_24h OR token_price_change_24h OR 'N/A'
2026-04-14 00:37:48 +00:00
shokollm
bb62e53093 fix: Handle price_change_24h field name in search results
Search API returns 'price_change_24h' not 'token_price_change_24h'. Now checks for both.
2026-04-14 00:33:38 +00:00
shokollm
cf74251ad0 fix: Show token name/symbol in risk analysis and handle unknown honeypot
- Display token name and symbol in risk analysis output
- Handle is_honeypot: -1 as 'Unknown (could not determine)'
- Show risk level (Low/Medium/High) with risk score
- Use risk_level field instead of status
2026-04-14 00:28:15 +00:00
shokollm
1efc0eaba6 feat: Add context awareness for price tool
- Store recent search results in agent instance
- When price returns empty, suggest using /search tool
- Check if user input matches recent search results and use that address
2026-04-14 00:19:42 +00:00
shokollm
f4f6168f68 revert: Keep using price API for price lookups
The price API requires full contract addresses (0x...-bsc format).
Improved error handling and formatting for price responses.
2026-04-14 00:12:46 +00:00
shokollm
62bcd6e099 fix: Use search API for price lookups instead of price API
The price API requires full contract addresses (0x...-bsc), but users typically provide symbols.
Now /price TRADOOR will search for the token and show price info from search results.
2026-04-14 00:03:34 +00:00
shokollm
6b8912a7eb fix: Better error detection for AVE API commands
- Added _is_error_output helper to detect errors in CLI output
- API errors like 'API error 403' now show proper error message instead of 'No price data available'
- Updated all command execution methods to use the helper
2026-04-13 23:55:51 +00:00
shokollm
2c3b6ef073 fix: Show token name and ticker in backtest result
- Added _get_token_info helper to fetch symbol and name
- Backtest result now shows: **PEPE** (Pepe) -
2026-04-13 23:37:17 +00:00
shokollm
613ec0dc1f fix: Provide default empty string for backtest/simulate calls
- Fixed missing message argument when calling direct execution methods
- Both /backtest and /simulate now work without arguments
2026-04-13 23:34:04 +00:00
shokollm
7bdd49a56c fix: Execute backtest and simulate commands directly
- Added _execute_backtest_direct() that extracts params from message or strategy config
- Added _execute_simulate_direct() that extracts params from message or strategy config
- Both commands now execute directly when called with /backtest or /simulate
- If token address is missing, asks user for the parameter
2026-04-13 23:32:08 +00:00
shokollm
e92506a787 feat: Two-step command execution flow
Commands now execute in two steps:
1. User sends /search -> acknowledge and wait for param
2. User sends 'pepe' -> auto-execute search with 'pepe'

Commands without params (/backtest, /simulate, /trending, /strategy) execute directly.

Pending commands tracked via self.pending_command state.
2026-04-13 23:23:01 +00:00
shokollm
696d3934d5 fix: Execute trending command directly when /trending is called
- Added _execute_trending method that runs the trending CLI command
- Returns formatted list of trending tokens on BSC
- Shows error message if no tokens found or command fails
2026-04-13 23:07:43 +00:00
shokollm
466fdf1fe9 fix: Fetch strategy from database when /strategy is called
- Added _get_strategy_response method to query bot's strategy_config from DB
- Shows formatted strategy with conditions, actions, and risk management
- Shows helpful message if no strategy is configured yet
2026-04-13 23:02:49 +00:00
shokollm
39a27caf05 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
2026-04-13 16:21:57 +00:00
shokollm
61b9da295b feat: Add /trending tool for popular tokens 2026-04-13 13:51:17 +00:00
shokollm
38e45b9fd0 fix: Use 'Commands:' instead of 'Usage:' to match issue spec 2026-04-13 13:48:17 +00:00
shokollm
e41d07486b feat: Add slash command help system for conversational interface
Implement slash command help system as described in issue #57:

- Add Tool Registry in backend with metadata for all available tools
- Add command parser for '/' prefix in ConversationalAgent
- Add slash command handling functions:
  - '/' shows list of all available tools
  - '/help' shows general help about Randebu
  - '/<tool-name>' shows detailed help for specific tool
- Update frontend ChatInterface to detect '/' and show formatted help dropdown
- Add keyboard navigation (Arrow keys, Tab, Enter, Escape) for slash menu
2026-04-13 13:05:08 +00:00
2 changed files with 1384 additions and 40 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,35 @@
import type { ChatMessage } from '$lib/stores/chatStore'; import type { ChatMessage } from '$lib/stores/chatStore';
import { parseMarkdown, parseInlineElements, type InlineSegment } from '$lib/utils/markdown'; import { parseMarkdown, parseInlineElements, type InlineSegment } from '$lib/utils/markdown';
interface ToolItem {
name: string;
description: string;
command: string;
}
const TOOLS: { category: string; label: string; tools: ToolItem[] }[] = [
{
category: 'randebu',
label: '🤖 Randebu Built-in',
tools: [
{ name: 'backtest', description: 'Run strategy backtest', command: '/backtest' },
{ name: 'simulate', description: 'Start/stop simulation', command: '/simulate' },
{ name: 'strategy', description: 'View/update strategy', command: '/strategy' },
]
},
{
category: 'ave',
label: '☁️ AVE Cloud Skills',
tools: [
{ name: 'search', description: 'Token search', command: '/search' },
{ name: 'trending', description: 'Popular tokens', command: '/trending' },
{ name: 'risk', description: 'Honeypot detection', command: '/risk' },
{ name: 'token', description: 'Token details', command: '/token' },
{ name: 'price', description: 'Batch prices', command: '/price' },
]
}
];
interface Props { interface Props {
bot: Bot | null; bot: Bot | null;
messages: ChatMessage[]; messages: ChatMessage[];
@@ -26,9 +55,16 @@
let messageInput = $state(''); let messageInput = $state('');
let chatContainer: HTMLDivElement; let chatContainer: HTMLDivElement;
let expandedThinking: Record<string, boolean> = $state({}); let expandedThinking: Record<string, boolean> = $state({});
let showSlashMenu = $state(false);
let slashMenuPosition = $state({ top: 0, left: 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;
onSendMessage(messageInput); onSendMessage(messageInput);
messageInput = ''; messageInput = '';
} }
@@ -36,7 +72,54 @@
function handleKeydown(e: KeyboardEvent) { function handleKeydown(e: KeyboardEvent) {
if (e.key === 'Enter' && !e.shiftKey) { if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault(); e.preventDefault();
handleSend(); if (showSlashMenu && filteredTools.length > 0) {
selectTool(filteredTools[selectedIndex]);
} else {
handleSend();
}
} else if (e.key === 'ArrowDown' && showSlashMenu) {
e.preventDefault();
selectedIndex = Math.min(selectedIndex + 1, filteredTools.length - 1);
} else if (e.key === 'ArrowUp' && showSlashMenu) {
e.preventDefault();
selectedIndex = Math.max(selectedIndex - 1, 0);
} else if (e.key === 'Escape' && showSlashMenu) {
showSlashMenu = false;
} else if (e.key === 'Tab' && showSlashMenu && filteredTools.length > 0) {
e.preventDefault();
selectTool(filteredTools[selectedIndex]);
}
}
function handleInput(e: Event) {
const target = e.target as HTMLTextAreaElement;
const value = target.value;
messageInput = value;
if (value.startsWith('/')) {
selectedIndex = 0;
showSlashMenu = filteredTools.length > 0;
if (showSlashMenu) {
// Position menu above the textarea
const rect = target.getBoundingClientRect();
const menuHeight = 300;
slashMenuPosition = {
top: Math.max(10, rect.top - menuHeight),
left: rect.left
};
}
} else {
showSlashMenu = false;
}
}
function selectTool(tool: ToolItem) {
messageInput = tool.command + ' ';
showSlashMenu = false;
const textarea = document.querySelector('.input-container textarea') as HTMLTextAreaElement;
if (textarea) {
textarea.focus();
} }
} }
@@ -74,8 +157,17 @@
} }
}).join(''); }).join('');
} }
function handleClickOutside(e: MouseEvent) {
const target = e.target as HTMLElement;
if (!target.closest('.slash-menu') && !target.closest('.input-container textarea')) {
showSlashMenu = false;
}
}
</script> </script>
<svelte:window on:click={handleClickOutside} />
<div class="chat-interface"> <div class="chat-interface">
{#if showBotSelector && availableBots.length > 0} {#if showBotSelector && availableBots.length > 0}
<div class="bot-selector"> <div class="bot-selector">
@@ -215,10 +307,32 @@
{#if bot} {#if bot}
<div class="input-container"> <div class="input-container">
{#if showSlashMenu && filteredTools.length > 0}
<div class="slash-menu" style="top: {slashMenuPosition.top}px; left: {slashMenuPosition.left}px;">
<div class="slash-menu-header">Available Commands</div>
{#each TOOLS as group}
{#if group.tools.some(t => filteredTools.includes(t))}
<div class="slash-menu-category">{group.label}</div>
{#each group.tools.filter(t => filteredTools.includes(t)) as tool, i}
<button
class="slash-menu-item"
class:selected={filteredTools.indexOf(tool) === selectedIndex}
onclick={() => selectTool(tool)}
>
<span class="slash-command">{tool.command}</span>
<span class="slash-description">{tool.description}</span>
</button>
{/each}
{/if}
{/each}
<div class="slash-menu-hint">Press Tab to select, Enter to send</div>
</div>
{/if}
<textarea <textarea
bind:value={messageInput} value={messageInput}
oninput={handleInput}
onkeydown={handleKeydown} onkeydown={handleKeydown}
placeholder="Describe your trading strategy..." placeholder="Describe your trading strategy... (or type / for commands)"
rows="1" rows="1"
></textarea> ></textarea>
<button onclick={handleSend}> <button onclick={handleSend}>
@@ -555,4 +669,76 @@
opacity: 0.5; opacity: 0.5;
cursor: not-allowed; cursor: not-allowed;
} }
.slash-menu {
position: fixed;
background: rgba(20, 20, 20, 0.98);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 12px;
padding: 0.5rem;
min-width: 280px;
max-width: 400px;
max-height: 300px;
overflow-y: auto;
z-index: 1000;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
}
.slash-menu-header {
font-size: 0.75rem;
color: #888;
padding: 0.5rem 0.75rem;
text-transform: uppercase;
letter-spacing: 0.5px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
margin-bottom: 0.5rem;
}
.slash-menu-category {
font-size: 0.75rem;
color: #666;
padding: 0.5rem 0.75rem 0.25rem;
}
.slash-menu-item {
display: flex;
flex-direction: column;
align-items: flex-start;
width: 100%;
padding: 0.5rem 0.75rem;
background: transparent;
border: none;
border-radius: 8px;
cursor: pointer;
text-align: left;
transition: background 0.15s;
margin: 0.15rem 0;
}
.slash-menu-item:hover,
.slash-menu-item.selected {
background: rgba(102, 126, 234, 0.2);
}
.slash-command {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 0.9rem;
color: #667eea;
font-weight: 500;
}
.slash-description {
font-size: 0.8rem;
color: #888;
margin-top: 0.15rem;
}
.slash-menu-hint {
font-size: 0.7rem;
color: #555;
padding: 0.5rem 0.75rem;
border-top: 1px solid rgba(255, 255, 255, 0.1);
margin-top: 0.5rem;
text-align: center;
}
</style> </style>