This commit is contained in:
2024-07-17 17:54:48 +02:00
commit 4991467d32
26 changed files with 3473 additions and 0 deletions

12
frontend/Cargo.toml Normal file
View File

@@ -0,0 +1,12 @@
[package]
name = "allpaca"
version = "0.1.0"
edition = "2021"
[dependencies]
allpaca-models = { workspace = true }
reqwest = { workspace = true }
prost = { workspace = true }
yew = { version = "0.21.0", features = ["csr"] }
wasm-bindgen = "0.2.92"
gloo-net = "0.5.0"

5
frontend/Dockerfile Normal file
View File

@@ -0,0 +1,5 @@
FROM rust:slim
RUN rustup default nightly
RUN rustup target add wasm32-unknown-unknown
RUN cargo install trunk
CMD [ "trunk", "watch" ]

11
frontend/build.rs Normal file
View File

@@ -0,0 +1,11 @@
#[cfg(not(debug_assertions))]
pub static BASE: &'static str = "https://gpt.42069.no/api";
#[cfg(debug_assertions)]
pub static BASE: &'static str = "http://localhost/api";
fn main() {
if cfg!(debug_assertions) {
println!("cargo::rustc-env=BASE={}", BASE);
}
}

10
frontend/index.html Normal file
View File

@@ -0,0 +1,10 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Allpaca</title>
<link data-trunk rel="rust" />
<link data-trunk rel="css" href="./styles.css" />
</head>
<body></body>
</html>

4
frontend/src/api/mod.rs Normal file
View File

@@ -0,0 +1,4 @@
pub mod models;
pub static BASE: &'static str = env!("BASE");

View File

@@ -0,0 +1,18 @@
use prost::Message;
use crate::Models;
macro_rules! path {
($path:expr) => {
super::BASE.to_owned() + "/models" + $path
};
}
pub async fn list() -> Result<Models, String> {
match Models::decode(
reqwest::get(path!("/list"))
.await.unwrap().text().await.unwrap().as_bytes()
) {
Err(_err) => Err("API response was not valid protobuf format".to_owned()),
Ok(models) => Ok(models),
}
}

View File

@@ -0,0 +1,59 @@
use yew::prelude::*;
pub struct Message {
pub content: String,
pub is_user: bool,
}
pub enum ChatMsg {
Send
}
pub struct Chat {
pub history: Vec<Message>,
}
impl Component for Chat {
type Properties = ();
type Message = ChatMsg;
fn create(_ctx: &Context<Self>) -> Self {
Self { history: vec![
Message {
content: "How may I help you today?".to_owned(),
is_user: false,
}]
}
}
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
ChatMsg::Send => false,
}
}
fn view(&self, ctx: &Context<Self>) -> Html {
html! {
<div>
<div class="chat">
{
self.history.iter().map(|message| {
html! {
<div class={ if message.is_user { "user" } else { "assistant" } }>
<p>{ &message.content }</p>
</div>
}
}).collect::<Html>()
}
</div>
<form class="chat">
<div>
<input type="textfield" required=true />
</div>
<div>
<button onclick={
ctx.link().callback(|_| ChatMsg::Send)
}>{ "Send" }</button>
</div>
</form>
</div>
}
}
}

View File

@@ -0,0 +1,53 @@
use yew::prelude::*;
pub mod models;
pub mod chat;
pub mod nav;
pub use chat::Chat;
pub enum FetchMsg {
Response(String), Fetch,
}
pub struct Fetcher {
res: String,
}
impl From<String> for FetchMsg {
fn from(value: String) -> Self {
Self::Response(value)
}
}
impl Component for Fetcher {
type Properties = ();
type Message = FetchMsg;
fn create(_ctx: &Context<Self>) -> Self {
Self { res: String::new() }
}
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
FetchMsg::Response(res) => {
self.res = res;
true
},
FetchMsg::Fetch => {
ctx.link().send_future((async move || {
FetchMsg::from(reqwest::get("http://localhost/api").await.unwrap().text().await.unwrap())
})());
false
},
}
}
fn view(&self, ctx: &Context<Self>) -> Html {
html! {
<div>
<button onclick={
ctx.link().callback(|_| FetchMsg::Fetch)
}>{ "balls" }</button>
<p>{ &self.res }</p>
</div>
}
}
}

View File

@@ -0,0 +1,57 @@
use yew::{Component, Html, html};
use crate::{STORE, ModelsResult, api};
pub struct List(pub Option<ModelsResult>);
pub enum ListMsg {
List(ModelsResult)
}
impl Component for List {
type Properties = ();
type Message = ListMsg;
fn create(ctx: &yew::Context<Self>) -> Self {
let models = STORE.models.lock().unwrap();
if models.is_none() {
ctx.link().send_future((async move || {
ListMsg::List(api::models::list().await)
})());
Self(None)
} else {
Self(models.clone())
}
}
fn update(&mut self, _ctx: &yew::Context<Self>, msg: Self::Message) -> bool {
match msg {
ListMsg::List(models) => self.0 = Some(models),
}
true
}
fn view(&self, _ctx: &yew::Context<Self>) -> Html {
match &self.0 {
None => html! { <div>{ "Loading..." }</div> },
Some(result) => {
match result {
Err(err) => html! { <div class="error">{ err }</div> },
Ok(models) => html! {
<ul class="model-list">
{
models.0.iter().map(|model| {
let size = model.size.parse::<usize>().unwrap();
html! {
<li>
<ul>
<li>{ "Name: "} { &model.name }</li>
<li>{ "Size: "} { size / ( 1024 * 1024) } { "MiB" }</li>
<li>{ "Modified at: "} { &model.modified_at }</li>
</ul>
</li>
}
}).collect::<Html>()
}
</ul>
},
}
}
}
}
}

View File

@@ -0,0 +1,2 @@
mod list;
pub use list::List;

View File

@@ -0,0 +1,23 @@
use yew::prelude::*;
use allpaca_models::Model;
#[derive(Clone, PartialEq, Properties)]
pub struct Props {
// models: Vec<Model>,
}
#[function_component]
pub fn Bar(props: &Props) -> Html {
html! {
<ul>
<li class="model-dropdown">
<select>
// { props.models.iter().map(|model| {
// html! { <option>{model.name.clone()}</option> }
// }).collect::<Html>() }
</select>
</li>
</ul>
}
}

35
frontend/src/lib.rs Normal file
View File

@@ -0,0 +1,35 @@
#![feature(const_trait_impl)]
#![feature(async_closure)]
use std::sync::Mutex;
use yew::Callback;
pub mod components;
pub mod api;
use allpaca_models::Models;
pub type ModelsResult = Result<Models, String>;
pub struct Store {
pub models: Mutex<Option<ModelsResult>>,
}
pub static STORE: Store = Store {
models: Mutex::new(None),
};
impl Store {
fn set_models(&mut self, models: ModelsResult) {
*self.models.lock().unwrap() = Some(models);
}
}
use wasm_bindgen::prelude::wasm_bindgen;
#[cfg(debug_assertions)]
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
pub fn log(s: &str);
}

26
frontend/src/main.rs Normal file
View File

@@ -0,0 +1,26 @@
use yew::prelude::*;
use allpaca::components::*;
#[cfg(debug_assertions)]
use allpaca::log;
#[function_component]
fn App() -> Html {
html! {
<>
<div class="navbar">
<nav::Bar></nav::Bar>
</div>
<models::List></models::List>
<Chat></Chat>
<div>
<Fetcher></Fetcher>
</div>
</>
}
}
fn main() {
yew::Renderer::<App>::new().render();
}

12
frontend/styles.css Normal file
View File

@@ -0,0 +1,12 @@
body {
background-color: #333;
color: lime;
}
.navbar, .model-list {
list-style: none;
}
.model-list > li {
margin: 1vh;
}