Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Append LLM result into DOM #144

Merged
merged 12 commits into from
Sep 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 95 additions & 70 deletions fireSeqSearch_addon/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,72 +142,120 @@ function parseRawList(rawSearchResult) {
return hits;
}

function createTitleBarDom(count) {
const titleBar = createElementWithText("div");
titleBar.classList.add('fireSeqSearchTitleBar');
const hitCount = `<span>We found <b>${count.toString()}</b> results in your logseq notebook</span>`;
titleBar.insertAdjacentHTML("afterbegin",hitCount);

function setSummaryState(cl, state) {
let prop = 'none';
if (state) { prop = ''; }
for (const el of document.querySelectorAll(cl)) {
el.style.display=prop;
async function processLlmSummary(serverInfo, parsedSearchResult, fireDom) {

const doneListApi = "http://127.0.0.1:3030/llm_done_list";
let list = await fetch(doneListApi);
list = await list.text();
list = JSON.parse(list);

const findByTitle = function(title) {
const ul = fireDom.querySelector( ".fireSeqSearchHitList" );
if (ul === null) return null;
for (const child of ul.children) {
const liTitle = child.firstChild.text;
if (title === liTitle) {
return child;
}
}
}

let btn = document.createElement("button");
btn.classList.add("hideSummary");
let text = document.createTextNode("Hide Summary");
btn.appendChild(text);
btn.onclick = function () {
setSummaryState(".fireSeqSearchHitSummary", false);
setSummaryState(".fireSeqSearchLlmSummary", false);
};
titleBar.appendChild(btn);

btn = document.createElement("button");
btn.classList.add("showSummary");
text = document.createTextNode("Summary");
btn.appendChild(text);
btn.onclick = function () {
setSummaryState(".fireSeqSearchHitSummary", true);
setSummaryState(".fireSeqSearchLlmSummary", false);
return null;
};
titleBar.appendChild(btn);

const setLlmResult = function (title, llmSummary) {
const targetRow = findByTitle(title);
if (targetRow === null) {
consoleLogForDebug("Error! Can't find dom for ", title);
return;
}
if (targetRow.querySelector( ".fireSeqSearchLlmSummary" ) != null) {
consoleLogForDebug("Skip. We have the summary for ", title);
return;
}

btn = document.createElement("button");
btn.classList.add("showLlm");
text = document.createTextNode("LLM");
btn.appendChild(text);
btn.onclick = function () {
setSummaryState(".fireSeqSearchHitSummary", false);
setSummaryState(".fireSeqSearchLlmSummary", true);
const summary = createElementWithText("span", "");
summary.innerHTML = llmSummary;
summary.classList.add('fireSeqSearchLlmSummary');
targetRow.appendChild(summary);
};
titleBar.appendChild(btn);
for (const record of parsedSearchResult) {
const title = record.title;
if (!list.includes(title)) {
consoleLogForDebug("Not ready, skip" + title);
continue;
}
// TODO remove hard code port
const llm_api = "http://127.0.0.1:3030/summarize/" + title;
let sum = await fetch(llm_api);
sum = await sum.text();
setLlmResult(title, sum);
}
}


return titleBar;
}
function createFireSeqDom(count) {
function createFireSeqDom(serverInfo, parsedSearchResult) {
const count = parsedSearchResult.length;
const div = document.createElement("div");
div.setAttribute("id", fireSeqSearchDomId);
const bar = createTitleBarDom(count);

const createTitleBarDom = function () {
const titleBar = createElementWithText("div");
titleBar.classList.add('fireSeqSearchTitleBar');
const hitCount = `<span>We found <b>${count.toString()}</b> results in your logseq notebook</span>`;
titleBar.insertAdjacentHTML("afterbegin",hitCount);

function setSummaryState(cl, state) {
let prop = 'none';
if (state) { prop = ''; }
for (const el of document.querySelectorAll(cl)) {
el.style.display=prop;
}
}
let btn = document.createElement("button");
btn.classList.add("hideSummary");
let text = document.createTextNode("Hide Summary");
btn.appendChild(text);
btn.onclick = function () {
setSummaryState(".fireSeqSearchHitSummary", false);
setSummaryState(".fireSeqSearchLlmSummary", false);
};
titleBar.appendChild(btn);

btn = document.createElement("button");
btn.classList.add("showSummary");
text = document.createTextNode("Summary");
btn.appendChild(text);
btn.onclick = function () {
setSummaryState(".fireSeqSearchHitSummary", true);
setSummaryState(".fireSeqSearchLlmSummary", false);
};
titleBar.appendChild(btn);

btn = document.createElement("button");
btn.classList.add("showLlm");
text = document.createTextNode("LLM");
btn.appendChild(text);
btn.onclick = function () {
setSummaryState(".fireSeqSearchHitSummary", false);
setSummaryState(".fireSeqSearchLlmSummary", true);
processLlmSummary(serverInfo, parsedSearchResult, div);
};
titleBar.appendChild(btn);
return titleBar;
};
const bar = createTitleBarDom();
div.appendChild(bar);
return div;
}

async function appendResultToSearchResult(serverInfo, parsedSearchResult, dom) {
const firefoxExtensionUserOption = await checkUserOptions();

consoleLogForDebug('Loaded user option: ' + JSON.stringify(firefoxExtensionUserOption));


function buildListItems(parsedSearchResult) {
const hitList = document.createElement("ul");
hitList.classList.add('fireSeqSearchHitList');
for (const record of parsedSearchResult) {
const li = createElementWithText("li", "");
li.classList.add('fireSeqSearchHitListItem');
if (firefoxExtensionUserOption.ShowScore) {
const score = createElementWithText("span", String(record.score));
li.appendChild(score);
Expand Down Expand Up @@ -252,18 +300,6 @@ async function appendResultToSearchResult(serverInfo, parsedSearchResult, dom) {
insertDivToWebpage(dom);
}

async function processLlmSummary(serverInfo, parsedSearchResult, dom) {
for (const record of parsedSearchResult) {
// TODO remove hard code port
const llm_api = "http://127.0.0.1:3030/summarize/" + record.title;
console.log("llm called");
console.log(record.title);
const response = await fetch(llm_api);
const text = await response.text();
console.log(text);
}
}

async function mainProcess(fetchResultArray) {
consoleLogForDebug("main process");

Expand All @@ -272,18 +308,10 @@ async function mainProcess(fetchResultArray) {
consoleLogForDebug(serverInfo);
const parsedSearchResult = parseRawList(rawSearchResult);

console.log("in main");
console.log(rawSearchResult);
console.log(parsedSearchResult);

const fireDom = createFireSeqDom(parsedSearchResult.length);
const fireDom = createFireSeqDom(serverInfo, parsedSearchResult);

appendResultToSearchResult(serverInfo, parsedSearchResult, fireDom);

if (serverInfo.llm_enabled) {
consoleLogForDebug("llm");
processLlmSummary(serverInfo, parsedSearchResult, fireDom);
}
}


Expand Down Expand Up @@ -318,7 +346,6 @@ function getSearchParameterFromCurrentPage() {
(function() {
const searchParameter = getSearchParameterFromCurrentPage();


addGlobalStyle(fireSeqSearchScriptCSS);

//https://gomakethings.com/waiting-for-multiple-all-api-responses-to-complete-with-the-vanilla-js-promise.all-method/
Expand All @@ -328,9 +355,7 @@ function getSearchParameterFromCurrentPage() {
]).then(function (responses) {
return Promise.all(responses.map(function (response) {return response.json();}));
}).then(function (data) {
//consoleLogForDebug(data);
mainProcess(data);
//return appendResultToSearchResult(data);
}).then((_e) => {
const highlightedItems = document.querySelectorAll('.fireSeqSearchHighlight');
consoleLogForDebug(highlightedItems);
Expand Down
11 changes: 10 additions & 1 deletion fire_seq_search_server/src/http_client/endpoints.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::sync::Arc;
use log::debug;
use log::{debug, info};

use crate::query_engine::{QueryEngine, ServerInformation};
use axum::Json;
Expand Down Expand Up @@ -30,6 +30,15 @@ pub async fn summarize(
Html(r.await)
}

pub async fn get_llm_done_list(
State(engine_arc): State<Arc<QueryEngine>>
) -> Html<String>{

info!("get list endpoint called");
let r = engine_arc.get_llm_done_list();
Html(r.await)
}

pub async fn generate_word_cloud(State(engine_arc): State<Arc<QueryEngine>>)
-> Html<String> {
let div_id = "fireSeqSearchWordcloudRawJson";
Expand Down
55 changes: 35 additions & 20 deletions fire_seq_search_server/src/local_llm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ pub struct Usage {
pub total_tokens: i64,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct HealthCheck {
pub slots_idle: i64,
pub slots_processing: i64,
pub status: String,
}

// End genereated

const LLM_SERVER_PORT: &str = "8081"; // TODO Remove this magic number
Expand Down Expand Up @@ -165,7 +172,6 @@ impl LlmEngine {

impl LlmEngine{
pub async fn summarize(&self, full_text: &str) -> String {
info!("summarize called");
//http://localhost:8080/completion
let ep = self.endpoint.to_owned() + "/v1/chat/completions";
let data = Self::build_data(full_text);
Expand All @@ -175,17 +181,12 @@ impl LlmEngine{
.send()
.await
.unwrap();
//info!(" response {:?}", &res);
let content = res.text().await.unwrap();
//info!(" text {:?}", &content);
let parsed: LlamaResponse = serde_json::from_str(&content).unwrap();
//info!(" parsed {:?}", &parsed);
let v = parsed.choices;
let v0 = v.into_iter().next().unwrap();
v0.message.content


//TODO remove unwrap
//TODO remove unwrap
}

pub async fn post_summarize_job(&self, doc: DocData) {
Expand All @@ -196,13 +197,19 @@ impl LlmEngine{
}

pub async fn call_llm_engine(&self) {

let health = self.health().await.unwrap();
if health.slots_idle == 0 {
info!("No valid slot, continue");
return;
}

let mut next_job: Option<DocData> = None;

let mut jcache = self.job_cache.lock().await;//.unwrap();
next_job = jcache.job_queue.pop_front();
drop(jcache);


let doc = match next_job {
Some(x) => x,
None => { return; },
Expand All @@ -216,31 +223,39 @@ impl LlmEngine{
}
drop(jcache);

info!("Start summarize job: {}", &title);
let summarize_result = self.summarize(&doc.body).await;
info!("Finished summarize job: {}", &title);

let mut jcache = self.job_cache.lock().await;//.unwrap();
next_job = jcache.job_queue.pop_front();
info!("get summarize result {}", &title);
jcache.done_job.insert(title, summarize_result);
drop(jcache);

}

pub async fn quick_fetch(&self, title: &str) -> Option<String> {
let jcache = self.job_cache.lock().await;
return jcache.done_job.get(title).cloned();
}

pub async fn health(&self) -> Result<(), Box<dyn std::error::Error>> {
info!("Calling health check");
let resp = reqwest::get(self.endpoint.to_owned() + "/health")
.await?
.headers().to_owned()
//.status()
//.text().await?
;
info!("Health check: {:#?}", resp);
Ok(())
pub async fn get_llm_done_list(&self) -> Vec<String> {
let mut r = Vec::new();
let jcache = self.job_cache.lock().await;
for (title, _text) in &jcache.done_job {
info!("already done : {}", &title);
r.push(title.to_owned());
}
return r;
}

pub async fn health(&self) -> Result<HealthCheck, Box<dyn std::error::Error>> {
let res = self.client.get(self.endpoint.to_owned() + "/health")
.send()
.await
.unwrap();
let content = res.text().await.unwrap();
let parsed: HealthCheck = serde_json::from_str(&content).unwrap();
Ok(parsed)
}
}

Expand Down
3 changes: 1 addition & 2 deletions fire_seq_search_server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,10 @@ async fn main() {
engine.llm = Some(llm_arc);

let poll_handle = tokio::spawn( async move {
info!("inside main loop");
loop {
llm_poll.call_llm_engine().await;
let wait_llm = tokio::time::Duration::from_millis(500);
tokio::time::sleep(wait_llm).await;
info!("main loop: poll again");
}
});
// poll_handle.await;
Expand All @@ -97,6 +95,7 @@ async fn main() {
.route("/server_info", get(endpoints::get_server_info))
.route("/wordcloud", get(endpoints::generate_word_cloud))
.route("/summarize/:title", get(endpoints::summarize))
.route("/llm_done_list", get(endpoints::get_llm_done_list))
.with_state(engine_arc.clone());

let listener = tokio::net::TcpListener::bind(&engine_arc.server_info.host)
Expand Down
10 changes: 10 additions & 0 deletions fire_seq_search_server/src/query_engine/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,16 @@ impl QueryEngine {
"LLM turned off".to_owned()
}
}
pub async fn get_llm_done_list(&self) -> String {
if cfg!(feature="llm") {
let llm = self.llm.as_ref().unwrap();
let result = &llm.get_llm_done_list().await;
let json = serde_json::to_string(&result).unwrap();
return json;
} else {
"LLM turned off".to_owned()
}
}
}

fn term_preprocess(term:String) -> String {
Expand Down
Loading