# 剛好最近換電腦用4070跑一下meta出的 llama3.2 3b 和 code llama3-8b模型 這邊換成 llama 呼叫方式 # download llama module ``` https://huggingface.co/hugging-quants/Llama-3.2-3B-Instruct-Q4_K_M-GGUF https://huggingface.co/bartowski/Code-Llama-3-8B-GGUF ``` 這邊當然比不太上chatgpt 和有反應速度,和精確程度,目前我把 llama 模型是裝在 wsl ,所以要裝一下 # install cuda ```bash set -x PATH /usr/local/cuda/bin $PATH set -x LD_LIBRARY_PATH /usr/local/cuda/lib64 $LD_LIBRARY_PATH nvcc --version nvcc sudo apt install cuda sudo apt install libxkbcommon-x11-0 sudo apt install libxkbcommon0=0.10.0-1 apt-cache policy libxkbcommon0 sudo apt update sudo apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/7fa2af80.pub sudo sh -c 'echo "deb https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/ /" > /etc/apt/sources.list.d/cuda.list' nvidia-smiset -x PATH /usr/local/cuda/bin $PATH set -x LD_LIBRARY_PATH /usr/local/cuda/lib64 $LD_LIBRARY_PATH nvcc --version nvcc ``` 然後就可以切換到 # install llama ```bash git clone https://github.com/ggerganov/llama.cpp cd llama.cpp sudo LLAMA_CURL=1 LLAMA_CUDA=1 makesudo LLAMA_CURL=1 LLAMA_CUDA=1 make ``` # run llama ```bash ./llama-server --port 9090 --hf-repo hugging-quants/Llama-3.2-3B-Instruct-Q4_K_M-GGUF --hf-file llama-3.2-3b-instruct-q4_k_m.gguf -c 4096 --n-gpu-layers 28 ./llama-server --port 9090 --hf-repo bartowski/Code-Llama-3-8B-GGUF --hf-file Code-Llama-3-8B-Q4_K_M.gguf -c 4096 --n-gpu-layers 46 ``` 唯一要注意是request 這邊還是蠻重要的 ![image](https://hackmd.io/_uploads/r14VR380A.png)可以看到request 這邊還是蠻重要的 ![image](https://hackmd.io/_uploads/r14VR380A.png) ```rust let final_request_body = serde_json::json!( { "n_predict": 4096, "temperature": 0.28, "stop": ["</s>", "<|end|>", "<|eot_id|>", "<|end_of_text|>", "<|im_end|>", "<|EOT|>", "<|END_OF_TURN_TOKEN|>", "<|end_of_turn|>", "<|endoftext|>", "ASSISTANT", "USER"], "repeat_last_n": 0, "repeat_penalty": 0.80, "penalize_nl": false, "top_k": 40, "top_p": 0.79, "min_p": 0.43, "tfs_z": 1, "typical_p": 1, "presence_penalty": 0, "frequency_penalty": 0, "mirostat": 0, "mirostat_tau": 5, "mirostat_eta": 0.1, "grammar": "", "n_probs": 0, "min_keep": 0, "prompt": final_prompt.trim() }); let final_request_body = serde_json::json!( { "n_predict": 4096, "temperature": 0.28, "stop": ["</s>", "<|end|>", "<|eot_id|>", "<|end_of_text|>", "<|im_end|>", "<|EOT|>", "<|END_OF_TURN_TOKEN|>", "<|end_of_turn|>", "<|endoftext|>", "ASSISTANT", "USER"], "repeat_last_n": 0, "repeat_penalty": 0.80, "penalize_nl": false, "top_k": 40, "top_p": 0.79, "min_p": 0.43, "tfs_z": 1, "typical_p": 1, "presence_penalty": 0, "frequency_penalty": 0, "mirostat": 0, "mirostat_tau": 5, "mirostat_eta": 0.1, "grammar": "", "n_probs": 0, "min_keep": 0, "prompt": final_prompt.trim() }); ``` 你也可以在這裡微調 ![image](https://hackmd.io/_uploads/rkpHka8C0.png) 一開始還以為為什麼參數有設錯嗎,怎麼跟網頁板差那麼多,這邊直接照抄他的,下面這個是罪魁禍首 ```json stream: truestream: true ``` ![image](https://hackmd.io/_uploads/BkQKkaLR0.png) ```rust use reqwest::Client; use serde::{Deserialize, Serialize}; use std::fs; use std::path::Path; use std::io; use warp::Filter; use warp::Reply; // 添加此导入 use dotenv::dotenv; use std::env; use std::collections::HashMap; use futures::future::join_all; use std::sync::Arc; use tokio::sync::RwLock; // =========================== // 可配置的常數 // =========================== // 伺服器埠號設定 const SERVER_PORT: u16 = 3030; // 程式碼檔案的副檔名清單 const CODE_FILE_EXTENSIONS: &[&str] = &[ "rs", "py", "js", "ts", "java", "cpp", "c", "go", "sh", "rb", "bat", "cs", "resx","h","md", ]; const FILE_SUMMARY_PROMPT: &str = "你是一個專業的軟體分析工程師,給你程式碼你可以描述原始碼的大致實現那些具體功能,並精確地請以 「繁體中文 」的方式撰寫,大概寫個50個字。\nUSER:{}\nASSISTANT"; // 專案目錄路徑設定 const PROJECT_PATH: &str = "/root/angr_ctf"; const FOLDER_ANALYSIS_PROMPT: &str = "Please analyze the following folder names and filter out those that are likely to be user-written source code directories. If no directories are found, please use the default path: /root/c. The result should only return a JSON structure in the following format: {\"analysis_key\": [folder names that meet the criteria]}, where 'analysis_key' is the only key, and the corresponding value is an array of folder names that meet the criteria. Please ensure that the returned JSON structure contains only this key-value pair and does not include any additional information or explanations.\nThe list of folder names is as follows:\n{folders}\n{extra_folders}"; // =========================== // llama 請求和回應結構 // =========================== #[derive(Serialize, Deserialize)] struct LlamaRequest { prompt: String, n_predict: usize, temperature: f32, top_k: usize, top_p: f32, } // =========================== // GPT 請求和回應結構 // =========================== #[derive(Serialize, Deserialize)] struct GPTRequest { model: String, messages: Vec<Message>, } #[derive(Serialize, Deserialize)] struct Message { role: String, content: String, } #[derive(Deserialize)] struct GPTResponse { choices: Vec<Choice>, } #[derive(Deserialize)] struct Choice { message: Message, } // 定義用於解析 GPT 分析回應的結構 #[derive(Serialize, Deserialize)] struct GPTAnalysis { analysis_key: Vec<String>, } // 過濾隱藏目錄與不重要的目錄 fn is_hidden_or_common_ignore(path: &Path) -> bool { let hidden_dirs = vec![".git", ".github", ".pytest_cache", ".gitignore", "site-packages"]; if let Some(dir_name) = path.file_name() { if let Some(dir_name_str) = dir_name.to_str() { return hidden_dirs.contains(&dir_name_str); } } false } use serde_json::Value; // 引入通用的 Value 類型 // 使用 Llama 過濾檔案並生成摘要 async fn summarize_file_with_llama( file_content: String, ) -> Result<String, Box<dyn std::error::Error + Send + Sync>> { let client = Client::new(); let max_lines = 500; // 設定每次請求的最大行數 let mut summaries = Vec::new(); // 將 file_content 切割成多個片段 let lines: Vec<&str> = file_content.lines().collect(); // 將內容切割為行 let mut start = 0; while start < lines.len() { let end = std::cmp::min(start + max_lines, lines.len()); let chunk = lines[start..end].join("\n"); // 合併行為一個片段 // 替換 FILE_SUMMARY_PROMPT 中的佔位符 let prompt = FILE_SUMMARY_PROMPT.replace("{}", &chunk); // 設置 POST 請求的 body let request_body = serde_json::json!( { "n_predict": 4096, "temperature": 0.2, "stop": ["</s>", "<|end|>", "<|eot_id|>", "<|end_of_text|>", "<|im_end|>", "<|EOT|>", "<|END_OF_TURN_TOKEN|>", "<|end_of_turn|>", "<|endoftext|>", "ASSISTANT", "USER"], "repeat_last_n": 0, "repeat_penalty": 0.80, "penalize_nl": false, "top_k": 40, "top_p": 0.79, "min_p": 0.43, "tfs_z": 1, "typical_p": 1, "presence_penalty": 0, "frequency_penalty": 0, "mirostat": 0, "mirostat_tau": 5, "mirostat_eta": 0.1, "grammar": "", "n_probs": 0, "min_keep": 0, "prompt": prompt.trim() }); // 發送請求 let res = client .post("http://127.0.0.1:9090/completion") .json(&request_body) .send() .await?; let res_text = res.text().await?; let res_json: Value = serde_json::from_str(&res_text)?; // 檢查 JSON 回應中是否存在 "content" 欄位 if let Some(summary) = res_json.get("content") { if let Some(summary_str) = summary.as_str() { summaries.push(summary_str.to_string()); // 將摘要添加到總結中 } } start += max_lines; // 移動到下一個片段 } // 合併所有摘要為一個大段落 let final_summary = summaries.join(" "); // 使用空格合併片段 // 最終的摘要調用 let final_prompt = FILE_SUMMARY_PROMPT.replace("{}", &final_summary); let final_request_body = serde_json::json!( { "n_predict": 4096, "temperature": 0.28, "stop": ["</s>", "<|end|>", "<|eot_id|>", "<|end_of_text|>", "<|im_end|>", "<|EOT|>", "<|END_OF_TURN_TOKEN|>", "<|end_of_turn|>", "<|endoftext|>", "ASSISTANT", "USER"], "repeat_last_n": 0, "repeat_penalty": 0.80, "penalize_nl": false, "top_k": 40, "top_p": 0.79, "min_p": 0.43, "tfs_z": 1, "typical_p": 1, "presence_penalty": 0, "frequency_penalty": 0, "mirostat": 0, "mirostat_tau": 5, "mirostat_eta": 0.1, "grammar": "", "n_probs": 0, "min_keep": 0, "prompt": final_prompt.trim() }); // 發送最終請求 let res = client .post("http://127.0.0.1:9090/completion") .json(&final_request_body) .send() .await?; let res_text = res.text().await?; let res_json: Value = serde_json::from_str(&res_text)?; // 檢查 JSON 回應中是否存在 "content" 欄位 if let Some(final_summary_content) = res_json.get("content") { if let Some(final_summary_str) = final_summary_content.as_str() { return Ok(final_summary_str.to_string()); } } Err("無法從 Llama 回應中提取最終摘要".into()) } use regex::Regex; use std::error::Error; // 使用 Llama 過濾資料夾 async fn analyze_folders_with_llama( folders: &str, extra_folders: &str, ) -> Result<String, Box<dyn Error>> { let client = Client::new(); // 替換 prompt 中的資料夾內容 let prompt = format!( "Please analyze the following folder names and filter out those that are likely to be user-written source code directories. If no directories are found, please use the default path: /root/c. The result should only return a JSON structure in the following format: {{\"analysis_key\": [folder names that meet the criteria]}}, where 'analysis_key' is the only key, and the corresponding value is an array of folder names that meet the criteria. Please ensure that the returned JSON structure contains only this key-value pair and does not include any additional information or explanations.\nThe list of folder names is as follows\n\n\nUSER:{}{}\nASSISTANT", folders.trim(), // 清除前後空白 extra_folders.trim() // 清除前後空白 ); // println!("伺服器回應: {}", prompt); // 構建 Llama 請求 let request_body = serde_json::json!( { "n_predict": 800, "temperature": 0.28, "stop": ["</s>", "<|end|>", "<|eot_id|>", "<|end_of_text|>", "<|im_end|>", "<|EOT|>", "<|END_OF_TURN_TOKEN|>", "<|end_of_turn|>", "<|endoftext|>", "ASSISTANT", "USER"], "repeat_last_n": 0, "repeat_penalty": 0.84, "penalize_nl": false, "top_k": 31, "top_p": 0.79, "min_p": 0.43, "tfs_z": 1, "typical_p": 1, "presence_penalty": 0, "frequency_penalty": 0, "mirostat": 0, "mirostat_tau": 5, "mirostat_eta": 0.1, "grammar": "", "n_probs": 0, "min_keep": 0, "prompt": prompt }); // 發送請求到 Llama 伺服器 let res = client .post("http://127.0.0.1:9090/completion") .json(&request_body) .send() .await?; let res_text = res.text().await?; // 打印伺服器回應內容,方便調試 println!("伺服器回應: {}", res_text); // 嘗試解析伺服器回應為 JSON let res_json: serde_json::Value = serde_json::from_str(&res_text)?; // 提取 content 欄位 if let Some(content) = res_json.get("content") { if let Some(content_str) = content.as_str() { // 使用正則表達式匹配 JSON 結構,尋找最後出現的 { ... } 包含 "analysis_key" 的結構 let json_re = Regex::new(r#"\{[^{}]*"analysis_key":[^{}]*\}"#)?; // 嘗試匹配 if let Some(captures) = json_re.captures(content_str) { let json_str = captures.get(0).map_or("", |m| m.as_str()); // 顯示提取到的 JSON 結構 println!("提取到的 JSON 結構: {}", json_str); return Ok(json_str.to_string()); } } } // 如果解析失敗,返回錯誤 Err("無法從 Llama 回應中提取 JSON 結構".into()) } // 定義檔案資訊結構 #[derive(Debug, Serialize, Deserialize, Clone)] struct FileInfo { name: String, summary: Option<String>, } // 定義目錄結構 #[derive(Debug, Serialize, Deserialize, Clone)] struct Directory { name: String, subdirs: Vec<Directory>, files: Vec<FileInfo>, path: String, } impl Directory { fn new(name: String, path: String) -> Self { Directory { name, subdirs: Vec::new(), files: Vec::new(), path, } } // 修改後的 from_path 函數,添加了排序功能 fn from_path(path: &Path, collect_files: bool) -> Self { let name = path .file_name() .unwrap_or_default() .to_str() .unwrap_or("") .to_string(); let path_str = path.to_string_lossy().to_string(); let mut dir = Directory::new(name.clone(), path_str.clone()); if let Ok(entries) = fs::read_dir(path) { let mut dirs = Vec::new(); let mut files = Vec::new(); for entry in entries.flatten() { let entry_path = entry.path(); if entry_path.is_dir() && !is_hidden_or_common_ignore(&entry_path) { dirs.push(entry_path); } else if collect_files && entry_path.is_file() && Directory::is_code_file(&entry_path) { files.push(entry_path); } } // 對目錄和檔案進行排序 dirs.sort_by(|a, b| a.file_name().unwrap_or_default().cmp(&b.file_name().unwrap_or_default())); files.sort_by(|a, b| a.file_name().unwrap_or_default().cmp(&b.file_name().unwrap_or_default())); for entry_path in dirs { dir.subdirs.push(Directory::from_path(&entry_path, collect_files)); } for entry_path in files { if let Some(file_name) = entry_path.file_name() { if let Some(file_name_str) = file_name.to_str() { dir.files.push(FileInfo { name: file_name_str.to_string(), summary: None, }); } } } } dir } // 判斷檔案是否為程式碼檔案 fn is_code_file(path: &Path) -> bool { if let Some(ext) = path.extension().and_then(|e| e.to_str()) { CODE_FILE_EXTENSIONS.contains(&ext) } else { false } } // 收集所有資料夾名稱,格式化為字串,並標示每個資料夾的上層(供 GPT/Llama 使用) fn collect_folders(&self) -> String { let mut result = String::new(); result.push_str(""); self.collect_folders_recursively(0, &mut result, None, false); result } // 修改後的遞迴收集函數,新增 parent_name 用來追蹤上層資料夾,並顯示上層結構 fn collect_folders_recursively( &self, depth: usize, result: &mut String, parent_name: Option<&str>, // 追蹤上層資料夾 include_files: bool, ) { // 使用縮排來表示資料夾層次結構 for _ in 0..depth { result.push_str(" "); // 每個層級增加兩個空格 } // 標記為資料夾並添加名稱和上層資料夾 if let Some(parent) = parent_name { result.push_str(&format!("folder: {} (top folder: {})\n", self.name, parent)); } else { result.push_str(&format!("folder: {} (root folder)\n", self.name)); // 如果沒有上層,標示為根資料夾 } // 遞迴列出子資料夾,並將當前資料夾設為子資料夾的上層 for subdir in &self.subdirs { subdir.collect_folders_recursively(depth + 1, result, Some(&self.name), include_files); } // 如果需要,列出檔案 if include_files { for file in &self.files { for _ in 0..(depth + 1) { result.push_str(" "); // 顯示檔案的縮排,略多於資料夾 } // 標記為檔案並添加上層資料夾名稱 result.push_str(&format!("檔案: {} (上層: {})\n", file.name, self.name)); } } } // 收集需要生成摘要的檔案 fn collect_files_to_summarize(&mut self, filtered_folders: &[String]) -> Vec<(String, String)> { let mut files = Vec::new(); if filtered_folders.iter().any(|folder| self.name.to_lowercase() == folder.to_lowercase()) { // 重新從檔案系統中收集其所有子目錄和檔案 *self = Directory::from_path(Path::new(&self.path), true); // 收集當前目錄及其子目錄的所有檔案 self.collect_all_files(&mut files); } else { // 遞迴檢查子目錄 for subdir in &mut self.subdirs { files.extend(subdir.collect_files_to_summarize(filtered_folders)); } } files } // 收集當前目錄及其所有子目錄的所有檔案 fn collect_all_files(&self, files: &mut Vec<(String, String)>) { for file in &self.files { let file_path = Path::new(&self.path).join(&file.name).to_string_lossy().to_string(); files.push((file_path, file.name.clone())); } for subdir in &self.subdirs { subdir.collect_all_files(files); } } // 更新檔案摘要 fn update_file_summary(&mut self, file_path: &str, summary: String) { if self.path == file_path { // 當前路徑即為檔案路徑 if let Some(file) = self.files.iter_mut().find(|f| { let full_path = format!("{}/{}", self.path, f.name); full_path == file_path }) { file.summary = Some(summary); } return; } // 遞迴更新子目錄 for subdir in &mut self.subdirs { if file_path.starts_with(&subdir.path) { subdir.update_file_summary(file_path, summary.clone()); } } } } // 從使用者輸入取得要保留的資料夾名稱 fn get_folders_to_add() -> String { println!("請輸入要保留的資料夾名稱(以逗號分隔,或輸入 'ok' 表示完成):"); let mut input = String::new(); io::stdin().read_line(&mut input).expect("無法讀取輸入"); input.trim().to_string() } // 定義進度結構 #[derive(Debug, Serialize, Clone)] struct Progress { total_files: usize, completed_files: usize, summaries: HashMap<String, String>, } #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { // 使用有效的 API 金鑰 dotenv().ok(); let api_key = env::var("OPENAI_API_KEY").expect("未設置 OPENAI_API_KEY"); // 指定專案目錄路徑 let path = Path::new(PROJECT_PATH); let mut project = Directory::from_path(path, false); // 初次僅收集目錄 // 1. 初始收集資料夾 let folders = project.collect_folders(); println!("收集的資料夾:\n{}", folders); // 2. 初始呼叫 GPT 進行資料夾過濾 let mut extra_prompt = String::new(); // 保存使用者補充的資料夾 let filtered_folders = analyze_folders_with_llama(&folders.to_string(), &extra_prompt.to_string()).await?; println!("重新過濾後的結果:\n{}", filtered_folders); // 3. 解析 GPT 回應 let analysis: GPTAnalysis = serde_json::from_str(&filtered_folders)?; let mut filtered_folder_list = analysis.analysis_key; // 4. 互動式資料夾選擇 loop { let folders_to_add = get_folders_to_add(); if folders_to_add.to_lowercase() == "ok" { break; } // 將新增資料夾加到 GPT 請求中 extra_prompt.push_str(&format!(", 請再額外判斷 {}", folders_to_add)); // 再次過濾資料夾,包含新的資料夾清單 let updated_folders = project.collect_folders(); let filtered_folders = analyze_folders_with_llama(&updated_folders.to_string(), &extra_prompt.to_string()).await?; println!("重新過濾後的結果:\n{}", filtered_folders); // 解析更新後的 GPT 回應 let analysis: GPTAnalysis = serde_json::from_str(&filtered_folders)?; filtered_folder_list = analysis.analysis_key.clone(); } // 5. 列出最終選定的資料夾結構 println!("最終選定的資料夾為:\n{:#?}", filtered_folder_list); // 6. 為選定的資料夾收集檔案並生成摘要 let files_to_summarize = project.collect_files_to_summarize(&filtered_folder_list); // 定義進度狀態 let progress = Arc::new(RwLock::new(Progress { total_files: files_to_summarize.len(), completed_files: 0, summaries: HashMap::new(), })); // 共享的項目目錄結構 let project_arc = Arc::new(RwLock::new(project)); // 異步生成檔案摘要 let mut tasks = Vec::new(); for (file_path, _file_name) in files_to_summarize { let api_key_clone = api_key.clone(); let progress_clone = Arc::clone(&progress); let project_clone = Arc::clone(&project_arc); tasks.push(tokio::spawn(async move { let file_content = fs::read_to_string(&file_path).unwrap_or_default(); let summary = if file_content.trim().is_empty() { "檔案內容為空".to_string() } else { summarize_file_with_llama(file_content.clone()) .await .unwrap_or_else(|_| "摘要生成失敗".to_string()) }; // 更新進度 { let mut progress = progress_clone.write().await; progress.completed_files += 1; progress.summaries.insert(file_path.clone(), summary.clone()); } // 更新項目目錄結構中的摘要 { let mut project = project_clone.write().await; project.update_file_summary(&file_path, summary); } println!("已完成摘要:{}", file_path); })); } // 等待所有任務完成 join_all(tasks).await; // 從 Arc 中取出項目目錄結構 let project = Arc::try_unwrap(project_arc).unwrap().into_inner(); // 7. 準備啟動 Web 伺服器顯示Quick Project Report 和進度 let project_arc = Arc::new(RwLock::new(project)); let progress_arc = Arc::clone(&progress); // 定義 /filtered-tree 端點 let project_clone = Arc::clone(&project_arc); let filtered_tree_route = warp::path("filtered-tree") .and(warp::get()) .and_then({ let project_clone = Arc::clone(&project_clone); move || { let project_clone = Arc::clone(&project_clone); async move { let project = project_clone.read().await; Ok::<_, std::convert::Infallible>(warp::reply::json(&*project)) } } }); // 定義 /progress 端點 let progress_route = warp::path("progress") .and(warp::get()) .and_then({ let progress_arc = Arc::clone(&progress_arc); move || { let progress_arc = Arc::clone(&progress_arc); async move { let progress = progress_arc.read().await; Ok::<_, std::convert::Infallible>(warp::reply::json(&*progress)) } } }); let index_html = warp::path::end().map(|| { warp::reply::html( r#" <!DOCTYPE html> <html lang="zh-TW"> <head> <meta charset="UTF-8"> <title>Quick Project Report</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jstree/dist/themes/default/style.min.css" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prism/1.28.0/themes/prism-okaidia.min.css"> <style> body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background-color: #1e1e1e; /* 黑色背景 */ color: #d4d4d4; /* 淡灰色文字 */ display: flex; flex-direction: column; height: 100vh; } h1, h2 { text-align: center; color: #d4d4d4; } #controls { text-align: center; margin-bottom: 20px; } button { margin: 0 10px; padding: 10px 20px; font-size: 16px; background-color: #007acc; color: #fff; border: none; cursor: pointer; } button:hover { background-color: #005f99; } #main { display: flex; flex: 1; } #jstree { width: 30%; background-color: #252526; /* 深灰色背景 */ padding: 10px; overflow-y: auto; color: #d4d4d4; } #summary { width: 70%; padding: 20px; background-color: #1e1e1e; margin-left: 20px; overflow-y: auto; color: #d4d4d4; } pre { background-color: #1e1e1e; padding: 10px; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word; } code { font-family: Consolas, 'Courier New', monospace; } /* Tabs Style */ .tab-container { width: 100%; display: flex; justify-content: center; margin-bottom: 20px; } .tab { padding: 10px 20px; cursor: pointer; background-color: #007acc; color: white; margin: 0 5px; border: none; } .tab.active { background-color: #005f99; } .content-container { display: none; } .content-container.active { display: block; } </style> <script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/jstree@3.3.12/dist/jstree.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/prismjs@1.28.0/prism.min.js"></script> </head> <body> <h1>Quick Project Report </h1> <!-- Tabs --> <div class="tab-container"> <button class="tab active" onclick="showTab('file-tab')">檔案目錄與程式碼</button> <button class="tab" onclick="showTab('summary-tab')">總摘要</button> </div> <!-- Content: File Directory and Code --> <div id="file-tab" class="content-container active"> <div id="controls"> <button onclick="fetchTree()">顯示目錄樹</button> <button onclick="fetchProgress()">查看摘要進度</button> </div> <div id="main"> <div id="jstree"></div> <div id="summary"> <h2>檔案摘要和程式碼</h2> <div id="file-summary">請選擇一個檔案以查看摘要和程式碼。</div> </div> </div> </div> <!-- Content: Total Summary --> <div id="summary-tab" class="content-container"> <h2>總摘要</h2> <div id="progress"></div> </div> <script> let progressData = null; function showTab(tabId) { // Hide all content containers document.querySelectorAll('.content-container').forEach(tab => { tab.classList.remove('active'); }); // Remove 'active' class from all tabs document.querySelectorAll('.tab').forEach(tab => { tab.classList.remove('active'); }); // Show the selected tab and activate the corresponding button document.getElementById(tabId).classList.add('active'); document.querySelector(`[onclick="showTab('${tabId}')"]`).classList.add('active'); } async function fetchTree() { try { const response = await fetch('/filtered-tree'); const data = await response.json(); displayTree(data); } catch (error) { console.error('抓取目錄樹時出錯:', error); } } async function fetchProgress() { try { const response = await fetch('/progress'); const data = await response.json(); progressData = data; displayProgress(data, document.getElementById('progress')); } catch (error) { console.error('抓取進度時出錯:', error); } } function displayProgress(progress, parentElement) { parentElement.innerHTML = ''; const progressText = `已完成 ${progress.completed_files} / ${progress.total_files} 個摘要`; const progressDiv = document.createElement('div'); progressDiv.innerText = progressText; parentElement.appendChild(progressDiv); const summariesUl = document.createElement('ul'); for (const [filePath, summary] of Object.entries(progress.summaries)) { const li = document.createElement('li'); li.textContent = `${filePath}: ${summary}`; summariesUl.appendChild(li); } parentElement.appendChild(summariesUl); } function displayTree(directory) { const treeData = [convertToJsTreeFormat(directory)]; $('#jstree').jstree('destroy'); // 重置 jstree $('#jstree').jstree({ 'core': { 'data': treeData, 'themes': { 'variant': 'large', 'dots': true, 'icons': true } }, 'plugins': ['wholerow'] }); // 綁定節點點擊事件 $('#jstree').on('select_node.jstree', function (e, data) { const node = data.node; if (node.original && node.original.type === 'file') { const filePath = node.original.path; displayFileSummaryAndCode(filePath); showTab('file-tab'); // 點擊檔案後顯示檔案目錄和程式碼頁 } else { $('#file-summary').html('請選擇一個檔案以查看摘要和程式碼。'); } }); } function convertToJsTreeFormat(directory) { const node = { text: directory.name, children: [], state: { opened: true }, type: 'folder', path: directory.path }; directory.files.sort((a, b) => a.name.localeCompare(b.name)); for (const file of directory.files) { node.children.push({ text: file.name, type: 'file', path: `${directory.path}/${file.name}`, summary: file.summary || '無摘要', icon: 'jstree-file' }); } directory.subdirs.sort((a, b) => a.name.localeCompare(b.name)); for (const subdir of directory.subdirs) { node.children.push(convertToJsTreeFormat(subdir)); } return node; } async function displayFileSummaryAndCode(filePath) { if (!progressData) { $('#file-summary').html('請先點擊 "查看摘要進度" 以載入摘要資料。'); return; } const summary = progressData.summaries[filePath]; let codeContent = ''; try { const response = await fetch('/get-file?path=' + encodeURIComponent(filePath)); if (response.ok) { codeContent = await response.text(); } else { codeContent = '無法取得檔案內容。'; } } catch (error) { codeContent = '抓取檔案內容時出錯。'; } const fileExtension = filePath.split('.').pop().toLowerCase(); const languageClass = languageMapping[fileExtension] || 'plaintext'; const codeHtml = `<pre><code class="language-${languageClass}">${escapeHtml(codeContent)}</code></pre>`; Prism.highlightAll(); if (summary) { $('#file-summary').html(`<h3>摘要:</h3><p>${summary}</p><h3>程式碼:</h3>${codeHtml}`); } else { $('#file-summary').html(`<h3>摘要:</h3><p>此檔案沒有摘要。</p><h3>程式碼:</h3>${codeHtml}`); } } function escapeHtml(text) { return text .replace(/&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;') .replace(/"/g, '&quot;') .replace(/'/g, '&#039;'); } let languageMapping = { "rs": "rust", "py": "python", "js": "javascript", "ts": "typescript", "java": "java", "cpp": "cpp", "c": "c", "go": "go", "sh": "bash", "rb": "ruby", "bat": "batch", "cs": "csharp", "resx": "xml", "h": "clike", "md": "markdown" }; </script> </body> </html> "# ) }); // 添加新的路由來處理檔案內容請求 let get_file_route = warp::path("get-file") .and(warp::get()) .and(warp::query::<HashMap<String, String>>()) .and_then({ move |params: HashMap<String, String>| async move { let response = if let Some(path) = params.get("path") { if let Ok(content) = fs::read_to_string(path) { warp::reply::html(content).into_response() } else { warp::reply::with_status( warp::reply::html("無法取得檔案內容。"), warp::http::StatusCode::NOT_FOUND, ) .into_response() } } else { warp::reply::with_status( warp::reply::html("無法取得檔案內容。"), warp::http::StatusCode::NOT_FOUND, ) .into_response() }; Ok::<_, std::convert::Infallible>(response) } }); // 合併所有路由 let routes = filtered_tree_route .or(progress_route) .or(get_file_route) .or(index_html); // 啟動伺服器 println!("啟動網頁伺服器,請訪問 http://127.0.0.1:{}", SERVER_PORT); warp::serve(routes) .run(([127, 0, 0, 1], SERVER_PORT)) .await; Ok(()) } ``` ![image](https://hackmd.io/_uploads/BJEqlpUA0.png) 這邊將 .post("http://127.0.0.1:9090/completion") gpu 竟然開始滿載工作了這樣才會有內容上下文關聯嗎(? ![image](https://hackmd.io/_uploads/HyciF6LCR.png) 至少沒出現跳針