index.html 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>面向PikaPython的AI驱动Python-C编译加速器与自动化验证平台</title>
  7. <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
  8. </head>
  9. <body>
  10. <div class="sidebar">
  11. <button id="chat-btn" class="active">聊天功能</button>
  12. <button id="log-btn">获取日志</button>
  13. <button id="file-btn">获取文件</button>
  14. </div>
  15. <div class="main-content">
  16. <!-- 聊天部分 -->
  17. <div id="chat-section" class="section active">
  18. <div class="chat-messages" id="chat-messages"></div>
  19. <form id="chat-form">
  20. <div class="chat-input">
  21. <textarea id="user-input" placeholder="输入您的消息..."></textarea>
  22. <button type="submit">发送</button>
  23. </div>
  24. </form>
  25. <div id="loading">处理中...</div>
  26. </div>
  27. <!-- 日志扫描部分 -->
  28. <div id="log-section" class="section">
  29. <div class="scan-section">
  30. <div class="scan-controls">
  31. <p>日志目录: {{ LOG_DIR }}</p>
  32. <button id="scan-logs-btn">获取日志</button>
  33. </div>
  34. <div class="scan-results" id="log-results">
  35. <p>请点击"获取日志"按钮开始扫描...</p>
  36. </div>
  37. </div>
  38. </div>
  39. <!-- 文件扫描部分 -->
  40. <div id="file-section" class="section">
  41. <div class="scan-section">
  42. <div class="scan-controls">
  43. <p>文件目录: {{ FILE_DIR }}</p>
  44. <button id="refresh-files-btn">刷新文件</button>
  45. </div>
  46. <div class="scan-results" id="file-results">
  47. <p>请点击"刷新文件"按钮开始扫描...</p>
  48. </div>
  49. </div>
  50. </div>
  51. <script>
  52. // 确保在页面加载时正确初始化
  53. document.addEventListener('DOMContentLoaded', function() {
  54. // 初始化:只显示聊天部分
  55. switchSection('chat-section');
  56. setActiveButton(document.getElementById('chat-btn'));
  57. });
  58. // 切换部分功能
  59. document.getElementById('chat-btn').addEventListener('click', function() {
  60. switchSection('chat-section');
  61. setActiveButton(this);
  62. });
  63. document.getElementById('log-btn').addEventListener('click', function() {
  64. switchSection('log-section');
  65. setActiveButton(this);
  66. });
  67. document.getElementById('file-btn').addEventListener('click', function() {
  68. switchSection('file-section');
  69. setActiveButton(this);
  70. });
  71. function switchSection(sectionId) {
  72. console.log('切换到部分:', sectionId);
  73. // 隐藏所有部分
  74. document.querySelectorAll('.section').forEach(section => {
  75. section.classList.remove('active');
  76. });
  77. // 显示选中的部分
  78. const targetSection = document.getElementById(sectionId);
  79. if (targetSection) {
  80. targetSection.classList.add('active');
  81. }
  82. }
  83. function setActiveButton(button) {
  84. // 移除所有按钮的active类
  85. document.querySelectorAll('.sidebar button').forEach(btn => {
  86. btn.classList.remove('active');
  87. });
  88. // 为当前按钮添加active类
  89. button.classList.add('active');
  90. }
  91. // 聊天功能
  92. document.getElementById('chat-form').addEventListener('submit', function(e) {
  93. e.preventDefault();
  94. const userInput = document.getElementById('user-input');
  95. const message = userInput.value.trim();
  96. if (!message) return;
  97. // 显示用户消息(需要转义)
  98. addMessage(message, 'user');
  99. userInput.value = '';
  100. // 显示加载中
  101. const loading = document.getElementById('loading');
  102. loading.style.display = 'block';
  103. // 发送请求到后端
  104. fetch('/chat', {
  105. method: 'POST',
  106. headers: {
  107. 'Content-Type': 'application/json',
  108. },
  109. body: JSON.stringify({ message: message })
  110. })
  111. .then(response => response.json())
  112. .then(data => {
  113. loading.style.display = 'none';
  114. if (data.success) {
  115. // 显示agent回复(需要转义)
  116. addMessage(data.agent_response, 'agent');
  117. } else {
  118. addMessage('错误: ' + data.error, 'agent');
  119. }
  120. })
  121. .catch(error => {
  122. loading.style.display = 'none';
  123. addMessage('网络错误,请重试', 'agent');
  124. console.error('Error:', error);
  125. });
  126. });
  127. function addMessage(content, sender) {
  128. const chatMessages = document.getElementById('chat-messages');
  129. const messageDiv = document.createElement('div');
  130. messageDiv.className = `message ${sender}-message`;
  131. const contentDiv = document.createElement('div');
  132. contentDiv.className = 'message-content';
  133. // 使用 innerHTML 并转义内容,同时保留换行和空格
  134. contentDiv.innerHTML = escapeHtml(content);
  135. messageDiv.appendChild(contentDiv);
  136. chatMessages.appendChild(messageDiv);
  137. // 滚动到底部
  138. chatMessages.scrollTop = chatMessages.scrollHeight;
  139. }
  140. // 允许按Enter发送,Ctrl+Enter换行
  141. document.getElementById('user-input').addEventListener('keydown', function(e) {
  142. if (e.key === 'Enter' && !e.ctrlKey) {
  143. e.preventDefault();
  144. document.getElementById('chat-form').dispatchEvent(new Event('submit'));
  145. }
  146. });
  147. // 日志扫描功能
  148. document.getElementById('scan-logs-btn').addEventListener('click', function() {
  149. const logResults = document.getElementById('log-results');
  150. logResults.innerHTML = '<p>开始获取日志文件...</p>';
  151. console.log('开始SSE连接...');
  152. // 使用EventSource
  153. const eventSource = new EventSource('/scan_logs');
  154. eventSource.onopen = function(event) {
  155. console.log('SSE连接已建立');
  156. logResults.innerHTML += '<p style="color: green;">连接已建立,开始扫描...</p>';
  157. };
  158. eventSource.onmessage = function(event) {
  159. console.log('收到SSE消息:', event.data);
  160. try {
  161. const data = JSON.parse(event.data);
  162. // 忽略心跳包
  163. if (data.heartbeat) {
  164. console.log('收到心跳包');
  165. return;
  166. }
  167. displayLogEntry(data);
  168. // 如果是完成文件,关闭连接
  169. if (data.is_completion) {
  170. console.log('收到完成信号,关闭连接');
  171. eventSource.close();
  172. logResults.innerHTML += '<p style="color: green;">日志扫描完成</p>';
  173. }
  174. } catch (e) {
  175. console.error('解析SSE数据错误:', e, '原始数据:', event.data);
  176. logResults.innerHTML += `<p style="color: red;">数据解析错误: ${e.message}</p>`;
  177. }
  178. };
  179. eventSource.onerror = function(event) {
  180. console.error('SSE连接错误:', event);
  181. if (eventSource.readyState === EventSource.CLOSED) {
  182. console.log('SSE连接已关闭');
  183. logResults.innerHTML += '<p style="color: gray;">连接已关闭</p>';
  184. } else {
  185. console.log('SSE连接错误');
  186. logResults.innerHTML += '<p style="color: red;">连接错误,请检查服务器状态</p>';
  187. }
  188. eventSource.close();
  189. };
  190. // // 停止按钮
  191. // const stopButton = document.createElement('button');
  192. // stopButton.textContent = '停止扫描';
  193. // stopButton.onclick = function() {
  194. // console.log('手动停止扫描');
  195. // eventSource.close();
  196. // logResults.innerHTML += '<p>扫描已停止</p>';
  197. // stopButton.remove();
  198. // };
  199. // logResults.appendChild(stopButton);
  200. });
  201. // 显示日志条目的函数
  202. function displayLogEntry(logEntry) {
  203. const logResults = document.getElementById('log-results');
  204. const isCompletion = logEntry.is_completion || logEntry.filename === 'summary_stats.log';
  205. const completionClass = isCompletion ? 'completion-file' : '';
  206. const logElement = document.createElement('div');
  207. logElement.className = `log-file ${completionClass}`;
  208. logElement.setAttribute('data-log-id', logEntry.n || logEntry.filename);
  209. let content = '';
  210. if (logEntry.error) {
  211. content = `错误: ${logEntry.error}`;
  212. } else if (typeof logEntry.content === 'object') {
  213. content = JSON.stringify(logEntry.content, null, 2);
  214. } else {
  215. content = logEntry.content || '';
  216. }
  217. const fileName = logEntry.filename || '未知文件';
  218. logElement.innerHTML = `
  219. <div class="file-name">${fileName} ${isCompletion ? '(完成文件)' : ''} [序号: ${logEntry.n || 'N/A'}]</div>
  220. <div class="file-content">${content}</div>
  221. `;
  222. logResults.appendChild(logElement);
  223. // 自动滚动到底部
  224. logResults.scrollTop = logResults.scrollHeight;
  225. console.log('已显示日志条目:', logEntry);
  226. }
  227. // 文件扫描功能
  228. document.getElementById('refresh-files-btn').addEventListener('click', function() {
  229. const fileResults = document.getElementById('file-results');
  230. fileResults.innerHTML = '<p>扫描中...</p>';
  231. fetch('/refresh_files', {
  232. method: 'POST',
  233. headers: {
  234. 'Content-Type': 'application/json',
  235. }
  236. })
  237. .then(response => response.json())
  238. .then(data => {
  239. if (data.success) {
  240. displayFileResults(data.files);
  241. } else {
  242. fileResults.innerHTML = `<p style="color: red;">错误: ${data.error}</p>`;
  243. }
  244. })
  245. .catch(error => {
  246. fileResults.innerHTML = `<p style="color: red;">网络错误: ${error}</p>`;
  247. console.error('Error:', error);
  248. });
  249. });
  250. function displayFileResults(files) {
  251. const fileResults = document.getElementById('file-results');
  252. if (files.length === 0) {
  253. fileResults.innerHTML = '<p>未找到文件</p>';
  254. return;
  255. }
  256. let html = '';
  257. files.forEach(item => {
  258. const indent = '&nbsp;'.repeat(item.depth * 4);
  259. const icon = item.type === 'directory' ? '📁' :
  260. item.type === 'file' ? '📄' : '❌';
  261. html += `
  262. <div class="file-item ${item.type}">
  263. <div class="file-name">${indent}${icon} ${item.name}</div>
  264. ${item.type === 'file' ? `<div class="file-content">${escapeHtml(item.content)}</div>` : ''}
  265. </div>
  266. `;
  267. });
  268. fileResults.innerHTML = html;
  269. }
  270. // 辅助函数:转义 HTML 特殊字符
  271. function escapeHtml(unsafe) {
  272. if (typeof unsafe !== 'string') return unsafe;
  273. return unsafe
  274. .replace(/&/g, "&amp;")
  275. .replace(/</g, "&lt;")
  276. .replace(/>/g, "&gt;")
  277. .replace(/"/g, "&quot;")
  278. .replace(/'/g, "&#039;")
  279. .replace(/\n/g, "<br>")
  280. .replace(/ /g, "&nbsp;")
  281. .replace(/\t/g, "&nbsp;&nbsp;&nbsp;&nbsp;");
  282. }
  283. </script>
  284. </body>
  285. </html>