mirror of
https://github.com/ParisNeo/lollms_hub.git
synced 2026-05-04 03:01:01 -04:00
This commit introduces the foundational structure and routes for the administrative interface, including necessary API endpoints, core memory management services, database models, agent node logic, and the corresponding admin dashboard templates. Key changes include: - Updating API routes for administration, data stores, and playground features. - Implementing core memory management and asset handling. - Defining database models, sessions, and migrations. - Establishing the structure for agent components and administrative templates.
518 lines
29 KiB
HTML
518 lines
29 KiB
HTML
{% extends "admin/base.html" %}
|
|
{% block title %}Tools Library{% endblock %}
|
|
{% block header_title %}Tools Library (Python Native){% endblock %}
|
|
|
|
{% block content %}
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
<div class="space-y-6 h-[80vh] flex flex-col">
|
|
<!-- Header Controls -->
|
|
<div class="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 bg-black/20 p-4 rounded-xl border border-white/10 flex-shrink-0">
|
|
<div class="flex gap-2 items-center">
|
|
<button onclick="createNewTool()" class="bg-sky-600 hover:bg-sky-500 px-6 py-2 rounded font-black text-xs uppercase tracking-widest shadow-[0_0_15px_rgba(14,165,233,0.4)] transition-all">
|
|
+ New Toolset
|
|
</button>
|
|
<div class="h-6 w-px bg-white/10 mx-2"></div>
|
|
<button onclick="buildToolWithAI()" class="bg-purple-600 hover:bg-purple-500 px-6 py-2 rounded font-black text-xs uppercase tracking-widest transition-colors">
|
|
✨ Build with AI
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main Workspace -->
|
|
<div class="flex-grow grid grid-cols-1 lg:grid-cols-4 gap-6 min-h-0 h-full">
|
|
<!-- Sidebar: Tool List -->
|
|
<div class="lg:col-span-1 flex flex-col gap-4 min-h-0 h-full">
|
|
<div class="card-style bg-black/40 border-white/5 flex flex-col h-full p-4 overflow-hidden">
|
|
<div class="flex justify-between items-center mb-4 flex-shrink-0">
|
|
<h3 class="text-[10px] font-black text-gray-500 uppercase tracking-widest flex items-center gap-2">
|
|
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>
|
|
Local Toolsets
|
|
</h3>
|
|
<button onclick="loadTools()" class="text-[9px] text-sky-400 hover:text-white uppercase font-bold">Refresh</button>
|
|
</div>
|
|
<div id="tools-list" class="space-y-1 overflow-y-auto custom-scrollbar flex-grow pr-2"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main Editor View -->
|
|
<div class="lg:col-span-3 flex flex-col h-full min-h-0">
|
|
<div id="editor-container" class="hidden card-style border-sky-500/30 bg-[#0a0a0f] flex flex-col h-full p-0 overflow-hidden">
|
|
<div class="flex justify-between items-center p-4 border-b border-white/10 flex-shrink-0 bg-black/20">
|
|
<div class="flex items-center gap-3">
|
|
<h3 class="text-white font-bold text-sm tracking-tight" id="current-filename"></h3>
|
|
<span id="t-lib-name" class="text-[10px] bg-sky-500/20 text-sky-300 px-2 py-0.5 rounded font-black uppercase"></span>
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<div class="flex bg-white/5 rounded-lg p-0.5 mr-2">
|
|
<button onclick="editor.undo()" class="p-1.5 hover:bg-white/10 rounded text-gray-400 hover:text-white transition-all" title="Undo (Ctrl+Z)">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"></path></svg>
|
|
</button>
|
|
<button onclick="editor.redo()" class="p-1.5 hover:bg-white/10 rounded text-gray-400 hover:text-white transition-all" title="Redo (Ctrl+Y)">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 10h-10a8 8 0 00-8 8v2M21 10l-6 6m6-6l-6-6"></path></svg>
|
|
</button>
|
|
</div>
|
|
<button onclick="initToolLibrary()" class="text-[10px] font-bold bg-white/5 hover:bg-indigo-600/20 text-indigo-400 px-3 py-1.5 rounded border border-indigo-500/20 uppercase transition-all">Init Deps</button>
|
|
<button id="btn-save" onclick="saveCurrentTool()" class="text-[10px] font-black bg-sky-600 hover:bg-sky-500 px-4 py-1.5 rounded text-white uppercase tracking-widest">Save & Deploy</button>
|
|
<button onclick="deleteCurrentTool()" class="text-[10px] font-bold text-red-400 hover:text-red-300 uppercase">Delete</button>
|
|
</div>
|
|
</div>
|
|
<!-- Unified flex container for Editor & Console -->
|
|
<div class="flex-grow flex flex-col min-h-0 bg-[#030305]">
|
|
<div class="flex-grow relative overflow-hidden border-b border-white/5">
|
|
<div id="code-editor-container" class="absolute inset-0"></div>
|
|
</div>
|
|
|
|
<!-- Test Console Section -->
|
|
<div id="test-console" class="h-64 flex flex-col bg-black/40 border-t border-white/10 flex-shrink-0">
|
|
<div class="p-2 px-4 bg-black/20 border-b border-white/5 flex justify-between items-center">
|
|
<span class="text-[10px] font-black text-gray-500 uppercase tracking-widest">Interactive Test Console</span>
|
|
<div class="flex gap-2">
|
|
<button onclick="fixWithAI()" class="text-[9px] font-bold text-purple-400 hover:text-purple-300 uppercase">✨ Fix with AI</button>
|
|
<button onclick="clearConsole()" class="text-[9px] font-bold text-gray-600 hover:text-white uppercase">Clear</button>
|
|
</div>
|
|
</div>
|
|
<div class="flex-grow flex p-2 gap-4 overflow-hidden">
|
|
<div class="w-1/3 flex flex-col gap-2">
|
|
<textarea id="test-prompt" class="flex-grow bg-black/60 border border-white/10 rounded-lg p-2 text-xs text-indigo-200 outline-none focus:border-indigo-500 custom-scrollbar" placeholder="Ask AI to test the tool... (e.g. 'Search for Einstein on Wikipedia')"></textarea>
|
|
<button onclick="runTest()" id="btn-test" class="bg-indigo-600 hover:bg-indigo-500 text-white font-black text-[10px] uppercase py-2 rounded-lg transition-all">Execute Test Call</button>
|
|
</div>
|
|
<div id="console-output" class="flex-grow bg-black/80 rounded-lg p-3 font-mono text-[11px] text-emerald-400 overflow-y-auto custom-scrollbar whitespace-pre-wrap">
|
|
<span class="text-gray-600 italic">Console ready. Deploy your tool and run a test.</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div id="empty-state" class="card-style border-dashed border-white/10 flex flex-col items-center justify-center h-full text-center p-8">
|
|
<h3 class="text-xl font-bold text-white mb-2">Tools Studio</h3>
|
|
<p class="text-sm text-gray-400 max-w-md">Select a library from the sidebar to modify Python tool logic, or use the AI builder to scaffold new capabilities.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<template id="ai-tool-builder-template">
|
|
<div class="space-y-4">
|
|
<textarea id="build-tool-prompt" class="w-full h-40 p-3 bg-black/40 border border-white/10 rounded-xl text-sm text-white focus:border-indigo-500 outline-none" placeholder="e.g. Create a tool library to interact with Spotify to search tracks..."></textarea>
|
|
|
|
<div class="flex flex-col gap-2">
|
|
<span class="text-[10px] font-black text-gray-500 uppercase tracking-widest px-1">Attached Context</span>
|
|
<div id="m-previews" class="flex flex-wrap gap-2 min-h-[32px] p-2 bg-black/20 border border-white/5 rounded-lg"></div>
|
|
</div>
|
|
|
|
<div class="flex gap-2">
|
|
<button type="button" onclick="document.getElementById('m-img-input').click()" class="flex items-center gap-2 text-[10px] font-bold py-2 px-3 bg-white/5 rounded-lg border border-white/10 hover:bg-white/10">📷 Images</button>
|
|
<button type="button" onclick="document.getElementById('m-doc-input').click()" class="flex items-center gap-2 text-[10px] font-bold py-2 px-3 bg-white/5 rounded-lg border border-white/10 hover:bg-white/10">📄 Docs</button>
|
|
<button type="button" onclick="window.triggerToolBuildWebImport()" class="flex items-center gap-2 text-[10px] font-bold py-2 px-3 bg-indigo-600/20 text-indigo-300 rounded-lg border border-indigo-500/30 hover:bg-indigo-600/30 transition-all">🌍 Web Import</button>
|
|
</div>
|
|
|
|
<input type="file" id="m-img-input" class="hidden" multiple accept="image/*">
|
|
<input type="file" id="m-doc-input" class="hidden" multiple accept=".txt,.md,.pdf,.csv">
|
|
|
|
<div id="build-tool-status" class="hidden bg-black/60 border border-white/10 rounded-lg p-3 h-24 overflow-y-auto font-mono text-[10px] text-gray-400 space-y-1"></div>
|
|
<label class="flex items-center gap-2 cursor-pointer group px-1">
|
|
<input type="checkbox" id="shl-build-tool" class="h-4 w-4 rounded text-sky-600 bg-black/40 border-white/10">
|
|
<span class="text-[10px] font-black text-gray-500 uppercase tracking-widest group-hover:text-sky-400 transition-colors">Verify Code & Auto-Fix Bugs</span>
|
|
</label>
|
|
<button onclick="window.executeToolBuild()" id="tool-build-run-btn" class="w-full py-3 bg-indigo-600 hover:bg-indigo-500 rounded-xl font-black text-xs uppercase tracking-widest transition-all">Generate Implementation</button>
|
|
</div>
|
|
</template>
|
|
|
|
<template id="ai-tool-fix-template">
|
|
<div class="space-y-4">
|
|
<div class="p-3 bg-indigo-500/10 border border-indigo-500/30 rounded-lg">
|
|
<h4 class="text-[10px] font-black text-indigo-400 uppercase tracking-widest mb-1">Contextual Repair</h4>
|
|
<p class="text-[11px] text-gray-400 leading-relaxed">The AI will review the <b>Current Source Code</b> and the <b>Console Output</b>. Add instructions or references below to guide the fix.</p>
|
|
</div>
|
|
|
|
<textarea id="fix-tool-prompt" class="w-full h-32 p-3 bg-black/40 border border-white/10 rounded-xl text-sm text-white focus:border-indigo-500 outline-none" placeholder="e.g. Add a timeout to the request, or use the provided API documentation..."></textarea>
|
|
|
|
<div class="flex flex-col gap-2">
|
|
<span class="text-[10px] font-black text-gray-500 uppercase tracking-widest px-1">Attached References</span>
|
|
<div id="f-previews" class="flex flex-wrap gap-2 min-h-[32px] p-2 bg-black/20 border border-white/5 rounded-lg"></div>
|
|
</div>
|
|
|
|
<div class="flex gap-2">
|
|
<button type="button" onclick="document.getElementById('f-img-input').click()" class="flex items-center gap-2 text-[10px] font-bold py-2 px-3 bg-white/5 rounded-lg border border-white/10 hover:bg-white/10 transition-colors">📷 Images</button>
|
|
<button type="button" onclick="document.getElementById('f-doc-input').click()" class="flex items-center gap-2 text-[10px] font-bold py-2 px-3 bg-white/5 rounded-lg border border-white/10 hover:bg-white/10 transition-colors">📄 Docs</button>
|
|
<button type="button" onclick="window.triggerToolFixWebImport()" class="flex items-center gap-2 text-[10px] font-bold py-2 px-3 bg-indigo-600/20 text-indigo-300 rounded-lg border border-indigo-500/30 hover:bg-indigo-600/30 transition-all">🌍 Web Import</button>
|
|
</div>
|
|
|
|
<input type="file" id="f-img-input" class="hidden" multiple accept="image/*">
|
|
<input type="file" id="f-doc-input" class="hidden" multiple accept=".txt,.md,.pdf,.csv">
|
|
|
|
<div id="fix-tool-status" class="hidden bg-black/60 border border-white/10 rounded-lg p-3 h-24 overflow-y-auto font-mono text-[10px] text-gray-400 space-y-1"></div>
|
|
<button onclick="window.executeToolFix()" id="tool-fix-run-btn" class="w-full py-3 bg-purple-600 hover:bg-purple-500 rounded-xl font-black text-xs uppercase tracking-widest transition-all">Repair Logic</button>
|
|
</div>
|
|
</template>
|
|
|
|
<link rel="stylesheet" href="{{ url_for('static', path='vendor/codemirror.min.css') }}">
|
|
<link rel="stylesheet" href="{{ url_for('static', path='vendor/codemirror-monokai.min.css') }}">
|
|
<script src="{{ url_for('static', path='vendor/codemirror.min.js') }}"></script>
|
|
<script src="{{ url_for('static', path='vendor/codemirror-python.min.js') }}"></script>
|
|
|
|
<script>
|
|
let currentFile = null;
|
|
let allTools = [];
|
|
let editor = null;
|
|
let testAbortController = null;
|
|
let isTesting = false;
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
loadTools();
|
|
editor = CodeMirror(document.getElementById('code-editor-container'), {
|
|
mode: 'python',
|
|
theme: 'monokai',
|
|
lineNumbers: true,
|
|
lineWrapping: true,
|
|
viewportMargin: Infinity // Helps with internal scroll calculation
|
|
});
|
|
|
|
// Proper sizing for CodeMirror in absolute container
|
|
editor.setSize("100%", "100%");
|
|
|
|
const sse = new EventSource("{{ url_for('sse_events') }}");
|
|
sse.onmessage = (e) => {
|
|
const msg = JSON.parse(e.data);
|
|
const box = document.getElementById('build-tool-status');
|
|
if (box && msg.id?.startsWith('sys_build_tool_') && msg.error) {
|
|
const entry = document.createElement('div');
|
|
entry.className = msg.type === 'error' ? 'text-red-400' : 'text-indigo-300';
|
|
entry.innerHTML = `<span class="opacity-50">[${new Date().toLocaleTimeString()}]</span> ${msg.error}`;
|
|
box.appendChild(entry); box.scrollTop = box.scrollHeight;
|
|
box.classList.remove('hidden');
|
|
}
|
|
};
|
|
});
|
|
|
|
async function loadTools() {
|
|
const resp = await fetch("{{ url_for('api_get_tools') }}");
|
|
allTools = await resp.json();
|
|
renderToolsList();
|
|
}
|
|
|
|
function renderToolsList() {
|
|
const listEl = document.getElementById('tools-list');
|
|
listEl.innerHTML = allTools.map(t => `
|
|
<div class="w-full text-left p-3 rounded-lg hover:bg-white/5 group border-b border-white/5 transition-all mb-1 flex items-center justify-between ${currentFile === t.filename ? 'bg-sky-500/10 border-l-2 border-l-sky-500' : ''}">
|
|
<button onclick="openTool('${t.filename}')" class="flex-grow text-left min-w-0 flex items-center gap-3">
|
|
<span class="text-lg flex-shrink-0">${t.icon || '🔧'}</span>
|
|
<div class="min-w-0">
|
|
<div class="text-xs font-bold text-white group-hover:text-sky-400 truncate">${t.name}</div>
|
|
<div class="text-[9px] text-gray-500 group-hover:text-gray-300 line-clamp-1 leading-tight">${t.description}</div>
|
|
</div>
|
|
</button>
|
|
<div class="flex items-center opacity-0 group-hover:opacity-100 transition-opacity flex-shrink-0 ml-2">
|
|
<button onclick="renameTool('${t.filename}')" class="p-1 text-gray-500 hover:text-indigo-400" title="Rename File">
|
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.5L15.232 5.232z"></path></svg>
|
|
</button>
|
|
<button onclick="deleteTool('${t.filename}')" class="p-1 text-gray-500 hover:text-red-400" title="Delete Library">
|
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
window.openTool = (filename) => {
|
|
const tool = allTools.find(t => t.filename === filename);
|
|
if (!tool) return;
|
|
currentFile = filename;
|
|
document.getElementById('current-filename').textContent = filename;
|
|
document.getElementById('t-lib-name').textContent = tool.name;
|
|
|
|
editor.setValue(tool.raw);
|
|
document.getElementById('empty-state').classList.add('hidden');
|
|
document.getElementById('editor-container').classList.remove('hidden');
|
|
|
|
// Force refresh CodeMirror layout
|
|
setTimeout(() => editor.refresh(), 50);
|
|
renderToolsList();
|
|
};
|
|
|
|
window.saveCurrentTool = async () => {
|
|
const content = editor.getValue();
|
|
const btn = document.getElementById('btn-save');
|
|
const originalText = btn.innerText;
|
|
|
|
btn.disabled = true;
|
|
btn.innerText = "DEPLOYING...";
|
|
|
|
const formData = new FormData();
|
|
formData.append("filename", currentFile);
|
|
formData.append("content", content);
|
|
formData.append("csrf_token", "{{ csrf_token }}");
|
|
|
|
try {
|
|
const resp = await fetch("{{ url_for('api_save_tool') }}", { method: "POST", body: formData });
|
|
const result = await resp.json();
|
|
|
|
if (resp.ok && result.success) {
|
|
btn.innerText = "SUCCESS ✓";
|
|
btn.classList.replace('bg-sky-600', 'bg-green-600');
|
|
|
|
// IMMEDIATE UI UPDATE: Update local model name without reload
|
|
if (result.meta) {
|
|
document.getElementById('t-lib-name').textContent = result.meta.name;
|
|
const toolIdx = allTools.findIndex(t => t.filename === currentFile);
|
|
if (toolIdx !== -1) {
|
|
allTools[toolIdx].name = result.meta.name;
|
|
allTools[toolIdx].description = result.meta.description;
|
|
allTools[toolIdx].icon = result.meta.icon;
|
|
allTools[toolIdx].raw = content;
|
|
renderToolsList();
|
|
}
|
|
}
|
|
|
|
setTimeout(() => {
|
|
btn.innerText = originalText;
|
|
btn.classList.replace('bg-green-600', 'bg-sky-600');
|
|
btn.disabled = false;
|
|
}, 2000);
|
|
}
|
|
} catch(e) {
|
|
alert("Save failed: " + e.message);
|
|
btn.disabled = false;
|
|
btn.innerText = originalText;
|
|
}
|
|
};
|
|
|
|
window.renameTool = async (filename) => {
|
|
const oldName = filename;
|
|
const newName = prompt("Enter new filename (including .py):", oldName);
|
|
if (!newName || newName === oldName) return;
|
|
|
|
const formData = new FormData();
|
|
formData.append("old_filename", oldName);
|
|
formData.append("new_filename", newName);
|
|
formData.append("csrf_token", "{{ csrf_token }}");
|
|
|
|
try {
|
|
const resp = await fetch("{{ url_for('api_rename_tool') }}", {
|
|
method: "POST",
|
|
body: formData
|
|
});
|
|
const data = await resp.json();
|
|
if (data.success) {
|
|
currentFile = data.new_filename;
|
|
await loadTools();
|
|
openTool(currentFile);
|
|
} else {
|
|
alert("Rename failed: " + (data.error || data.detail));
|
|
}
|
|
} catch(e) { console.error(e); alert("Network error during rename."); }
|
|
};
|
|
|
|
window.initToolLibrary = async () => {
|
|
const btn = event.target;
|
|
const old = btn.innerText;
|
|
btn.innerText = "INITIALIZING...";
|
|
btn.disabled = true;
|
|
|
|
const formData = new FormData();
|
|
formData.append("filename", currentFile);
|
|
formData.append("csrf_token", "{{ csrf_token }}");
|
|
|
|
try {
|
|
const resp = await fetch("{{ url_for('api_init_tool') }}", { method: 'POST', body: formData });
|
|
const data = await resp.json();
|
|
logToConsole(data.message || data.error, data.error ? 'text-red-400' : 'text-indigo-400');
|
|
} catch(e) { logToConsole("Network error: " + e.message, 'text-red-400'); }
|
|
finally { btn.innerText = old; btn.disabled = false; }
|
|
};
|
|
|
|
window.runTest = async () => {
|
|
const btn = document.getElementById('btn-test');
|
|
|
|
// --- STOP LOGIC ---
|
|
if (isTesting && testAbortController) {
|
|
testAbortController.abort();
|
|
return;
|
|
}
|
|
|
|
const prompt = document.getElementById('test-prompt').value;
|
|
const out = document.getElementById('console-output');
|
|
|
|
if (!prompt.trim()) return alert("Enter a test prompt.");
|
|
|
|
// --- START LOGIC ---
|
|
isTesting = true;
|
|
testAbortController = new AbortController();
|
|
|
|
btn.innerHTML = `<svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-white inline" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg> STOP`;
|
|
btn.classList.replace('bg-indigo-600', 'bg-red-600');
|
|
btn.classList.replace('hover:bg-indigo-500', 'hover:bg-red-500');
|
|
|
|
out.innerHTML = "";
|
|
logToConsole(`🚀 [DISPATCH] Initiating AI Test Request: "${prompt}"`, 'text-indigo-400 font-bold');
|
|
|
|
const formData = new FormData();
|
|
formData.append("filename", currentFile);
|
|
formData.append("user_prompt", prompt);
|
|
formData.append("csrf_token", "{{ csrf_token }}");
|
|
|
|
try {
|
|
const resp = await fetch("{{ url_for('api_test_tool') }}", {
|
|
method: 'POST',
|
|
body: formData,
|
|
signal: testAbortController.signal
|
|
});
|
|
const data = await resp.json();
|
|
|
|
if (data.success) {
|
|
if (data.ai_response && data.ai_response.content) {
|
|
logToConsole(`🤖 [AI THOUGHT]: ${data.ai_response.content}`, 'text-gray-400 italic');
|
|
}
|
|
|
|
if (data.executions && data.executions.length > 0) {
|
|
data.executions.forEach((ex, i) => {
|
|
const fn = ex.call.function;
|
|
logToConsole(`\n🛠️ [EXECUTE #${i+1}] Calling ${fn.name}(${JSON.stringify(fn.arguments)})`, 'text-amber-400 font-black');
|
|
|
|
const isError = (typeof ex.output === 'object' && ex.output.error) || (typeof ex.output === 'string' && ex.output.includes('Error:'));
|
|
const outColor = isError ? 'text-red-400 bg-red-950/20 p-2 rounded border border-red-500/30' : 'text-emerald-400';
|
|
|
|
if (ex.logs) {
|
|
logToConsole(`📖 [INTERNAL LOGS]:`, 'text-indigo-500 font-bold');
|
|
logToConsole(ex.logs, 'text-indigo-300 text-[10px] bg-white/5 p-2 rounded');
|
|
}
|
|
|
|
logToConsole(`📥 [RESULT]:`, 'text-gray-500 underline');
|
|
logToConsole(typeof ex.output === 'object' ? JSON.stringify(ex.output, null, 2) : ex.output, outColor);
|
|
});
|
|
} else {
|
|
logToConsole("⚠️ [NOTICE]: AI did not trigger any tool calls. Try adjusting the prompt.", 'text-amber-500 font-bold');
|
|
}
|
|
} else {
|
|
logToConsole("🛑 [SYSTEM ERROR]:", 'text-red-500 font-black');
|
|
logToConsole(data.error, 'text-red-300');
|
|
}
|
|
} catch(e) {
|
|
if (e.name === 'AbortError') {
|
|
logToConsole("🛑 [CANCELLED]: User stopped execution.", 'text-amber-500 italic');
|
|
} else {
|
|
logToConsole("💥 [NETWORK ERROR]: " + e.message, 'text-red-500');
|
|
}
|
|
} finally {
|
|
isTesting = false;
|
|
testAbortController = null;
|
|
btn.innerHTML = `Execute Test Call`;
|
|
btn.classList.replace('bg-red-600', 'bg-indigo-600');
|
|
btn.classList.replace('hover:bg-red-500', 'hover:bg-indigo-500');
|
|
}
|
|
};
|
|
|
|
window.fixWithAI = () => {
|
|
const template = document.getElementById('ai-tool-fix-template');
|
|
window.showModal("AI Tool Repair Architect", template.innerHTML);
|
|
|
|
const promptEl = document.getElementById('fix-tool-prompt');
|
|
if (promptEl) promptEl.value = window.Architect.prompt;
|
|
|
|
document.getElementById('f-img-input').onchange = (e) => window.Architect.handleFiles(e.target.files, () => window.Architect.renderPreviews('f-previews'));
|
|
document.getElementById('f-doc-input').onchange = (e) => window.Architect.handleFiles(e.target.files, () => window.Architect.renderPreviews('f-previews'));
|
|
|
|
window.Architect.renderPreviews('f-previews');
|
|
};
|
|
|
|
window.triggerToolFixWebImport = () => {
|
|
window.Architect.triggerWebImport('fix-tool-prompt', window.fixWithAI);
|
|
};
|
|
|
|
window.executeToolFix = () => {
|
|
const consoleOutput = document.getElementById('console-output').innerText;
|
|
|
|
window.Architect.executeBuild(
|
|
'fix-tool-prompt',
|
|
"{{ url_for('api_fix_tool') }}",
|
|
'fix-tool-status',
|
|
'tool-fix-run-btn',
|
|
(data) => {
|
|
if (data.fixed_code) {
|
|
// Use replaceRange to preserve history for undo
|
|
const totalLines = editor.lineCount();
|
|
editor.replaceRange(data.fixed_code, {line: 0, ch: 0}, {line: totalLines});
|
|
logToConsole("✅ [REPAIR APPLIED]: Logic updated. Review and Save.", 'text-fuchsia-400 font-bold');
|
|
}
|
|
}
|
|
);
|
|
|
|
// Inject the console log into the prompt context via the form data override
|
|
const originalPrepare = window.Architect.prepareFormData;
|
|
window.Architect.prepareFormData = function(id) {
|
|
const fd = originalPrepare.call(this, id);
|
|
fd.append("filename", currentFile);
|
|
fd.append("error_log", consoleOutput);
|
|
return fd;
|
|
};
|
|
};
|
|
|
|
function logToConsole(text, colorClass) {
|
|
const out = document.getElementById('console-output');
|
|
const div = document.createElement('div');
|
|
div.className = colorClass + " mb-2";
|
|
div.innerText = text;
|
|
out.appendChild(div);
|
|
out.scrollTop = out.scrollHeight;
|
|
}
|
|
|
|
window.clearConsole = () => {
|
|
document.getElementById('console-output').innerHTML = '<span class="text-gray-600 italic">Console cleared.</span>';
|
|
};
|
|
|
|
window.deleteTool = async (filename) => {
|
|
if(!confirm(`Permanently delete '${filename}'?`)) return;
|
|
|
|
try {
|
|
const resp = await fetch(`/api/v1/api/tools/${filename}`, {
|
|
method: "DELETE",
|
|
headers: { "X-CSRF-Token": "{{ csrf_token }}" }
|
|
});
|
|
if (resp.ok) {
|
|
if (currentFile === filename) {
|
|
currentFile = null;
|
|
document.getElementById('editor-container').classList.add('hidden');
|
|
document.getElementById('empty-state').classList.remove('hidden');
|
|
}
|
|
await loadTools();
|
|
}
|
|
} catch(e) { alert("Delete failed"); }
|
|
};
|
|
|
|
// Required wrapper to prevent breaking button in deleteTool above
|
|
window.deleteCurrentTool = () => deleteTool(currentFile);
|
|
|
|
window.createNewTool = () => {
|
|
currentFile = "new_toolset.py";
|
|
document.getElementById('current-filename').textContent = currentFile;
|
|
document.getElementById('t-lib-name').textContent = "Draft Toolset";
|
|
editor.setValue("TOOL_LIBRARY_NAME = 'New Toolset'\nTOOL_LIBRARY_DESC = '...'\nTOOL_LIBRARY_ICON = '🔧'\n\ndef init_tool_library():\n '''Initialize dependencies using pipmaster'''\n import pipmaster as pm\n # pm.ensure_packages({'requests':'>=2.0.0'})\n\ndef tool_example(args: dict):\n '''\n Example Tool Description\n \n Args:\n args: dict with keys:\n - query (str): search term\n '''\n try:\n return f\"Processed: {args.get('query')}\"\n except Exception as e:\n return f\"Error: {str(e)}\"");
|
|
document.getElementById('empty-state').classList.add('hidden');
|
|
document.getElementById('editor-container').classList.remove('hidden');
|
|
setTimeout(() => editor.refresh(), 50);
|
|
};
|
|
|
|
window.buildToolWithAI = () => {
|
|
const template = document.getElementById('ai-tool-builder-template');
|
|
window.showModal("Architect Tool with AI", template.innerHTML);
|
|
|
|
const promptEl = document.getElementById('build-tool-prompt');
|
|
if (promptEl) promptEl.value = window.Architect.prompt;
|
|
|
|
document.getElementById('m-img-input').onchange = (e) => window.Architect.handleFiles(e.target.files, () => window.Architect.renderPreviews('m-previews'));
|
|
document.getElementById('m-doc-input').onchange = (e) => window.Architect.handleFiles(e.target.files, () => window.Architect.renderPreviews('m-previews'));
|
|
|
|
window.Architect.renderPreviews('m-previews');
|
|
};
|
|
|
|
window.triggerToolBuildWebImport = () => {
|
|
window.Architect.triggerWebImport('build-tool-prompt', window.buildToolWithAI);
|
|
};
|
|
|
|
window.executeToolBuild = () => {
|
|
window.Architect.executeBuild('build-tool-prompt', "{{ url_for('api_build_tool') }}", 'build-tool-status', 'tool-build-run-btn', (data) => {
|
|
loadTools().then(() => openTool(data.filename));
|
|
});
|
|
};
|
|
</script>
|
|
{% endblock %} |