Newer
Older
TelosDB / src / frontend / index.js
const { invoke } = window.__TAURI__.core;
const { listen } = window.__TAURI__.event;

let activeTable = null;
let currentPage = 0;
const PAGE_SIZE = 20;

async function init() {
    await updateSystemStatus();
    await loadTables();
    setupEvents();

    setInterval(updateSystemStatus, 5000);
}

async function updateSystemStatus() {
    try {
        const sidecar = await invoke('get_sidecar_status');
        const sidecarEl = document.getElementById('sidecar-status');
        sidecarEl.textContent = sidecar ? 'Ready' : 'Down';
        sidecarEl.className = sidecar ? 'tag tag-green' : 'tag tag-orange';

        document.getElementById('mcp-status').textContent = 'Online';
        document.getElementById('mcp-status').className = 'tag tag-green';
    } catch (e) {
        console.error(e);
    }
}

async function loadTables() {
    try {
        const tables = await invoke('get_table_list');
        const list = document.getElementById('table-list');
        list.innerHTML = '';
        tables.forEach(name => {
            const li = document.createElement('li');
            li.className = 'table-item';
            li.textContent = name;
            li.onclick = () => selectTable(name);
            list.appendChild(li);
        });
    } catch (e) {
        console.error(e);
    }
}

async function selectTable(name) {
    activeTable = name;
    currentPage = 0;
    document.querySelectorAll('.table-item').forEach(el => {
        el.classList.toggle('active', el.textContent === name);
    });
    document.getElementById('current-title').textContent = `Table: ${name}`;
    document.getElementById('main-tabs').style.display = 'flex';
    document.getElementById('view-empty').style.display = 'none';

    switchTab('data');
    await loadData();
    await loadSchema();
}

async function loadData() {
    if (!activeTable) return;
    showLoading(true);
    try {
        const result = await invoke('get_table_data', {
            tableName: activeTable,
            limit: PAGE_SIZE,
            offset: currentPage * PAGE_SIZE
        });
        renderDataTable(result.data, result.total);
    } catch (e) {
        alert(`Error: ${e}`);
    } finally {
        showLoading(false);
    }
}

function renderDataTable(data, total) {
    const head = document.getElementById('data-head');
    const body = document.getElementById('data-body');
    head.innerHTML = '';
    body.innerHTML = '';

    if (data.length === 0) {
        body.innerHTML = '<tr><td colspan="100" style="text-align:center; color:var(--text-secondary); padding: 40px;">No records found.</td></tr>';
        return;
    }

    const columns = Object.keys(data[0]);
    const htr = document.createElement('tr');
    columns.forEach(col => {
        const th = document.createElement('th');
        th.textContent = col;
        htr.appendChild(th);
    });
    head.appendChild(htr);

    data.forEach(row => {
        const tr = document.createElement('tr');
        columns.forEach(col => {
            const td = document.createElement('td');
            let val = row[col];
            if (val === null) val = '-';
            td.textContent = val;
            tr.appendChild(td);
        });
        body.appendChild(tr);
    });

    const start = total === 0 ? 0 : currentPage * PAGE_SIZE + 1;
    const end = Math.min((currentPage + 1) * PAGE_SIZE, total);
    document.getElementById('pagination-info').textContent = `Showing ${start}-${end} of ${total} records`;
    document.getElementById('prev-btn').disabled = currentPage === 0;
    document.getElementById('next-btn').disabled = end >= total;
}

async function loadSchema() {
    try {
        const info = await invoke('get_table_schema', { tableName: activeTable });
        const body = document.getElementById('schema-body');
        body.innerHTML = '';
        info.forEach(col => {
            const tr = document.createElement('tr');
            tr.innerHTML = `
                <td style="font-weight:700;">${col.name}</td>
                <td><span class="tag tag-blue">${col.type}</span></td>
                <td>${col.notnull === "0" ? 'Yes' : 'No'}</td>
                <td>${col.pk !== "0" ? '🔑' : '-'}</td>
                <td style="color:var(--text-secondary);">${col.dflt_value || 'NULL'}</td>
            `;
            body.appendChild(tr);
        });
    } catch (e) {
        console.error(e);
    }
}

window.switchTab = function (type) {
    document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
    document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));

    const tab = document.getElementById(`tab-${type}`);
    if (tab) tab.classList.add('active');

    const view = document.getElementById(`view-${type}`);
    if (view) view.classList.add('active');
};

window.changePage = async function (delta) {
    currentPage += delta;
    await loadData();
};

window.executeVectorSearch = async function () {
    const text = document.getElementById('vector-input').value;
    const limit = parseInt(document.getElementById('vector-limit').value);
    if (!text.trim()) return;

    showLoading(true);
    try {
        const results = await invoke('vector_search_text', { text, limit });
        const container = document.getElementById('vector-results');
        container.innerHTML = '';

        results.forEach(res => {
            const div = document.createElement('div');
            div.className = 'vector-result';
            // Custom formatting for 'items' table if possible
            div.innerHTML = `
                <div class="vector-result-header">
                    <span style="font-weight:700; color:var(--text-secondary);">ID: ${res.id}</span>
                    <span class="score-badge">Similarity: ${(1 - (res.distance || 0)).toFixed(4)}</span>
                </div>
                <div class="content-preview">${res.content || 'N/A'}</div>
                ${res.path ? `<div style="font-size:0.75rem; color:var(--accent-color); margin-top:8px;">📁 ${res.path}</div>` : ''}
            `;
            container.appendChild(div);
        });

        if (results.length === 0) {
            container.innerHTML = '<div class="empty-state">一致する結果が見つかりませんでした。</div>';
        }
    } catch (e) {
        alert(`Search Failed: ${e}`);
    } finally {
        showLoading(false);
    }
};

window.loadMcpConfig = async function () {
    activeTable = null;
    document.querySelectorAll('.table-item').forEach(el => el.classList.remove('active'));
    document.getElementById('current-title').textContent = 'MCP Configuration';
    document.getElementById('main-tabs').style.display = 'none';
    document.getElementById('view-empty').style.display = 'none';
    switchTab('mcp');

    try {
        const data = await invoke('get_mcp_info');
        document.getElementById('mcp-json').textContent = JSON.stringify(data, null, 2);
    } catch (e) {
        console.error(e);
    }
};

window.copyConfig = function () {
    const text = document.getElementById('mcp-json').textContent;
    navigator.clipboard.writeText(text).then(() => {
        alert('Copied to clipboard!');
    });
};

function showLoading(show) {
    document.getElementById('loading-indicator').style.display = show ? 'block' : 'none';
}

function setupEvents() {
    listen('mcp-db-update', () => {
        if (activeTable === 'items') loadData();
    });
}

// Global scope initialization
window.addEventListener('DOMContentLoaded', init);