{% extends 'base.html' %}
{% block title %}LLM 自动分析 - 天璇{% endblock %}
{% block content %}
<style>
.thinking-panel {
    background: #f0f4ff;
    border: 1px solid #d0d8f0;
    border-radius: 6px;
    padding: 0.8rem;
    margin-top: 1rem;
    max-height: 200px;
    overflow: auto;
    font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
    font-size: 0.75rem;
    line-height: 1.5;
    color: #333;
    display: none;
    white-space: pre-wrap;
    word-break: break-word;
}
.thinking-panel .label {
    font-weight: 700;
    font-size: 0.8rem;
    color: #4361ee;
    margin-bottom: 0.4rem;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
.thinking-panel .label span {
    font-weight: 400;
    color: #888;
    font-size: 0.7rem;
}
.tool-call-item {
    border: 1px solid #e0e0e0;
    border-radius: 6px;
    margin-bottom: 0.4rem;
    overflow: hidden;
    background: #fff;
}
.tool-call-header {
    background: #f8f9fa;
    padding: 0.5rem 1rem;
    cursor: pointer;
    display: flex;
    justify-content: space-between;
    align-items: center;
    user-select: none;
    transition: background 0.15s;
}
.tool-call-header:hover {
    background: #eef1f5;
}
.tool-call-header .step-badge {
    display: inline-block;
    background: #4361ee;
    color: #fff;
    border-radius: 3px;
    padding: 0 6px;
    font-size: 0.7rem;
    font-weight: 600;
    margin-right: 6px;
}
.tool-call-header .tool-name {
    font-weight: 600;
    font-size: 0.85rem;
}
.tool-call-header .arrow {
    transition: transform 0.2s;
    font-size: 0.75rem;
    color: #888;
}
.tool-call-header.open .arrow {
    transform: rotate(90deg);
}
.tool-call-body {
    display: none;
    padding: 0.8rem 1rem;
    background: #fafbfc;
    border-top: 1px solid #eee;
}
.tool-call-body.open {
    display: block;
}
.tool-call-body .section-title {
    font-weight: 600;
    font-size: 0.75rem;
    color: #555;
    margin-bottom: 0.3rem;
    margin-top: 0.6rem;
}
.tool-call-body .section-title:first-child {
    margin-top: 0;
}
.tool-call-body pre {
    background: #1a1a2e;
    color: #e0e0e0;
    padding: 0.6rem;
    border-radius: 4px;
    font-size: 0.72rem;
    max-height: 250px;
    overflow: auto;
    margin: 0 0 0.3rem 0;
}
.tool-call-body pre code {
    font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
}
</style>

<div class="card">
    <h2>LLM 自动分析</h2>
    <p style="color:#666;">LLM 将自动编排工具链完成：加载 → 过滤 → 聚类 → 特征提取</p>
    {% if not llm_configured %}
    <div style="background:#fff3cd;padding:1rem;border-radius:6px;margin:1rem 0;">
        ⚠️ LLM 尚未配置。请先在 <a href="/config/">配置页面</a> 设置 base_url 和 API Key。
    </div>
    {% endif %}
</div>

<div id="autoProgress" style="display:none;margin-top:1rem;">
    <p>LLM 编排中... <span id="autoStatus"></span></p>
    <div style="background:#eee;height:8px;border-radius:4px;overflow:hidden;">
        <div id="autoBar" style="background:#4361ee;height:100%;width:0%;"></div>
    </div>

    <button id="cancelBtn" class="btn" style="background:#f72585;color:white;display:none;" onclick="cancelAnalysis()">取消分析</button>

    <!-- Unified timeline: interleaved thinking + tool calls -->
    <div id="timelinePanel" style="display:none;margin-top:1rem;">
        <h4 style="margin-bottom:0.6rem;color:#333;">📋 分析步骤</h4>
        <div id="timeline"></div>
    </div>
</div>

<div id="noRunMessage" style="background:#fff3cd;padding:1rem;border-radius:6px;margin:1rem 0;display:none;">
    请从上传页面启动LLM分析
</div>

<script>
let statusPoll = null;

function escapeHtml(str) {
    if (str == null) return '';
    return String(str).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}

function prettyJson(obj) {
    try { return JSON.stringify(obj, null, 2); }
    catch(e) { return String(obj); }
}

function toggleStep(el) {
    el.classList.toggle('open');
    const body = el.nextElementSibling;
    if (body) body.classList.toggle('open');
}

function formatTimestamp() {
    return new Date().toLocaleTimeString('zh-Hans', {hour:'2-digit',minute:'2-digit',second:'2-digit'});
}

function buildTimeline(llmThinking, toolCalls) {
    // Build tool entries from tool_calls_json (no separate thought entries)
    const entries = (toolCalls || []).map(tc => ({
        step: tc.step,
        type: 'tool',
        name: tc.name,
        input: tc.input,
        output: tc.output,
    }));

    // Match llm_thinking text to tool entries by step number
    if (llmThinking) {
        const thinkingByStep = {};
        const parts = llmThinking.split(/\n(?=\[\d+\])/);
        for (const part of parts) {
            const m = part.match(/^\[(\d+)\]\s*(.*)/s);
            if (m) {
                thinkingByStep[parseInt(m[1])] = m[2].trim();
            }
        }
        for (const entry of entries) {
            if (thinkingByStep[entry.step] !== undefined) {
                entry.thinking = thinkingByStep[entry.step];
            }
        }
    }

    entries.sort((a, b) => a.step - b.step);
    return entries;
}

function renderTimeline(entries) {
    const container = document.getElementById('timeline');
    const panel = document.getElementById('timelinePanel');
    if (!entries || entries.length === 0) return;

    panel.style.display = 'block';
    // Clear and re-render entire timeline each poll
    container.innerHTML = '';
    let lastStep = -1;

    for (const entry of entries) {
        // Step header
        if (entry.step !== lastStep) {
            const header = document.createElement('div');
            header.style.cssText = 'font-weight:700;font-size:0.85rem;color:#4361ee;padding:0.6rem 0 0.3rem 0;border-top:1px solid #eee;margin-top:0.4rem;';
            header.textContent = `步骤 ${entry.step}`;
            container.appendChild(header);
            lastStep = entry.step;
        }

        if (entry.type === 'tool') {
            // Tool call — collapsible with I/O; thinking shown when output is null
            const card = document.createElement('div');
            card.style.cssText = 'border:1px solid #e0e0e0;border-radius:6px;margin-bottom:0.3rem;overflow:hidden;background:#fff;';
            const inputStr = prettyJson(entry.input);
            const hasThinking = entry.thinking && (entry.output == null || entry.output === '' || entry.output === 'null' || entry.output === 'None');
            const outputStr = prettyJson(entry.output);
            let bodyInner = '';
            bodyInner +=
                '<div style="font-weight:600;font-size:0.72rem;color:#555;margin-bottom:0.2rem;">输入 (INPUT):</div>' +
                '<pre style="background:#1a1a2e;color:#e0e0e0;padding:0.5rem;border-radius:4px;font-size:0.7rem;max-height:200px;overflow:auto;margin:0 0 0.3rem 0;"><code>' + escapeHtml(inputStr) + '</code></pre>';
            if (hasThinking) {
                bodyInner +=
                    '<div style="font-weight:600;font-size:0.72rem;color:#555;margin-bottom:0.2rem;">思考 (THINKING):</div>' +
                    '<div style="background:#eef2ff;padding:0.6rem;border-radius:4px;font-size:0.78rem;line-height:1.6;color:#333;max-height:200px;overflow:auto;margin:0;white-space:pre-wrap;">' + escapeHtml(entry.thinking) + '</div>';
            } else {
                bodyInner +=
                    '<div style="font-weight:600;font-size:0.72rem;color:#555;margin-bottom:0.2rem;">输出 (OUTPUT):</div>' +
                    '<pre style="background:#1a1a2e;color:#e0e0e0;padding:0.5rem;border-radius:4px;font-size:0.7rem;max-height:200px;overflow:auto;margin:0;"><code>' + escapeHtml(outputStr) + '</code></pre>';
            }
            card.innerHTML =
                '<div class="tool-call-header" onclick="toggleStep(this)" style="padding:0.4rem 0.8rem;">' +
                    '<span><span style="background:#43aa8b;color:#fff;border-radius:3px;padding:0 5px;font-size:0.7rem;font-weight:600;margin-right:6px;">🔧</span>' +
                    '<span style="font-weight:600;font-size:0.8rem;">' + escapeHtml(entry.name || '?') + '</span></span>' +
                    '<span class="arrow">▶</span>' +
                '</div>' +
                '<div class="tool-call-body" style="padding:0.5rem 0.8rem;background:#fafbfc;">' +
                    bodyInner +
                '</div>';
            container.appendChild(card);
        }
    }
}

async function startAuto() {
    const params = new URLSearchParams(window.location.search);
    const runId = params.get('run_id');
    if (!runId) {
        document.getElementById('noRunMessage').style.display = 'block';
        return;
    }

    const timelineContainer = document.getElementById('timeline');
    const timelinePanel = document.getElementById('timelinePanel');

    // Reset UI
    document.getElementById('noRunMessage').style.display = 'none';
    document.getElementById('autoProgress').style.display = 'block';
    document.getElementById('cancelBtn').style.display = 'inline-block';
    timelinePanel.style.display = 'none';
    timelineContainer.innerHTML = '';

    let resp;
    try {
        resp = await fetch('/analyze/llm/', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json', 'X-CSRFToken': '{{ csrf_token }}' },
            body: JSON.stringify({ run_id: parseInt(runId) }),
        });
    } catch (e) { showError('启动 LLM 分析失败: ' + e.message); return; }
    const data = await resp.json();

    let lastTimelineHash = '';

    statusPoll = setInterval(async () => {
        try {
            const r = await fetch(`/runs/${runId}/status/`);
            const s = await r.json();
            const pct = s.progress_pct || 0;
            const msg = s.progress_msg || s.status;
            document.getElementById('autoStatus').textContent = `${msg} (${pct}%)`;
            document.getElementById('autoBar').style.width = pct + '%';

            // Build and render unified timeline
            const entries = buildTimeline(s.llm_thinking, s.tool_calls);
            const hash = JSON.stringify(entries.map(e => ({step:e.step,name:e.name,thinking:(e.thinking||'').slice(0,40)})));
            if (hash !== lastTimelineHash) {
                renderTimeline(entries);
                lastTimelineHash = hash;
            }

            if (s.status === 'completed') {
                clearInterval(statusPoll);
                document.getElementById('autoBar').style.width = '100%';
                document.getElementById('cancelBtn').style.display = 'none';
                document.getElementById('autoStatus').textContent = '分析完成！正在跳转...';
                setTimeout(() => { window.location.href = '/clusters/' + runId + '/'; }, 1500);
            }
            if (s.status === 'failed') {
                clearInterval(statusPoll);
                document.getElementById('cancelBtn').style.display = 'none';
                document.getElementById('autoStatus').textContent = '失败: ' + (s.error_message || '');
            }
        } catch (e) { showError('轮询状态失败: ' + e.message); }
    }, 1000);
}

function cancelAnalysis() {
    if (statusPoll) { clearInterval(statusPoll); statusPoll = null; }
    document.getElementById('autoStatus').textContent = '已取消';
    document.getElementById('cancelBtn').style.display = 'none';
    document.getElementById('timelinePanel').innerHTML += '\n--- 分析已取消 ---\n';
}

// Auto-start if run_id is provided via URL parameter
(function() {
    const params = new URLSearchParams(window.location.search);
    if (params.get('run_id')) {
        startAuto();
    }
})();
</script>
{% endblock %}
<!--VERSION2-->