initial commit
This commit is contained in:
commit
adec57e1b0
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
3743
Cargo.lock
generated
Normal file
3743
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
17
Cargo.toml
Normal file
17
Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "icedtest"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.79"
|
||||
iced = "0.9.0"
|
||||
iced_web = "0.4.0"
|
||||
reqwest = { version = "0.11.24", features = ["stream"] }
|
||||
serde = { version = "1.0.196", features = ["derive"] }
|
||||
serde_json = "1.0.113"
|
||||
tokio = { version = "1.35.1", features = ["full"] }
|
||||
tokio-stream = { version = "0.1.14", features = ["io-util"] }
|
||||
tokio-util = "0.7.10"
|
18
index.html
Normal file
18
index.html
Normal file
@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Tour - Iced</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script type="module">
|
||||
import init from "./tour/tour.js";
|
||||
|
||||
init('./tour/tour_bg.wasm');
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
123
src/api.rs
Normal file
123
src/api.rs
Normal file
@ -0,0 +1,123 @@
|
||||
use reqwest::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::io::AsyncBufReadExt;
|
||||
use tokio_stream::wrappers::LinesStream;
|
||||
use tokio_stream::{Stream, StreamExt};
|
||||
use tokio_util::io::StreamReader;
|
||||
|
||||
const API_URL: &'static str = "http://localhost:11434";
|
||||
|
||||
pub struct OllamaAPI {
|
||||
api_url: &'static str,
|
||||
client: Client,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct OllamaChat<'a> {
|
||||
#[serde(skip_serializing)]
|
||||
api: &'a OllamaAPI,
|
||||
model: String,
|
||||
#[serde(skip_serializing)]
|
||||
history_size: u32,
|
||||
messages: Vec<ChatMessage>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum Role {
|
||||
#[serde(rename = "system")]
|
||||
System,
|
||||
#[serde(rename = "user")]
|
||||
User,
|
||||
#[serde(rename = "assistant")]
|
||||
Assistant,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ChatMessage {
|
||||
role: Role,
|
||||
content: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct OllamaResponse {
|
||||
message: ChatMessage,
|
||||
}
|
||||
|
||||
fn deserialize_chunk(chunk: Result<String, std::io::Error>) -> anyhow::Result<String> {
|
||||
match chunk {
|
||||
Err(err) => Err(anyhow::Error::new(err)),
|
||||
Ok(str) => {
|
||||
let response = serde_json::from_str::<OllamaResponse>(&str)?;
|
||||
Ok(response.message.content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OllamaAPI {
|
||||
pub async fn complete<'a>(
|
||||
&self,
|
||||
chat: &OllamaChat<'a>,
|
||||
) -> anyhow::Result<impl Stream<Item = anyhow::Result<String>>> {
|
||||
let body = serde_json::to_string(&chat)?;
|
||||
println!("Sending: {}", body);
|
||||
|
||||
let request = self
|
||||
.client
|
||||
.post(String::from(API_URL) + "/api/chat")
|
||||
.body(serde_json::to_string(&chat)?)
|
||||
.build()?;
|
||||
let stream = self
|
||||
.client
|
||||
.execute(request)
|
||||
.await?
|
||||
.bytes_stream()
|
||||
.map(|x| match x {
|
||||
Err(err) => Err(std::io::Error::new(std::io::ErrorKind::Other, err)),
|
||||
Ok(bytes) => Ok(bytes),
|
||||
});
|
||||
let lines = StreamReader::new(stream).lines();
|
||||
Ok(LinesStream::new(lines).map(deserialize_chunk))
|
||||
}
|
||||
|
||||
pub fn create(api_url: &'static str) -> anyhow::Result<Self> {
|
||||
let client = Client::builder().build()?;
|
||||
Ok(Self {
|
||||
api_url: api_url,
|
||||
client,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn create_chat(&self, model: &str) -> OllamaChat {
|
||||
OllamaChat {
|
||||
api: &self,
|
||||
history_size: 0,
|
||||
model: String::from(model),
|
||||
messages: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> OllamaChat<'a> {
|
||||
pub fn send_message(&mut self, role: Role, content: &str) {
|
||||
self.messages.push(ChatMessage {
|
||||
role,
|
||||
content: String::from(content),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn send_system(&mut self, content: &str) {
|
||||
self.send_message(Role::System, content);
|
||||
}
|
||||
|
||||
pub fn send_user(&mut self, content: &str) {
|
||||
self.send_message(Role::User, content);
|
||||
}
|
||||
|
||||
pub fn send_assistant(&mut self, content: &str) {
|
||||
self.send_message(Role::Assistant, content);
|
||||
}
|
||||
|
||||
pub async fn complete(&self) -> anyhow::Result<impl Stream<Item = anyhow::Result<String>>> {
|
||||
self.api.complete(&self).await
|
||||
}
|
||||
}
|
92
src/main.rs
Normal file
92
src/main.rs
Normal file
@ -0,0 +1,92 @@
|
||||
mod api;
|
||||
|
||||
use api::OllamaAPI;
|
||||
use iced::{
|
||||
futures::StreamExt,
|
||||
widget::{button, column, container, row, scrollable, text},
|
||||
window::{self},
|
||||
Application, Command, Settings, Theme,
|
||||
};
|
||||
|
||||
enum UI {
|
||||
Loading,
|
||||
Loaded,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum UIMessage {
|
||||
LoadDone,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
println!("Making request");
|
||||
|
||||
let api = OllamaAPI::create("http://localhost:11434")?;
|
||||
let mut chat = api.create_chat("dolphin-mixtral");
|
||||
chat.send_system("You are a bot that only replies with \"Hello\" and nothing else. You must never reply with anything other than \"Hello\"");
|
||||
chat.send_user("Who are you?");
|
||||
// println!("{}", amogus.get().await?);
|
||||
|
||||
let mut a = chat.complete().await?;
|
||||
while let Some(v) = a.next().await {
|
||||
println!("GOT = {:?}", v);
|
||||
}
|
||||
|
||||
UI::run(Settings {
|
||||
window: window::Settings {
|
||||
size: (720, 480),
|
||||
..window::Settings::default()
|
||||
},
|
||||
..Settings::default()
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Application for UI {
|
||||
type Executor = iced::executor::Default;
|
||||
type Message = UIMessage;
|
||||
type Theme = Theme;
|
||||
type Flags = ();
|
||||
|
||||
fn new(_flags: Self::Flags) -> (Self, iced::Command<Self::Message>) {
|
||||
(
|
||||
UI::Loading,
|
||||
Command::perform(async {}, |_| UIMessage::LoadDone),
|
||||
)
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Hello World")
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Self::Message) -> iced::Command<Self::Message> {
|
||||
match message {
|
||||
UIMessage::LoadDone => {
|
||||
*self = UI::Loaded;
|
||||
Command::none()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self) -> iced::Element<'_, Self::Message, iced::Renderer<Self::Theme>> {
|
||||
let content = match self {
|
||||
UI::Loading => row![text("This is amogus sussy baka!")],
|
||||
UI::Loaded => row![button(text("Hello Wordl!")).on_press(UIMessage::LoadDone)],
|
||||
};
|
||||
|
||||
let amogus = text("Hello!");
|
||||
|
||||
scrollable(
|
||||
container(column![content, amogus].spacing(20).max_width(480))
|
||||
.padding(40)
|
||||
.center_x(),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn theme(&self) -> Self::Theme {
|
||||
Theme::Dark
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user