we do a bit of solidifying :trolley:

master
Tymon 1 year ago
parent 7bdcce22a8
commit add896662f

1
.gitignore vendored

@ -1 +0,0 @@
/target

@ -0,0 +1,8 @@
{
"rust-analyzer.linkedProjects": [
".\\backend\\Cargo.toml"
],
"[scss]": {
"editor.tabSize": 2
}
}

@ -1,3 +0,0 @@
[workspace]
resolver = "2"
members = ["crates/*"]

@ -1,6 +1,3 @@
# ai-gui-web
Janky port of ai-gui to the web.
Made to test out leptos.
Probably won't be getting updated much considering it was made as a test.
A version of AI Gui made for the web.

@ -0,0 +1,3 @@
/target
/history
config.ron

File diff suppressed because it is too large Load Diff

@ -1 +0,0 @@
config.ron

@ -1 +0,0 @@
dist/

@ -1,14 +0,0 @@
[package]
name = "frontend"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
ai_core = { git = "https://git.ruthenic.com/xTymon/ai-gui", rev = "a847557914" }
leptos = "0.2.5"
tracing = "0.1.37"
tracing-wasm = "0.2.1"
gloo-net = "0.2.6"
serde_json = "1.0.96"

@ -1,3 +0,0 @@
[[proxy]]
rewrite = "/api/"
backend = "http://localhost:3000/"

@ -1,9 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<link data-trunk rel="scss" href="./index.scss"/>
</head>
<body></body>
</html>

@ -1,10 +0,0 @@
mod ui;
use leptos::*;
use ui::*;
fn main() {
tracing_wasm::set_as_global_default();
mount_to_body(|cx| view! { cx, <GenerationUI /> })
}

@ -1,120 +0,0 @@
use ai_core::ai::Params;
use gloo_net::http::Request;
use leptos::{
html::{Input, Option_, Select, Textarea},
*,
};
use serde_json::{json, Value};
#[component]
pub fn GenerationUI(cx: Scope) -> impl IntoView {
let input: NodeRef<Textarea> = create_node_ref(cx);
let output: NodeRef<Textarea> = create_node_ref(cx);
let ai_selector: NodeRef<Select> = create_node_ref(cx);
let temp: NodeRef<Input> = create_node_ref(cx);
let max_tokens: NodeRef<Input> = create_node_ref(cx);
let generate_action = create_action(cx, move |_: &()| async move {
// these unwraps should never fail as the elements will always exist at this point
let prompt = input.get().unwrap().value();
let output = output.get().unwrap();
let temperature = temp.get().unwrap().value_as_number();
let max_tokens = max_tokens.get().unwrap().value_as_number();
let ai_selector = ai_selector.get().unwrap().value();
output.set_value("Generating...");
let res = Request::post("/api/generate")
.json(&json!({
"ai": ai_selector,
"params": Params {
prompt,
max_tokens: max_tokens as _,
temperature: temperature as _
}
}))
.unwrap()
.send()
.await;
match res {
Ok(res) => {
if res.status() != 200 {
output.set_value("Failed to generate!");
tracing::error!("Status code not 200");
return;
}
let res = res.text().await.unwrap(); // should be safe to unwrap here?
output.set_value(&res);
}
Err(err) => {
output.set_value("Failed to generate!");
tracing::error!("{}", err);
}
}
});
_ = create_resource(cx, || (), move |_| async move {
let res = Request::get("/api/ais").send().await;
match res {
Ok(res) => {
if res.status() != 200 {
tracing::error!("Status code not 200 for AI selector fetch");
return;
}
// these should never fail in theory
let res: Value = res.json().await.unwrap();
let res = res.as_array().unwrap().to_vec();
for ai in res {
let ai = ai.as_str().unwrap(); // should never fail
let option = &Option_::default();
option.set_value(ai);
option.set_label(ai);
ai_selector.get().unwrap().append_child(&option).unwrap();
}
}
Err(err) => {
tracing::error!("Failed to get AIs for selector! {}", err);
}
}
});
view! { cx,
<main>
<div class="bar">
<button on:click=move |_| {
generate_action.dispatch(());
}>
"Generate"
</button>
<h5>"AI"</h5>
<select node_ref=ai_selector />
<h5>"Temperature"</h5>
<input type="number" min="0" max="2" step="0.1" value="1" node_ref=temp />
<h5>"Max Tokens"</h5>
<input type="number" min="0" max="2000" step="10" value="1000" node_ref=max_tokens />
</div>
<div class="io">
<textarea node_ref=input />
<textarea readonly="true" node_ref=output />
</div>
</main>
}
}

@ -0,0 +1,2 @@
node_modules
dist

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>AI Gui</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="/src/index.tsx" type="module"></script>
</body>
</html>

@ -0,0 +1,21 @@
{
"name": "frontend",
"version": "0.0.0",
"description": "",
"scripts": {
"start": "vite",
"dev": "vite",
"build": "vite build",
"serve": "vite preview"
},
"license": "MIT",
"devDependencies": {
"sass": "^1.62.0",
"typescript": "^5.0.4",
"vite": "^4.3.1",
"vite-plugin-solid": "^2.7.0"
},
"dependencies": {
"solid-js": "^1.7.3"
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,89 @@
import {
createSignal,
type Component,
createMemo,
For,
createResource,
} from "solid-js";
const App: Component = () => {
const [input, setInput] = createSignal("");
const [output, setOutput] = createSignal("");
const [selectedAI, setSelectedAI] = createSignal("");
const [temperature, setTemperature] = createSignal(1.0);
const [maxTokens, setMaxTokens] = createSignal(1000);
const [ais] = createResource(async () => {
const res: string[] = await (await fetch("/api/ais")).json();
setSelectedAI(res[0]);
return res;
});
async function generate() {
setOutput("Generating...");
const res = await (
await fetch("/api/generate", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
ai: selectedAI(),
params: {
prompt: input(),
temperature: temperature(),
max_tokens: maxTokens(),
},
}),
})
).text();
setOutput(res);
}
return (
<div class="root">
{/* TODO: Make this part of the ui better */}
<div class="bar">
<button onClick={generate}>Generate</button>
<label for="ais">AI</label>
<select id="ais" onChange={(e) => setSelectedAI(e.target.value)}>
<For each={ais()}>{(ai) => <option>{ai}</option>}</For>
</select>
<label for="temperature">Temperature</label>
<input
id="temperature"
type="number"
min="0"
max="2"
step="0.1"
value="1"
onChange={(e) => setTemperature(e.currentTarget.valueAsNumber)}
/>
<label for="maxTokens">Max Tokens</label>
<input
id="maxTokens"
type="number"
min="0"
max="2000"
step="10"
value="1000"
onChange={(e) => setMaxTokens(e.currentTarget.valueAsNumber)}
/>
</div>
<div class="io">
<textarea onInput={(e) => setInput(e.currentTarget.value)} />
<textarea readonly={true} value={output()} />
</div>
</div>
);
};
export default App;

@ -1,5 +1,3 @@
// this css is kinda jank
@import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Sans&display=swap");
* {
@ -12,78 +10,83 @@ body {
background-color: #232428;
}
main {
.root {
display: flex;
flex: 1;
height: 100%;
height: 100vh;
flex-direction: column;
padding: 5px;
}
.bar {
display: flex;
flex-wrap: wrap;
padding: 2.5px 5px 2.5px 5px;
padding: 5px 5px 1px 5px;
// probably should make these be a class but i have no idea what to name it so
button {
background-color: #313338;
border: 1px solid #dbdee1;
border-radius: 2.5px;
border: 1px solid #767676;
color: #dbdee1;
outline: none;
&:active {
border-color: #87929b;
border-color: #dbdee1;
}
}
h5 {
margin: 0;
padding: 0;
margin-left: 10px;
margin-right: 5px;
color: #dbdee1;
}
input {
select {
background-color: #313338;
border: 1px solid #dbdee1;
border-radius: 2.5px;
border: 1px solid #767676;
color: #dbdee1;
outline: none;
&:active {
border-color: #87929b;
border-color: #dbdee1;
}
}
select {
input {
background-color: #313338;
border: 1px solid #dbdee1;
border-radius: 2.5px;
border: 1px solid #767676;
color: #dbdee1;
outline: none;
&:active {
border-color: #87929b;
border-color: #dbdee1;
}
}
label {
margin: 0;
padding: 0;
margin-left: 10px;
margin-right: 5px;
color: #dbdee1;
}
}
.io {
display: flex;
flex: 1;
@media (max-width: 500px) {
flex-direction: column;
}
textarea {
display: flex;
flex: 1;
margin: 5px;
border-radius: 2.5px;
resize: none;
background-color: #313338;
border-color: #dbdee1;
color: #dbdee1;
border-color: #767676;
outline: none;
&:focus {
border-color: #dbdee1;
}
}
@media (max-width: 500px) {
flex-direction: column;
}
}

@ -0,0 +1,9 @@
/* @refresh reload */
import { render } from "solid-js/web";
import "./index.scss";
import App from "./App";
const root = document.getElementById("root");
render(() => <App />, root!);

@ -0,0 +1,14 @@
{
"compilerOptions": {
"strict": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"jsx": "preserve",
"jsxImportSource": "solid-js",
"noEmit": true,
"isolatedModules": true,
}
}

@ -0,0 +1,18 @@
import { defineConfig } from "vite";
import solidPlugin from "vite-plugin-solid";
export default defineConfig({
plugins: [solidPlugin()],
server: {
port: 8080,
proxy: {
"/api": {
target: "http://localhost:3000/",
rewrite: (path) => path.replace(/^\/api/, ""),
},
},
},
build: {
target: "esnext",
},
});