WIP Rework markdown parsing

This commit is contained in:
Tristan D. 2025-02-10 17:12:00 +01:00
parent 604410ef61
commit d39459f2a9
Signed by: tristan
SSH key fingerprint: SHA256:9oFM1J63hYWJjCnLG6C0fxBS15rwNcWwdQNMOHYKJ/4
11 changed files with 109 additions and 42 deletions

38
Cargo.lock generated
View file

@ -1730,6 +1730,15 @@ dependencies = [
"windows-targets 0.48.5", "windows-targets 0.48.5",
] ]
[[package]]
name = "getopts"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
dependencies = [
"unicode-width",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.1.16" version = "0.1.16"
@ -2869,7 +2878,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"windows-targets 0.48.5", "windows-targets 0.52.6",
] ]
[[package]] [[package]]
@ -2955,6 +2964,7 @@ dependencies = [
"mime_guess", "mime_guess",
"once_cell", "once_cell",
"pin-project-lite", "pin-project-lite",
"pulldown-cmark",
"rand 0.8.5", "rand 0.8.5",
"regex", "regex",
"reqwest", "reqwest",
@ -4039,6 +4049,26 @@ dependencies = [
"psl-types", "psl-types",
] ]
[[package]]
name = "pulldown-cmark"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f86ba2052aebccc42cbbb3ed234b8b13ce76f75c3551a303cb2bcffcff12bb14"
dependencies = [
"bitflags 2.8.0",
"getopts",
"memchr",
"pulldown-cmark-escape",
"serde",
"unicase",
]
[[package]]
name = "pulldown-cmark-escape"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae"
[[package]] [[package]]
name = "quick-xml" name = "quick-xml"
version = "0.37.2" version = "0.37.2"
@ -6166,6 +6196,12 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "unicode-width"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]] [[package]]
name = "unicode-xid" name = "unicode-xid"
version = "0.2.6" version = "0.2.6"

View file

@ -83,6 +83,7 @@ tracing-test = "0.2.4"
sysinfo = { version = "0.30.11", optional = true } sysinfo = { version = "0.30.11", optional = true }
derive_more = { version = "0.99.17", features = ["nightly"] } derive_more = { version = "0.99.17", features = ["nightly"] }
sqlx-macros = { version = "0.7.4", optional = true, features = ["chrono", "json", "migrate", "sqlite", "uuid"] } sqlx-macros = { version = "0.7.4", optional = true, features = ["chrono", "json", "migrate", "sqlite", "uuid"] }
pulldown-cmark = { version = "0.12.2", features = ["serde"] }
# qdrant-client = "1.11.2" # qdrant-client = "1.11.2"
# swiftide = "0.9.1" # swiftide = "0.9.1"

View file

@ -48,11 +48,15 @@ pub async fn run_starter_task(pool: sqlx::SqlitePool) {
use crate::server::backends::BackendService; use crate::server::backends::BackendService;
let _ = tracing::debug_span!("starter_task"); let _ = tracing::debug_span!("starter_task");
tracing::debug!("AAAAAAAAAAAAAAA");
return; // TODO ????
tracing::debug!("Starter task started"); tracing::debug!("Starter task started");
let service_handle = BackendService::new(); let service_handle = BackendService::new();
let mut stream = IntervalStream::new(time::interval(Duration::from_millis(1000))); let mut stream = IntervalStream::new(time::interval(Duration::from_millis(1000)));
while let Some(instant) = stream.next().await { while let Some(instant) = stream.next().await {
break; // TODO integrate proxy man ?
tracing::debug!("fire; instant={:?}", instant); tracing::debug!("fire; instant={:?}", instant);
let waiting_to_start: Vec<BackendProcess> = sqlx::query_as( let waiting_to_start: Vec<BackendProcess> = sqlx::query_as(

View file

@ -41,7 +41,8 @@ pub fn App() -> impl IntoView {
<Routes> <Routes>
<Route path="" view=MainPage> <Route path="" view=MainPage>
<Route path="/chat" view=ChatPage/> <Route path="/chat" view=ChatPage/>
<SettingsRoutes/> // TODO make settings page for proxy-man
// <SettingsRoutes/>
</Route> </Route>
</Routes> </Routes>
</main> </main>

View file

@ -12,7 +12,7 @@ use crate::{
app::components::{svgs::*, Card}, app::components::{svgs::*, Card},
}; };
#[component] #[island]
fn ChatMessageBubble( fn ChatMessageBubble(
msg: RwSignal<ChatMessage>, msg: RwSignal<ChatMessage>,
history: RwSignal<Vec<RwSignal<ChatMessage>>>, history: RwSignal<Vec<RwSignal<ChatMessage>>>,
@ -33,12 +33,37 @@ fn ChatMessageBubble(
let textarea_ref = NodeRef::<html::P>::new(); let textarea_ref = NodeRef::<html::P>::new();
use pulldown_cmark;
let editable_p = move || { let editable_p = move || {
// TODO Convert back to raw str when editable
let mode = if edit_mode.get() { "true" } else { "false" }; let mode = if edit_mode.get() { "true" } else { "false" };
let msg_str = move || msg.get().content.clone();
let md_str = move || {
let owned_str = msg_str();
let parser = pulldown_cmark::Parser::new(&owned_str);
let mut md_output = String::new();
pulldown_cmark::html::push_html(&mut md_output, parser);
md_output
};
let inner_p = move || {
if edit_mode.get() {
view! {
<p inner_html=move || { msg_str() }></p>
}
} else {
view! {
<p inner_html=move || { md_str() }></p>
}
}
};
view! { view! {
<p node_ref=textarea_ref contenteditable=move || { mode }> <p node_ref=textarea_ref contenteditable=move || { mode }>
<span inner_html=msg.get().content.clone()></span> {inner_p}
</p> </p>
} }
}; };

View file

@ -11,16 +11,16 @@ use crate::api::{ChannelMessage, Chat, ChatMessage};
#[derive(Serialize, Debug)] #[derive(Serialize, Debug)]
struct LlamaChatCompletionRequest { struct LlamaChatCompletionRequest {
stream: bool, stream: bool,
model: String, model: String,
messages: Vec<LlamaChatMessage>, messages: Vec<LlamaChatMessage>,
} }
impl From<Chat> for LlamaChatCompletionRequest { impl From<Chat> for LlamaChatCompletionRequest {
fn from(value: Chat) -> Self { fn from(value: Chat) -> Self {
Self { Self {
stream: true, stream: true,
model: "default".to_string(), model: "default".to_string(),
messages: value.history.into_iter().map(|e| e.into()).collect(), messages: value.history.into_iter().map(|e| e.into()).collect(),
} }
} }
@ -28,14 +28,14 @@ impl From<Chat> for LlamaChatCompletionRequest {
#[derive(Serialize, Debug)] #[derive(Serialize, Debug)]
struct LlamaChatMessage { struct LlamaChatMessage {
role: String, role: String,
content: String, content: String,
} }
impl From<ChatMessage> for LlamaChatMessage { impl From<ChatMessage> for LlamaChatMessage {
fn from(chat_message: ChatMessage) -> Self { fn from(chat_message: ChatMessage) -> Self {
Self { Self {
role: chat_message.role.into(), role: chat_message.role.into(),
content: chat_message.content, content: chat_message.content,
} }
} }
@ -68,9 +68,7 @@ pub struct LlamaService {
impl LlamaService { impl LlamaService {
pub fn new(id: Uuid) -> Self { pub fn new(id: Uuid) -> Self {
Self { Self { id }
id,
}
} }
} }
@ -80,7 +78,8 @@ async fn do_chat_request(chat: Chat, sender: mpsc::Sender<ChannelMessage>) -> an
let request_body: LlamaChatCompletionRequest = chat.into(); let request_body: LlamaChatCompletionRequest = chat.into();
let request_builder = client let request_builder = client
.post("http://localhost:8080/v1/chat/completions") // # .post("http://localhost:8080/v1/chat/completions")
.post("http://100.64.0.3:18080/v1/chat/completions")
.header("Content-Type", "application/json") .header("Content-Type", "application/json")
.json(&request_body); .json(&request_body);
@ -92,7 +91,19 @@ async fn do_chat_request(chat: Chat, sender: mpsc::Sender<ChannelMessage>) -> an
Ok(Event::Message(event)) => match event.event.as_str() { Ok(Event::Message(event)) => match event.event.as_str() {
"message" => { "message" => {
let data = event.data; let data = event.data;
let response: LlamaChatResponse = serde_json::from_str(&data).unwrap(); tracing::debug!(?data);
if data == "[DONE]" {
sender
.send(ChannelMessage::Stop)
.await
.expect("channel fail");
es.close();
break;
}
let response: LlamaChatResponse = serde_json::from_str(&data).expect("no json");
for choice in response.choices.into_iter() { for choice in response.choices.into_iter() {
if let Some(delta) = choice.delta { if let Some(delta) = choice.delta {

View file

@ -112,7 +112,7 @@ pub async fn do_completion_request() -> Result<(), Box<dyn std::error::Error>> {
let request_body = CompletionRequest::default(); let request_body = CompletionRequest::default();
let request_builder = client let request_builder = client
.post("http://localhost:8080/completion") .post("http://100.64.0.3:18080/completion")
.header("Accept", "text/event-stream") .header("Accept", "text/event-stream")
.header("Content-Type", "application/json") .header("Content-Type", "application/json")
.header("User-Agent", "llama_forge_rs") .header("User-Agent", "llama_forge_rs")

View file

@ -177,10 +177,7 @@ mod tests {
use crate::{ use crate::{
api::{ChannelMessage, ChatMessage, ChatRole}, api::{ChannelMessage, ChatMessage, ChatRole},
server::backends::{ server::backends::{
llama_chat::LlamaService, llama_chat::LlamaService, BackendService, BackendServiceStatus, ChatService,
BackendService,
BackendServiceStatus,
ChatService,
}, },
}; };

View file

@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
use tokio::process::Command; use tokio::process::Command;
pub struct RunnerArgs { pub struct RunnerArgs {
ctx_size: i64, ctx_size: i64,
gpu_layers: i64, gpu_layers: i64,
model_path: String, model_path: String,
} }
@ -36,8 +36,8 @@ impl From<RunnerArgs> for Vec<String> {
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Runner { pub struct Runner {
pwd: Option<String>, pwd: Option<String>,
cmd: String, cmd: String,
args: Vec<String>, args: Vec<String>,
} }
@ -45,8 +45,8 @@ impl Runner {
// FIXME does not exit properly when it is killed // FIXME does not exit properly when it is killed
pub fn new_llamafile_bin(runner_args: RunnerArgs) -> Self { pub fn new_llamafile_bin(runner_args: RunnerArgs) -> Self {
Self { Self {
pwd: None, pwd: None,
cmd: "bash".to_string(), cmd: "bash".to_string(),
args: vec![ args: vec![
format!( format!(
"{}/llamafile", "{}/llamafile",
@ -64,8 +64,8 @@ impl Runner {
pub fn new_llama_server_bin(runner_args: RunnerArgs) -> Self { pub fn new_llama_server_bin(runner_args: RunnerArgs) -> Self {
Self { Self {
pwd: None, pwd: None,
cmd: "llama-server".to_string(), cmd: "llama-server".to_string(),
args: runner_args.into(), args: runner_args.into(),
} }
} }

View file

@ -18,9 +18,7 @@ impl<S> Layer<S> for LoggingLayer {
type Service = LoggingService<S>; type Service = LoggingService<S>;
fn layer(&self, inner: S) -> Self::Service { fn layer(&self, inner: S) -> Self::Service {
LoggingService { LoggingService { inner }
inner,
}
} }
} }
@ -50,8 +48,8 @@ where
LoggingServiceFuture { LoggingServiceFuture {
inner: self.inner.call(req), inner: self.inner.call(req),
uuid: Arc::new(request_uuid), // Store UUID in an Arc for shared ownership uuid: Arc::new(request_uuid), // Store UUID in an Arc for shared ownership
span: Arc::new(span), span: Arc::new(span),
} }
} }
} }

View file

@ -6,26 +6,20 @@ use axum::{
http::Request, http::Request,
response::IntoResponse, response::IntoResponse,
routing::get, routing::get,
Extension, Extension, Router,
Router,
}; };
use leptos::*; use leptos::*;
use leptos_axum::{generate_route_list, handle_server_fns_with_context, LeptosRoutes}; use leptos_axum::{generate_route_list, handle_server_fns_with_context, LeptosRoutes};
use leptos_router::RouteListing; use leptos_router::RouteListing;
use sqlx::{ use sqlx::{
sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions, SqliteSynchronous}, sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions, SqliteSynchronous},
ConnectOptions, ConnectOptions, SqlitePool,
SqlitePool,
}; };
use tower::Layer; use tower::Layer;
use tower_http::{ use tower_http::{
compression::CompressionLayer, compression::CompressionLayer,
trace::{ trace::{
DefaultMakeSpan, DefaultMakeSpan, DefaultOnEos, DefaultOnFailure, DefaultOnRequest, DefaultOnResponse,
DefaultOnEos,
DefaultOnFailure,
DefaultOnRequest,
DefaultOnResponse,
TraceLayer, TraceLayer,
}, },
CompressionLevel, CompressionLevel,
@ -122,7 +116,7 @@ pub async fn app(leptos_options: LeptosOptions) -> Router {
let pool = new_pool().await.expect("pool err"); let pool = new_pool().await.expect("pool err");
// TODO move this out of server(pool has to be moved out too) // // TODO move this out of server(pool has to be moved out too)
let task = run_starter_task(pool.clone()); let task = run_starter_task(pool.clone());
tokio::task::spawn(task); tokio::task::spawn(task);