feat: Implement Trillium notes executor for searching and creating notes via ETAPI
- Added `trillium.py` for searching and creating notes with Trillium's ETAPI. - Implemented `search_notes` and `create_note` functions with appropriate error handling and validation. feat: Add web search functionality using DuckDuckGo - Introduced `web_search.py` for performing web searches without API keys. - Implemented `search_web` function with result handling and validation. feat: Create provider-agnostic function caller for iterative tool calling - Developed `function_caller.py` to manage LLM interactions with tools. - Implemented iterative calling logic with error handling and tool execution. feat: Establish a tool registry for managing available tools - Created `registry.py` to define and manage tool availability and execution. - Integrated feature flags for enabling/disabling tools based on environment variables. feat: Implement event streaming for tool calling processes - Added `stream_events.py` to manage Server-Sent Events (SSE) for tool calling. - Enabled real-time updates during tool execution for enhanced user experience. test: Add tests for tool calling system components - Created `test_tools.py` to validate functionality of code execution, web search, and tool registry. - Implemented asynchronous tests to ensure proper execution and result handling. chore: Add Dockerfile for sandbox environment setup - Created `Dockerfile` to set up a Python environment with necessary dependencies for code execution. chore: Add debug regex script for testing XML parsing - Introduced `debug_regex.py` to validate regex patterns against XML tool calls. chore: Add HTML template for displaying thinking stream events - Created `test_thinking_stream.html` for visualizing tool calling events in a user-friendly format. test: Add tests for OllamaAdapter XML parsing - Developed `test_ollama_parser.py` to validate XML parsing with various test cases, including malformed XML.
This commit is contained in:
286
test_thinking_stream.html
Normal file
286
test_thinking_stream.html
Normal file
@@ -0,0 +1,286 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Lyra - Show Your Work</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: #1a1a1a;
|
||||
color: #e0e0e0;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.panel {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #333;
|
||||
}
|
||||
|
||||
.chat-panel {
|
||||
border-right: 1px solid #333;
|
||||
}
|
||||
|
||||
.thinking-panel {
|
||||
background: #0d0d0d;
|
||||
}
|
||||
|
||||
.messages, .thinking-output {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 10px;
|
||||
background: #222;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-bottom: 15px;
|
||||
padding: 10px;
|
||||
border-radius: 6px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.user-message {
|
||||
background: #1e3a5f;
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.assistant-message {
|
||||
background: #2d2d2d;
|
||||
}
|
||||
|
||||
.thinking-event {
|
||||
margin-bottom: 10px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
font-family: 'Courier New', monospace;
|
||||
animation: fadeIn 0.3s;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(-5px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.event-thinking { background: #1a3a1a; color: #90ee90; }
|
||||
.event-tool_call { background: #3a2a1a; color: #ffa500; }
|
||||
.event-tool_result { background: #1a2a3a; color: #87ceeb; }
|
||||
.event-done { background: #2a1a3a; color: #da70d6; }
|
||||
.event-error { background: #3a1a1a; color: #ff6b6b; }
|
||||
|
||||
.input-area {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
input {
|
||||
flex: 1;
|
||||
padding: 12px;
|
||||
background: #2d2d2d;
|
||||
border: 1px solid #444;
|
||||
border-radius: 6px;
|
||||
color: #e0e0e0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 12px 24px;
|
||||
background: #4a7c59;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: #5a9c69;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
background: #333;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.status {
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.status.connected { color: #90ee90; }
|
||||
.status.disconnected { color: #ff6b6b; }
|
||||
|
||||
/* Mobile responsive */
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.chat-panel {
|
||||
border-right: none;
|
||||
border-bottom: 1px solid #333;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Chat Panel -->
|
||||
<div class="panel chat-panel">
|
||||
<div class="panel-header">💬 Chat</div>
|
||||
<div class="messages" id="messages"></div>
|
||||
<div class="input-area">
|
||||
<input
|
||||
type="text"
|
||||
id="userInput"
|
||||
placeholder="Ask something that requires tools... (e.g., 'Calculate 50/2 using Python')"
|
||||
onkeypress="if(event.key==='Enter') sendMessage()"
|
||||
>
|
||||
<button onclick="sendMessage()" id="sendBtn">Send</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Thinking Panel -->
|
||||
<div class="panel thinking-panel">
|
||||
<div class="panel-header">🧠 Show Your Work</div>
|
||||
<div class="thinking-output" id="thinking"></div>
|
||||
<div class="status" id="status">Not connected</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const SESSION_ID = 'thinking-demo-' + Date.now();
|
||||
let eventSource = null;
|
||||
|
||||
// Connect to thinking stream
|
||||
function connectThinkingStream() {
|
||||
if (eventSource) {
|
||||
eventSource.close();
|
||||
}
|
||||
|
||||
const url = `http://localhost:7081/stream/thinking/${SESSION_ID}`;
|
||||
eventSource = new EventSource(url);
|
||||
|
||||
eventSource.onopen = () => {
|
||||
document.getElementById('status').textContent = '🟢 Connected to thinking stream';
|
||||
document.getElementById('status').className = 'status connected';
|
||||
};
|
||||
|
||||
eventSource.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
addThinkingEvent(data);
|
||||
} catch (e) {
|
||||
console.error('Failed to parse event:', e);
|
||||
}
|
||||
};
|
||||
|
||||
eventSource.onerror = () => {
|
||||
document.getElementById('status').textContent = '🔴 Disconnected from thinking stream';
|
||||
document.getElementById('status').className = 'status disconnected';
|
||||
setTimeout(connectThinkingStream, 2000); // Reconnect after 2s
|
||||
};
|
||||
}
|
||||
|
||||
function addThinkingEvent(event) {
|
||||
const thinking = document.getElementById('thinking');
|
||||
const eventDiv = document.createElement('div');
|
||||
eventDiv.className = `thinking-event event-${event.type}`;
|
||||
|
||||
if (event.type === 'connected') {
|
||||
eventDiv.textContent = `✓ Connected (Session: ${event.session_id})`;
|
||||
} else if (event.data && event.data.message) {
|
||||
eventDiv.textContent = event.data.message;
|
||||
} else {
|
||||
eventDiv.textContent = JSON.stringify(event.data);
|
||||
}
|
||||
|
||||
thinking.appendChild(eventDiv);
|
||||
thinking.scrollTop = thinking.scrollHeight;
|
||||
}
|
||||
|
||||
async function sendMessage() {
|
||||
const input = document.getElementById('userInput');
|
||||
const message = input.value.trim();
|
||||
if (!message) return;
|
||||
|
||||
// Add user message to chat
|
||||
addMessage('user', message);
|
||||
input.value = '';
|
||||
|
||||
// Disable send button
|
||||
const sendBtn = document.getElementById('sendBtn');
|
||||
sendBtn.disabled = true;
|
||||
sendBtn.textContent = 'Thinking...';
|
||||
|
||||
// Clear thinking panel
|
||||
document.getElementById('thinking').innerHTML = '';
|
||||
|
||||
try {
|
||||
const response = await fetch('http://localhost:7081/simple', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
session_id: SESSION_ID,
|
||||
user_prompt: message,
|
||||
backend: 'SECONDARY'
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
addMessage('assistant', data.draft);
|
||||
|
||||
} catch (error) {
|
||||
addMessage('assistant', `Error: ${error.message}`);
|
||||
} finally {
|
||||
sendBtn.disabled = false;
|
||||
sendBtn.textContent = 'Send';
|
||||
}
|
||||
}
|
||||
|
||||
function addMessage(role, content) {
|
||||
const messages = document.getElementById('messages');
|
||||
const messageDiv = document.createElement('div');
|
||||
messageDiv.className = `message ${role}-message`;
|
||||
messageDiv.textContent = content;
|
||||
messages.appendChild(messageDiv);
|
||||
messages.scrollTop = messages.scrollHeight;
|
||||
}
|
||||
|
||||
// Connect on page load
|
||||
connectThinkingStream();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user