Files
blog-app/.review-tmp/analyze_page.html
2026-08-17 17:11:27 +08:00

296 lines
12 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI 分析 - AI Agent 项目追踪</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body class="d-flex flex-column min-vh-100">
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="/">AI Agent 追踪</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mainNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="mainNav">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="nav-link" href="/">首页</a></li>
<li class="nav-item"><a class="nav-link" href="/projects">项目列表</a></li>
<li class="nav-item"><a class="nav-link" href="/search">全局搜索</a></li>
</ul>
<form class="d-flex" action="/search" method="get" id="navSearchForm">
<input class="form-control me-2 nav-search-input" type="search"
name="q" placeholder="搜索项目 / release / issue" aria-label="Search">
<button class="btn btn-outline-light" type="submit">搜索</button>
</form>
</div>
</div>
</nav>
<main class="container py-4 flex-grow-1">
<h1 class="mb-4">AI 分析</h1>
<!-- Control panel -->
<div class="card mb-4">
<div class="card-body">
<div class="row g-3">
<!-- Scope selector -->
<div class="col-md-3">
<label class="form-label">分析范围</label>
<select class="form-select" id="scopeSelect">
<option value="home">全站概览</option>
<option value="projects">所有项目</option>
<option value="project/1">项目: hermes-agent</option>
<option value="project/2">项目: opencode</option>
<option value="project/3">项目: wxhook</option>
</select>
</div>
<!-- Preset buttons -->
<div class="col-md-6">
<label class="form-label">预设分析</label>
<div class="btn-group w-100" role="group">
<button type="button" class="btn btn-outline-primary preset-btn" data-preset="activity">
📊 活跃度评估
</button>
<button type="button" class="btn btn-outline-primary preset-btn" data-preset="evolution">
🚀 版本演进
</button>
<button type="button" class="btn btn-outline-primary preset-btn" data-preset="issues_hot">
🔥 Issue 热点
</button>
</div>
</div>
<!-- Start button -->
<div class="col-md-3 d-flex align-items-end">
<button type="button" class="btn btn-success w-100" id="startBtn">
开始分析
</button>
</div>
</div>
<!-- Custom question input -->
<div class="row g-3 mt-2">
<div class="col-12">
<label class="form-label">自定义问题 (可选)</label>
<input type="text" class="form-control" id="questionInput"
placeholder="例如: 这个项目的核心功能是什么? / 最近有什么重要更新?">
</div>
</div>
</div>
</div>
<!-- Output area with tabs -->
<div class="card">
<div class="card-header">
<ul class="nav nav-tabs card-header-tabs" id="outputTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="observation-tab" data-bs-toggle="tab"
data-bs-target="#observation" type="button" role="tab">
数据观察
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="prompt-tab" data-bs-toggle="tab"
data-bs-target="#prompt" type="button" role="tab">
Prompt
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="reasoning-tab" data-bs-toggle="tab"
data-bs-target="#reasoning" type="button" role="tab">
LLM 推理
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="conclusion-tab" data-bs-toggle="tab"
data-bs-target="#conclusion" type="button" role="tab">
结论
</button>
</li>
</ul>
</div>
<div class="card-body">
<div class="tab-content" id="outputTabContent">
<div class="tab-pane fade show active" id="observation" role="tabpanel">
<div class="output-content" id="observationContent">
<div class="text-muted">等待分析开始...</div>
</div>
</div>
<div class="tab-pane fade" id="prompt" role="tabpanel">
<div class="output-content" id="promptContent">
<div class="text-muted">等待分析开始...</div>
</div>
</div>
<div class="tab-pane fade" id="reasoning" role="tabpanel">
<div class="output-content" id="reasoningContent">
<div class="text-muted">等待分析开始...</div>
</div>
</div>
<div class="tab-pane fade" id="conclusion" role="tabpanel">
<div class="output-content" id="conclusionContent">
<div class="text-muted">等待分析开始...</div>
</div>
</div>
</div>
</div>
</div>
</main>
<footer class="bg-dark text-light text-center py-3 mt-auto">
<div class="container">
AI Agent 项目追踪 - Powered by Flask
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="/static/js/app.js"></script>
<script>
// State
let currentEventSource = null;
let currentTab = 'observation'; // Track which tab we're writing to
let reasoningStarted = false;
let conclusionStarted = false;
// DOM elements
const scopeSelect = document.getElementById('scopeSelect');
const questionInput = document.getElementById('questionInput');
const startBtn = document.getElementById('startBtn');
const presetBtns = document.querySelectorAll('.preset-btn');
const observationContent = document.getElementById('observationContent');
const promptContent = document.getElementById('promptContent');
const reasoningContent = document.getElementById('reasoningContent');
const conclusionContent = document.getElementById('conclusionContent');
// Tab switching
document.querySelectorAll('#outputTabs button').forEach(button => {
button.addEventListener('click', (e) => {
const tabId = e.target.id.replace('-tab', '');
currentTab = tabId;
});
});
// Preset button click
presetBtns.forEach(btn => {
btn.addEventListener('click', () => {
const preset = btn.dataset.preset;
startAnalysis(null, preset);
});
});
// Start button click
startBtn.addEventListener('click', () => {
const question = questionInput.value.trim();
if (!question) {
alert('请输入自定义问题或选择预设分析');
return;
}
startAnalysis(question, null);
});
function startAnalysis(question, preset) {
// Close existing connection
if (currentEventSource) {
currentEventSource.close();
currentEventSource = null;
}
// Reset content
observationContent.innerHTML = '<div class="text-muted">正在收集上下文...</div>';
promptContent.innerHTML = '<div class="text-muted">等待...</div>';
reasoningContent.innerHTML = '<div class="text-muted">等待...</div>';
conclusionContent.innerHTML = '<div class="text-muted">等待...</div>';
reasoningStarted = false;
conclusionStarted = false;
// Build URL
const scope = scopeSelect.value;
const params = new URLSearchParams({scope});
if (question) params.append('question', question);
if (preset) params.append('preset', preset);
const url = `/api/analyze/stream?${params.toString()}`;
// Start SSE
currentEventSource = new EventSource(url);
// Handle step events
currentEventSource.addEventListener('step', (e) => {
const data = JSON.parse(e.data);
const step = data.step;
const content = data.content;
if (step === 'context') {
observationContent.innerHTML = `<div class="alert alert-info">${escapeHtml(content)}</div>`;
} else if (step === 'prompt') {
promptContent.innerHTML = `<pre class="bg-light p-3 rounded" style="white-space: pre-wrap;">${escapeHtml(content)}</pre>`;
} else if (step === 'complete') {
conclusionContent.innerHTML = `<div class="alert alert-success">${escapeHtml(content)}</div>`;
currentEventSource.close();
currentEventSource = null;
}
});
// Handle token events (streaming LLM output)
currentEventSource.addEventListener('token', (e) => {
const data = JSON.parse(e.data);
const text = data.text;
// Determine which tab to write to based on content markers
if (!reasoningStarted && (text.includes('1.') || text.includes('数据观察') || text.includes('初步判断'))) {
reasoningStarted = true;
reasoningContent.innerHTML = '';
}
if (!conclusionStarted && (text.includes('4.') || text.includes('结论'))) {
conclusionStarted = true;
conclusionContent.innerHTML = '';
}
// Write to appropriate tab
if (reasoningStarted && !conclusionStarted) {
reasoningContent.innerHTML += escapeHtml(text);
} else if (conclusionStarted) {
conclusionContent.innerHTML += escapeHtml(text);
} else {
// Default to reasoning if no markers yet
reasoningContent.innerHTML += escapeHtml(text);
}
});
// Handle errors
currentEventSource.addEventListener('error', (e) => {
const data = JSON.parse(e.data);
const errorMsg = data.error || '未知错误';
observationContent.innerHTML = `<div class="alert alert-danger">错误: ${escapeHtml(errorMsg)}</div>`;
currentEventSource.close();
currentEventSource = null;
});
currentEventSource.onerror = (e) => {
observationContent.innerHTML = `<div class="alert alert-danger">连接错误, 请检查后端日志</div>`;
currentEventSource.close();
currentEventSource = null;
};
}
// Helper: escape HTML
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
</script>
<!-- 全站浮动 AI 分析面板 -->
<button id="ai-fab" type="button" title="AI 分析">🤖</button>
<script src="/static/js/ai-panel.js"></script>
</body>
</html>