- 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.
287 lines
8.4 KiB
HTML
287 lines
8.4 KiB
HTML
<!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>
|