mirror of
https://github.com/aljazceru/notedeck.git
synced 2025-12-18 17:14:21 +01:00
dave: auto-reply, initial avatar anim
Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -3305,6 +3305,7 @@ dependencies = [
|
|||||||
"hex",
|
"hex",
|
||||||
"nostrdb",
|
"nostrdb",
|
||||||
"notedeck",
|
"notedeck",
|
||||||
|
"rand 0.9.0",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ serde = { workspace = true }
|
|||||||
nostrdb = { workspace = true }
|
nostrdb = { workspace = true }
|
||||||
hex = { workspace = true }
|
hex = { workspace = true }
|
||||||
time = "0.3.41"
|
time = "0.3.41"
|
||||||
|
rand = "0.9.0"
|
||||||
bytemuck = "1.22.0"
|
bytemuck = "1.22.0"
|
||||||
futures = "0.3.31"
|
futures = "0.3.31"
|
||||||
reqwest = "0.12.15"
|
reqwest = "0.12.15"
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
use std::num::NonZeroU64;
|
use std::num::NonZeroU64;
|
||||||
|
|
||||||
|
use crate::vec3::Vec3;
|
||||||
use eframe::egui_wgpu::{self, wgpu};
|
use eframe::egui_wgpu::{self, wgpu};
|
||||||
use egui::{Rect, Response};
|
use egui::{Rect, Response};
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
pub struct DaveAvatar {
|
pub struct DaveAvatar {
|
||||||
rotation: Quaternion,
|
rotation: Quaternion,
|
||||||
rot_dir: egui::Vec2,
|
rot_dir: Vec3,
|
||||||
}
|
}
|
||||||
|
|
||||||
// A simple quaternion implementation
|
// A simple quaternion implementation
|
||||||
@@ -28,13 +30,13 @@ impl Quaternion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create from axis-angle representation
|
// Create from axis-angle representation
|
||||||
fn from_axis_angle(axis: [f32; 3], angle: f32) -> Self {
|
fn from_axis_angle(axis: &Vec3, angle: f32) -> Self {
|
||||||
let half_angle = angle * 0.5;
|
let half_angle = angle * 0.5;
|
||||||
let s = half_angle.sin();
|
let s = half_angle.sin();
|
||||||
Self {
|
Self {
|
||||||
x: axis[0] * s,
|
x: axis.x * s,
|
||||||
y: axis[1] * s,
|
y: axis.y * s,
|
||||||
z: axis[2] * s,
|
z: axis.z * s,
|
||||||
w: half_angle.cos(),
|
w: half_angle.cos(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -358,7 +360,7 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
rotation: Quaternion::identity(),
|
rotation: Quaternion::identity(),
|
||||||
rot_dir: egui::Vec2::ZERO,
|
rot_dir: Vec3::new(0.0, 0.0, 0.0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -373,6 +375,21 @@ fn apply_friction(val: f32, friction: f32, clamp: f32) -> f32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DaveAvatar {
|
impl DaveAvatar {
|
||||||
|
pub fn random_nudge(&mut self) {
|
||||||
|
let mut rng = rand::rng();
|
||||||
|
|
||||||
|
let nudge = Vec3::new(
|
||||||
|
rng.random::<f32>(),
|
||||||
|
rng.random::<f32>(),
|
||||||
|
rng.random::<f32>(),
|
||||||
|
)
|
||||||
|
.normalize();
|
||||||
|
|
||||||
|
self.rot_dir.x += nudge.x;
|
||||||
|
self.rot_dir.y += nudge.y;
|
||||||
|
self.rot_dir.z += nudge.z;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn render(&mut self, rect: Rect, ui: &mut egui::Ui) -> Response {
|
pub fn render(&mut self, rect: Rect, ui: &mut egui::Ui) -> Response {
|
||||||
let response = ui.allocate_rect(rect, egui::Sense::drag());
|
let response = ui.allocate_rect(rect, egui::Sense::drag());
|
||||||
|
|
||||||
@@ -381,10 +398,10 @@ impl DaveAvatar {
|
|||||||
// Create rotation quaternions based on drag
|
// Create rotation quaternions based on drag
|
||||||
let dx = response.drag_delta().x;
|
let dx = response.drag_delta().x;
|
||||||
let dy = response.drag_delta().y;
|
let dy = response.drag_delta().y;
|
||||||
let x_rotation = Quaternion::from_axis_angle([1.0, 0.0, 0.0], dy * 0.01);
|
let x_rotation = Quaternion::from_axis_angle(&Vec3::new(1.0, 0.0, 0.0), dy * 0.01);
|
||||||
let y_rotation = Quaternion::from_axis_angle([0.0, 1.0, 0.0], dx * 0.01);
|
let y_rotation = Quaternion::from_axis_angle(&Vec3::new(0.0, 1.0, 0.0), dx * 0.01);
|
||||||
|
|
||||||
self.rot_dir = egui::Vec2::new(dx, dy);
|
self.rot_dir = Vec3::new(dx, dy, 0.0);
|
||||||
|
|
||||||
// Apply rotations (order matters)
|
// Apply rotations (order matters)
|
||||||
self.rotation = y_rotation.multiply(&x_rotation).multiply(&self.rotation);
|
self.rotation = y_rotation.multiply(&x_rotation).multiply(&self.rotation);
|
||||||
@@ -394,15 +411,21 @@ impl DaveAvatar {
|
|||||||
let clamp = 0.1;
|
let clamp = 0.1;
|
||||||
self.rot_dir.x = apply_friction(self.rot_dir.x, friction, clamp);
|
self.rot_dir.x = apply_friction(self.rot_dir.x, friction, clamp);
|
||||||
self.rot_dir.y = apply_friction(self.rot_dir.y, friction, clamp);
|
self.rot_dir.y = apply_friction(self.rot_dir.y, friction, clamp);
|
||||||
|
self.rot_dir.z = apply_friction(self.rot_dir.y, friction, clamp);
|
||||||
|
|
||||||
// we only need to render if we're still spinning
|
// we only need to render if we're still spinning
|
||||||
if self.rot_dir.x > clamp || self.rot_dir.y > clamp {
|
if self.rot_dir.x > clamp || self.rot_dir.y > clamp || self.rot_dir.z > clamp {
|
||||||
let x_rotation =
|
let x_rotation =
|
||||||
Quaternion::from_axis_angle([1.0, 0.0, 0.0], self.rot_dir.y * 0.01);
|
Quaternion::from_axis_angle(&Vec3::new(1.0, 0.0, 0.0), self.rot_dir.y * 0.01);
|
||||||
let y_rotation =
|
let y_rotation =
|
||||||
Quaternion::from_axis_angle([0.0, 1.0, 0.0], self.rot_dir.x * 0.01);
|
Quaternion::from_axis_angle(&Vec3::new(0.0, 1.0, 0.0), self.rot_dir.x * 0.01);
|
||||||
|
let z_rotation =
|
||||||
|
Quaternion::from_axis_angle(&Vec3::new(0.0, 0.0, 1.0), self.rot_dir.z * 0.01);
|
||||||
|
|
||||||
self.rotation = y_rotation.multiply(&x_rotation).multiply(&self.rotation);
|
self.rotation = y_rotation
|
||||||
|
.multiply(&x_rotation)
|
||||||
|
.multiply(&z_rotation)
|
||||||
|
.multiply(&self.rotation);
|
||||||
|
|
||||||
ui.ctx().request_repaint();
|
ui.ctx().request_repaint();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ use egui::{Rect, Vec2};
|
|||||||
use egui_wgpu::RenderState;
|
use egui_wgpu::RenderState;
|
||||||
|
|
||||||
mod avatar;
|
mod avatar;
|
||||||
|
mod vec3;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
@@ -331,8 +332,12 @@ impl Dave {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render(&mut self, app_ctx: &AppContext, ui: &mut egui::Ui) {
|
fn render(&mut self, app_ctx: &AppContext, ui: &mut egui::Ui) {
|
||||||
|
let mut should_send = false;
|
||||||
if let Some(recvr) = &self.incoming_tokens {
|
if let Some(recvr) = &self.incoming_tokens {
|
||||||
while let Ok(res) = recvr.try_recv() {
|
while let Ok(res) = recvr.try_recv() {
|
||||||
|
if let Some(avatar) = &mut self.avatar {
|
||||||
|
avatar.random_nudge();
|
||||||
|
}
|
||||||
match res {
|
match res {
|
||||||
DaveResponse::Token(token) => match self.chat.last_mut() {
|
DaveResponse::Token(token) => match self.chat.last_mut() {
|
||||||
Some(Message::Assistant(msg)) => *msg = msg.clone() + &token,
|
Some(Message::Assistant(msg)) => *msg = msg.clone() + &token,
|
||||||
@@ -357,31 +362,44 @@ impl Dave {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
should_send = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scroll area for chat messages
|
// Scroll area for chat messages
|
||||||
egui::Frame::new().inner_margin(10.0).show(ui, |ui| {
|
egui::Frame::new()
|
||||||
egui::ScrollArea::vertical()
|
.outer_margin(egui::Margin {
|
||||||
.stick_to_bottom(true)
|
top: 100,
|
||||||
.auto_shrink([false; 2])
|
..Default::default()
|
||||||
.show(ui, |ui| {
|
})
|
||||||
ui.vertical(|ui| {
|
.inner_margin(10.0)
|
||||||
self.render_chat(ui);
|
.show(ui, |ui| {
|
||||||
|
egui::ScrollArea::vertical()
|
||||||
|
.stick_to_bottom(true)
|
||||||
|
.auto_shrink([false; 2])
|
||||||
|
.show(ui, |ui| {
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
self.render_chat(ui);
|
||||||
|
|
||||||
self.inputbox(app_ctx, ui);
|
self.inputbox(app_ctx, ui);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(avatar) = &mut self.avatar {
|
if let Some(avatar) = &mut self.avatar {
|
||||||
let avatar_size = Vec2::splat(200.0);
|
let avatar_size = Vec2::splat(100.0);
|
||||||
let pos = Vec2::splat(100.0).to_pos2();
|
let pos = Vec2::splat(10.0).to_pos2();
|
||||||
let pos = Rect::from_min_max(pos, pos + avatar_size);
|
let pos = Rect::from_min_max(pos, pos + avatar_size);
|
||||||
avatar.render(pos, ui);
|
avatar.render(pos, ui);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// send again
|
||||||
|
if should_send {
|
||||||
|
self.send_user_message(app_ctx, ui.ctx());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_chat(&self, ui: &mut egui::Ui) {
|
fn render_chat(&self, ui: &mut egui::Ui) {
|
||||||
@@ -441,7 +459,7 @@ impl Dave {
|
|||||||
egui::Frame::new()
|
egui::Frame::new()
|
||||||
.inner_margin(10.0)
|
.inner_margin(10.0)
|
||||||
.corner_radius(10.0)
|
.corner_radius(10.0)
|
||||||
.fill(ui.visuals().extreme_bg_color)
|
.fill(ui.visuals().widgets.inactive.weak_bg_fill)
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
ui.label(msg);
|
ui.label(msg);
|
||||||
})
|
})
|
||||||
|
|||||||
38
crates/notedeck_dave/src/vec3.rs
Normal file
38
crates/notedeck_dave/src/vec3.rs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Vec3 {
|
||||||
|
pub x: f32,
|
||||||
|
pub y: f32,
|
||||||
|
pub z: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vec3 {
|
||||||
|
pub fn new(x: f32, y: f32, z: f32) -> Self {
|
||||||
|
Vec3 { x, y, z }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn squared_length(&self) -> f32 {
|
||||||
|
self.x * self.x + self.y * self.y + self.z * self.z
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn length(&self) -> f32 {
|
||||||
|
self.squared_length().sqrt()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn normalize(&self) -> Vec3 {
|
||||||
|
let len = self.length();
|
||||||
|
if len != 0.0 {
|
||||||
|
Vec3 {
|
||||||
|
x: self.x / len,
|
||||||
|
y: self.y / len,
|
||||||
|
z: self.z / len,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Return zero vector if length is zero to avoid NaNs
|
||||||
|
Vec3 {
|
||||||
|
x: 0.0,
|
||||||
|
y: 0.0,
|
||||||
|
z: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user