Clean up code, Per chat text input box state, Improve UI, Add launch config

This commit is contained in:
MrLetsplay 2024-02-10 19:39:22 +01:00
parent db80840c14
commit 4ae2dc55d8
Signed by: mr
SSH Key Fingerprint: SHA256:92jBH80vpXyaZHjaIl47pjRq+Yt7XGTArqQg1V7hSqg
3 changed files with 109 additions and 62 deletions

45
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,45 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug executable 'icedtest'",
"cargo": {
"args": [
"build",
"--bin=icedtest",
"--package=icedtest"
],
"filter": {
"name": "icedtest",
"kind": "bin"
}
},
"args": [],
"cwd": "${workspaceFolder}"
},
{
"type": "lldb",
"request": "launch",
"name": "Debug unit tests in executable 'icedtest'",
"cargo": {
"args": [
"test",
"--no-run",
"--bin=icedtest",
"--package=icedtest"
],
"filter": {
"name": "icedtest",
"kind": "bin"
}
},
"args": [],
"cwd": "${workspaceFolder}"
}
]
}

View File

@ -29,7 +29,7 @@ pub struct OllamaChat {
pub messages: Vec<ChatMessage>,
#[serde(skip_serializing)]
pub current_message: Option<Arc<RwLock<String>>>,
pub generating_message: Option<String>,
}
#[derive(Serialize, Deserialize, Clone)]
@ -79,7 +79,7 @@ impl OllamaAPI {
api_url: self.api_url.clone(),
model: String::from(model),
messages: Vec::new(),
current_message: None,
generating_message: None,
}));
self.chats.push(chat.clone());
@ -115,8 +115,7 @@ impl OllamaChat {
let body = serde_json::to_string(chat.deref())?;
println!("Sending: {}", body);
let msg = Arc::new(RwLock::new(String::from("")));
chat.current_message = Some(msg.clone());
chat.generating_message = Some(String::new());
}
let chat = chat.read().await;

View File

@ -3,7 +3,6 @@ mod api;
use std::{
cell::RefCell,
io::{self, Write},
ops::Deref,
sync::{
mpsc::{Receiver, Sender},
Arc,
@ -14,8 +13,10 @@ use api::{ChatMessage, OllamaAPI};
use iced::{
alignment::{Horizontal, Vertical},
futures::StreamExt,
subscription,
widget::{button, column, container, row, scrollable, text, text_input},
window, Application, Color, Command, Length, Settings, Subscription, Theme,
window::{self},
Application, Color, Command, Event, Length, Settings, Subscription, Theme,
};
use tokio::sync::RwLock;
@ -24,13 +25,13 @@ use crate::api::{OllamaChat, Role};
enum UI {
Error(String),
Chats(UIState),
Closing,
}
struct UIState {
ollama_api: Arc<RwLock<OllamaAPI>>,
active_chat: Option<usize>,
chat_input: String,
busy: bool,
send: Sender<UIMessage>,
recv: RefCell<Option<Receiver<UIMessage>>>,
}
@ -44,8 +45,7 @@ impl UIState {
Ok(Self {
ollama_api: Arc::new(RwLock::new(OllamaAPI::create(api_url)?)),
active_chat: None,
chat_input: String::from(""),
busy: false,
chat_input: String::new(),
send,
recv: RefCell::new(Some(recv)),
})
@ -55,6 +55,7 @@ impl UIState {
#[derive(Debug, Clone)]
enum UIMessage {
Nop,
EventOccurred(Event),
CreateChat,
OpenChat(usize),
ChatInput(String),
@ -63,10 +64,9 @@ enum UIMessage {
}
fn main() -> anyhow::Result<()> {
println!("Making request");
let (send, recv) = std::sync::mpsc::channel();
let mut settings = Settings::with_flags(UIFlags { send, recv });
settings.exit_on_close_request = false;
settings.window = window::Settings {
size: (720, 480),
..window::Settings::default()
@ -101,10 +101,19 @@ impl Application for UI {
}
fn title(&self) -> String {
String::from("Hello World")
String::from("AIGUI")
}
fn update(&mut self, message: Self::Message) -> iced::Command<Self::Message> {
if let UIMessage::EventOccurred(event) = &message {
if let Event::Window(window::Event::CloseRequested) = event {
*self = UI::Closing;
return window::close();
} else {
return Command::none();
}
}
match self {
UI::Chats(state) => match message {
UIMessage::CreateChat => {
@ -132,8 +141,7 @@ impl Application for UI {
if let Some(index) = state.active_chat {
let content = state.chat_input.clone();
state.chat_input = String::from("");
state.busy = true;
state.chat_input = String::new();
let send = state.send.clone();
let ollama_api = state.ollama_api.clone();
return Command::perform(
@ -146,8 +154,7 @@ impl Application for UI {
chat = api.chats[index].clone();
let mut chat = chat.write().await;
chat.send_user(&content);
chat.current_message =
Some(Arc::new(RwLock::new(String::from(""))));
chat.generating_message = Some(String::new());
}
//let mut chat = chat.write().await;
@ -163,10 +170,10 @@ impl Application for UI {
let content = content.unwrap();
let chat_arc = chat.clone();
let chat = chat_arc.read().await;
let msg = chat.current_message.clone();
let mut chat = chat_arc.write().await;
let msg = &mut chat.generating_message;
if let Some(msg) = msg {
msg.write().await.push_str(&content);
msg.push_str(&content);
}
print!("{}", content);
@ -176,14 +183,12 @@ impl Application for UI {
}
let mut chat = chat.write().await;
let msg = chat.current_message.clone();
let msg = &chat.generating_message.clone();
if let Some(msg) = msg {
let msg = msg.write().await;
chat.send_assistant(msg.deref());
chat.current_message = None;
chat.send_assistant(&msg);
chat.generating_message = None;
}
}
//state.ollama_api.complete(&x);
},
|_| UIMessage::DoneGenerating,
);
@ -191,13 +196,11 @@ impl Application for UI {
Command::none()
}
UIMessage::DoneGenerating => {
state.busy = false;
Command::none()
}
UIMessage::DoneGenerating => Command::none(),
_ => Command::none(),
},
UI::Error(_) => Command::none(),
UI::Closing => Command::none(),
}
}
@ -210,16 +213,13 @@ impl Application for UI {
.horizontal_alignment(Horizontal::Center)
.into(),
UI::Chats(state) => {
println!("Blocking");
let api = state.ollama_api.blocking_read();
println!("Blocing done!");
let mut chats: Vec<iced::Element<'_, Self::Message, iced::Renderer<Self::Theme>>> =
api.chats
.iter()
.enumerate()
.map(|c| {
button("amogus")
button("Chat")
.on_press(UIMessage::OpenChat(c.0))
.width(Length::Fill)
.into()
@ -234,13 +234,14 @@ impl Application for UI {
.into(),
);
let chats_list = container(column(chats).padding(10.0).spacing(10))
let chats_list = scrollable(container(column(chats).padding(10.0).spacing(10)))
.height(Length::Fill)
.width(Length::Fixed(300.0));
let mut messages: Vec<
iced::Element<'_, Self::Message, iced::Renderer<Self::Theme>>,
>;
let mut input_box = text_input("Your message", &state.chat_input);
if let Some(chat_index) = state.active_chat {
let chat = &api.chats[chat_index].blocking_read();
messages = chat
@ -249,20 +250,23 @@ impl Application for UI {
.map(|x| self.render_message(x))
.collect();
if let Some(msg) = &chat.current_message {
messages.push(text(msg.blocking_read()).into());
if let Some(msg) = &chat.generating_message {
let mut msg = msg.clone();
msg.push_str("...");
let in_progress_message = ChatMessage {
role: Role::Assistant,
content: msg,
};
messages.push(self.render_message(&in_progress_message));
} else {
input_box = input_box
.on_input(|x| UIMessage::ChatInput(x))
.on_submit(UIMessage::ChatSubmit)
}
} else {
messages = Vec::new();
}
let mut input_box = text_input("Your message", &state.chat_input);
if !state.busy {
input_box = input_box
.on_input(|x| UIMessage::ChatInput(x))
.on_submit(UIMessage::ChatSubmit)
}
let active_chat = container(
column![
scrollable(container(column(messages).spacing(10)))
@ -277,14 +281,8 @@ impl Application for UI {
.height(Length::Fill);
container(row![chats_list, active_chat]).into()
/*scrollable(
container(column![content, amogus].spacing(20).max_width(480))
.padding(40)
.center_x(),
)
.into()*/
}
UI::Closing => text("Closing").into(),
}
}
@ -293,22 +291,27 @@ impl Application for UI {
}
fn subscription(&self) -> iced::Subscription<Self::Message> {
let event_subscription = subscription::events().map(UIMessage::EventOccurred);
match self {
UI::Chats(state) => iced::subscription::unfold(
"external messages",
state.recv.take(),
move |recv| async move {
let msg = recv.as_ref().unwrap().recv();
UI::Chats(state) => {
let recv_subscription = iced::subscription::unfold(
"external messages",
state.recv.take(),
move |recv| async move {
let msg = recv.as_ref().unwrap().recv();
if let Err(_) = msg {
return (UIMessage::Nop, recv);
}
if let Err(_) = msg {
return (UIMessage::Nop, recv);
}
let msg = msg.unwrap();
(msg, recv)
},
),
_ => Subscription::none(),
let msg = msg.unwrap();
(msg, recv)
},
);
Subscription::batch(vec![event_subscription, recv_subscription])
}
_ => event_subscription,
}
}
}