batman
This commit is contained in:
12
frontend/Cargo.toml
Normal file
12
frontend/Cargo.toml
Normal 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
5
frontend/Dockerfile
Normal 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
11
frontend/build.rs
Normal 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
10
frontend/index.html
Normal 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
4
frontend/src/api/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
pub mod models;
|
||||
|
||||
pub static BASE: &'static str = env!("BASE");
|
18
frontend/src/api/models.rs
Normal file
18
frontend/src/api/models.rs
Normal 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),
|
||||
}
|
||||
}
|
59
frontend/src/components/chat.rs
Normal file
59
frontend/src/components/chat.rs
Normal 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>
|
||||
}
|
||||
}
|
||||
}
|
53
frontend/src/components/mod.rs
Normal file
53
frontend/src/components/mod.rs
Normal 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>
|
||||
}
|
||||
}
|
||||
}
|
57
frontend/src/components/models/list.rs
Normal file
57
frontend/src/components/models/list.rs
Normal 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>
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
2
frontend/src/components/models/mod.rs
Normal file
2
frontend/src/components/models/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
mod list;
|
||||
pub use list::List;
|
23
frontend/src/components/nav.rs
Normal file
23
frontend/src/components/nav.rs
Normal 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
35
frontend/src/lib.rs
Normal 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
26
frontend/src/main.rs
Normal 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
12
frontend/styles.css
Normal file
@@ -0,0 +1,12 @@
|
||||
body {
|
||||
background-color: #333;
|
||||
color: lime;
|
||||
}
|
||||
|
||||
.navbar, .model-list {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.model-list > li {
|
||||
margin: 1vh;
|
||||
}
|
Reference in New Issue
Block a user