822
Cargo.lock
generated
822
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -5,8 +5,9 @@ members = [
|
|||||||
"crates/notedeck",
|
"crates/notedeck",
|
||||||
"crates/notedeck_chrome",
|
"crates/notedeck_chrome",
|
||||||
"crates/notedeck_columns",
|
"crates/notedeck_columns",
|
||||||
|
"crates/notedeck_dave",
|
||||||
|
|
||||||
"crates/enostr", "crates/tokenator",
|
"crates/enostr", "crates/tokenator", "crates/notedeck_dave",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
@@ -18,6 +19,7 @@ bitflags = "2.5.0"
|
|||||||
dirs = "5.0.1"
|
dirs = "5.0.1"
|
||||||
eframe = { version = "0.31.1", default-features = false, features = [ "wgpu", "wayland", "x11", "android-game-activity" ] }
|
eframe = { version = "0.31.1", default-features = false, features = [ "wgpu", "wayland", "x11", "android-game-activity" ] }
|
||||||
egui = { version = "0.31.1", features = ["serde"] }
|
egui = { version = "0.31.1", features = ["serde"] }
|
||||||
|
egui-wgpu = "0.31.1"
|
||||||
egui_extras = { version = "0.31.1", features = ["all_loaders"] }
|
egui_extras = { version = "0.31.1", features = ["all_loaders"] }
|
||||||
egui-winit = { version = "0.31.1", features = ["android-game-activity", "clipboard"] }
|
egui-winit = { version = "0.31.1", features = ["android-game-activity", "clipboard"] }
|
||||||
egui_nav = { git = "https://github.com/damus-io/egui-nav", rev = "5e816ac95e20f31dbb243a0d76179eab329a8ac0" }
|
egui_nav = { git = "https://github.com/damus-io/egui-nav", rev = "5e816ac95e20f31dbb243a0d76179eab329a8ac0" }
|
||||||
@@ -39,6 +41,7 @@ nostrdb = { git = "https://github.com/damus-io/nostrdb-rs", rev = "7a6af440c12e7
|
|||||||
notedeck = { path = "crates/notedeck" }
|
notedeck = { path = "crates/notedeck" }
|
||||||
notedeck_chrome = { path = "crates/notedeck_chrome" }
|
notedeck_chrome = { path = "crates/notedeck_chrome" }
|
||||||
notedeck_columns = { path = "crates/notedeck_columns" }
|
notedeck_columns = { path = "crates/notedeck_columns" }
|
||||||
|
notedeck_dave = { path = "crates/notedeck_dave" }
|
||||||
tokenator = { path = "crates/tokenator" }
|
tokenator = { path = "crates/tokenator" }
|
||||||
open = "5.3.0"
|
open = "5.3.0"
|
||||||
poll-promise = { version = "0.3.0", features = ["tokio"] }
|
poll-promise = { version = "0.3.0", features = ["tokio"] }
|
||||||
@@ -67,6 +70,7 @@ profiling = "1.0"
|
|||||||
lightning-invoice = "0.33.1"
|
lightning-invoice = "0.33.1"
|
||||||
secp256k1 = "0.30.0"
|
secp256k1 = "0.30.0"
|
||||||
hashbrown = "0.15.2"
|
hashbrown = "0.15.2"
|
||||||
|
openai-api-rs = "6.0.3"
|
||||||
|
|
||||||
[profile.small]
|
[profile.small]
|
||||||
inherits = 'release'
|
inherits = 'release'
|
||||||
@@ -87,6 +91,7 @@ strip = true # Strip symbols from binary*
|
|||||||
egui = { git = "https://github.com/damus-io/egui", rev = "93cd1cedc1e8eed2b055e317226838e37a845aad" }
|
egui = { git = "https://github.com/damus-io/egui", rev = "93cd1cedc1e8eed2b055e317226838e37a845aad" }
|
||||||
eframe = { git = "https://github.com/damus-io/egui", rev = "93cd1cedc1e8eed2b055e317226838e37a845aad" }
|
eframe = { git = "https://github.com/damus-io/egui", rev = "93cd1cedc1e8eed2b055e317226838e37a845aad" }
|
||||||
egui-winit = { git = "https://github.com/damus-io/egui", rev = "93cd1cedc1e8eed2b055e317226838e37a845aad" }
|
egui-winit = { git = "https://github.com/damus-io/egui", rev = "93cd1cedc1e8eed2b055e317226838e37a845aad" }
|
||||||
|
egui-wgpu = { git = "https://github.com/damus-io/egui", rev = "93cd1cedc1e8eed2b055e317226838e37a845aad" }
|
||||||
egui_extras = { git = "https://github.com/damus-io/egui", rev = "93cd1cedc1e8eed2b055e317226838e37a845aad" }
|
egui_extras = { git = "https://github.com/damus-io/egui", rev = "93cd1cedc1e8eed2b055e317226838e37a845aad" }
|
||||||
epaint = { git = "https://github.com/damus-io/egui", rev = "93cd1cedc1e8eed2b055e317226838e37a845aad" }
|
epaint = { git = "https://github.com/damus-io/egui", rev = "93cd1cedc1e8eed2b055e317226838e37a845aad" }
|
||||||
puffin = { git = "https://github.com/jb55/puffin", package = "puffin", rev = "c6a6242adaf90b6292c0f462d2acd34d96d224d2" }
|
puffin = { git = "https://github.com/jb55/puffin", package = "puffin", rev = "c6a6242adaf90b6292c0f462d2acd34d96d224d2" }
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ eframe = { workspace = true }
|
|||||||
egui_extras = { workspace = true }
|
egui_extras = { workspace = true }
|
||||||
egui = { workspace = true }
|
egui = { workspace = true }
|
||||||
notedeck_columns = { workspace = true }
|
notedeck_columns = { workspace = true }
|
||||||
|
notedeck_dave = { workspace = true }
|
||||||
notedeck = { workspace = true }
|
notedeck = { workspace = true }
|
||||||
puffin = { workspace = true, optional = true }
|
puffin = { workspace = true, optional = true }
|
||||||
puffin_egui = { workspace = true, optional = true }
|
puffin_egui = { workspace = true, optional = true }
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use notedeck_chrome::setup::{generate_native_options, setup_chrome};
|
|||||||
|
|
||||||
use notedeck::{DataPath, DataPathType, Notedeck};
|
use notedeck::{DataPath, DataPathType, Notedeck};
|
||||||
use notedeck_columns::Damus;
|
use notedeck_columns::Damus;
|
||||||
|
use notedeck_dave::Dave;
|
||||||
use tracing_appender::non_blocking::WorkerGuard;
|
use tracing_appender::non_blocking::WorkerGuard;
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
@@ -11,6 +12,38 @@ use tracing_subscriber::EnvFilter;
|
|||||||
//#[cfg(target_arch = "wasm32")]
|
//#[cfg(target_arch = "wasm32")]
|
||||||
//use wasm_bindgen::prelude::*;
|
//use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
struct Chrome {
|
||||||
|
active: i32,
|
||||||
|
apps: Vec<Box<dyn notedeck::App>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Chrome {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Chrome {
|
||||||
|
active: 0,
|
||||||
|
apps: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_app(&mut self, app: impl notedeck::App + 'static) {
|
||||||
|
self.apps.push(Box::new(app));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_active(&mut self, app: i32) {
|
||||||
|
self.active = app;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl notedeck::App for Chrome {
|
||||||
|
fn update(&mut self, ctx: &mut notedeck::AppContext, ui: &mut egui::Ui) {
|
||||||
|
let active = self.active;
|
||||||
|
self.apps[active as usize].update(ctx, ui);
|
||||||
|
//for i in 0..self.apps.len() {
|
||||||
|
// self.apps[i].update(ctx, ui);
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn setup_logging(path: &DataPath) -> Option<WorkerGuard> {
|
fn setup_logging(path: &DataPath) -> Option<WorkerGuard> {
|
||||||
#[allow(unused_variables)] // need guard to live for lifetime of program
|
#[allow(unused_variables)] // need guard to live for lifetime of program
|
||||||
let (maybe_non_blocking, maybe_guard) = {
|
let (maybe_non_blocking, maybe_guard) = {
|
||||||
@@ -78,15 +111,19 @@ async fn main() {
|
|||||||
Box::new(|cc| {
|
Box::new(|cc| {
|
||||||
let args: Vec<String> = std::env::args().collect();
|
let args: Vec<String> = std::env::args().collect();
|
||||||
let ctx = &cc.egui_ctx;
|
let ctx = &cc.egui_ctx;
|
||||||
let mut notedeck = Notedeck::new(ctx, base_path, &args);
|
|
||||||
setup_chrome(ctx, notedeck.args(), notedeck.theme());
|
|
||||||
|
|
||||||
let damus = Damus::new(&mut notedeck.app_context(), &args);
|
let mut notedeck = Notedeck::new(ctx, base_path, &args);
|
||||||
|
|
||||||
|
let mut chrome = Chrome::new();
|
||||||
|
let columns = Damus::new(&mut notedeck.app_context(), &args);
|
||||||
|
let dave = Dave::new(cc.wgpu_render_state.as_ref());
|
||||||
|
|
||||||
|
setup_chrome(ctx, notedeck.args(), notedeck.theme());
|
||||||
|
|
||||||
// ensure we recognized all the arguments
|
// ensure we recognized all the arguments
|
||||||
let completely_unrecognized: Vec<String> = notedeck
|
let completely_unrecognized: Vec<String> = notedeck
|
||||||
.unrecognized_args()
|
.unrecognized_args()
|
||||||
.intersection(damus.unrecognized_args())
|
.intersection(columns.unrecognized_args())
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect();
|
.collect();
|
||||||
assert!(
|
assert!(
|
||||||
@@ -95,8 +132,13 @@ async fn main() {
|
|||||||
completely_unrecognized
|
completely_unrecognized
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: move "chrome" frame over Damus app somehow
|
chrome.add_app(columns);
|
||||||
notedeck.set_app(damus);
|
chrome.add_app(dave);
|
||||||
|
|
||||||
|
// test dav
|
||||||
|
chrome.set_active(1);
|
||||||
|
|
||||||
|
notedeck.set_app(chrome);
|
||||||
|
|
||||||
Ok(Box::new(notedeck))
|
Ok(Box::new(notedeck))
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ pub fn generate_native_options(paths: DataPath) -> NativeOptions {
|
|||||||
});
|
});
|
||||||
|
|
||||||
eframe::NativeOptions {
|
eframe::NativeOptions {
|
||||||
|
// for 3d widgets
|
||||||
|
depth_buffer: 24,
|
||||||
window_builder: Some(window_builder),
|
window_builder: Some(window_builder),
|
||||||
viewport: egui::ViewportBuilder::default().with_icon(std::sync::Arc::new(
|
viewport: egui::ViewportBuilder::default().with_icon(std::sync::Arc::new(
|
||||||
eframe::icon_data::from_png_bytes(app_icon()).expect("icon"),
|
eframe::icon_data::from_png_bytes(app_icon()).expect("icon"),
|
||||||
@@ -80,6 +82,8 @@ fn generate_native_options_with_builder_modifiers(
|
|||||||
Box::new(move |builder: egui::ViewportBuilder| apply_builder_modifiers(builder));
|
Box::new(move |builder: egui::ViewportBuilder| apply_builder_modifiers(builder));
|
||||||
|
|
||||||
eframe::NativeOptions {
|
eframe::NativeOptions {
|
||||||
|
// for 3d widgets
|
||||||
|
depth_buffer: 24,
|
||||||
window_builder: Some(window_builder),
|
window_builder: Some(window_builder),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
|
|||||||
16
crates/notedeck_dave/Cargo.toml
Normal file
16
crates/notedeck_dave/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
[package]
|
||||||
|
name = "notedeck_dave"
|
||||||
|
edition = "2021"
|
||||||
|
version.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
async-openai = "0.28.0"
|
||||||
|
egui = { workspace = true }
|
||||||
|
notedeck = { workspace = true }
|
||||||
|
eframe = { workspace = true }
|
||||||
|
tokio = { workspace = true }
|
||||||
|
tracing = { workspace = true }
|
||||||
|
egui-wgpu = { workspace = true }
|
||||||
|
bytemuck = "1.22.0"
|
||||||
|
futures = "0.3.31"
|
||||||
|
reqwest = "0.12.15"
|
||||||
68
crates/notedeck_dave/shader.wgsl
Normal file
68
crates/notedeck_dave/shader.wgsl
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
struct VertexOut {
|
||||||
|
@location(0) color: vec4<f32>,
|
||||||
|
@builtin(position) position: vec4<f32>,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Uniforms {
|
||||||
|
@size(16) angle: f32, // pad to 16 bytes
|
||||||
|
};
|
||||||
|
|
||||||
|
@group(0) @binding(0)
|
||||||
|
var<uniform> uniforms: Uniforms;
|
||||||
|
|
||||||
|
@vertex
|
||||||
|
fn vs_main(@builtin(vertex_index) v_idx: u32) -> VertexOut {
|
||||||
|
// Cube vertices hardcoded in the shader
|
||||||
|
var positions = array<vec3<f32>, 8>(
|
||||||
|
vec3<f32>(-0.5, -0.5, 0.5), // front bottom left
|
||||||
|
vec3<f32>(0.5, -0.5, 0.5), // front bottom right
|
||||||
|
vec3<f32>(0.5, 0.5, 0.5), // front top right
|
||||||
|
vec3<f32>(-0.5, 0.5, 0.5), // front top left
|
||||||
|
vec3<f32>(-0.5, -0.5, -0.5), // back bottom left
|
||||||
|
vec3<f32>(0.5, -0.5, -0.5), // back bottom right
|
||||||
|
vec3<f32>(0.5, 0.5, -0.5), // back top right
|
||||||
|
vec3<f32>(-0.5, 0.5, -0.5) // back top left
|
||||||
|
);
|
||||||
|
|
||||||
|
// Cube indices hardcoded in the shader
|
||||||
|
var indices = array<u32, 36>(
|
||||||
|
// front face
|
||||||
|
0, 1, 2, 2, 3, 0,
|
||||||
|
// back face
|
||||||
|
4, 5, 6, 6, 7, 4,
|
||||||
|
// right face
|
||||||
|
1, 5, 6, 6, 2, 1,
|
||||||
|
// left face
|
||||||
|
0, 4, 7, 7, 3, 0,
|
||||||
|
// top face
|
||||||
|
3, 2, 6, 6, 7, 3,
|
||||||
|
// bottom face
|
||||||
|
0, 1, 5, 5, 4, 0
|
||||||
|
);
|
||||||
|
|
||||||
|
var out: VertexOut;
|
||||||
|
var idx = indices[v_idx];
|
||||||
|
var pos = positions[idx];
|
||||||
|
|
||||||
|
// simple rotation around Y axis
|
||||||
|
var cosA = cos(uniforms.angle);
|
||||||
|
var sinA = sin(uniforms.angle);
|
||||||
|
var rotated_x = pos.x * cosA + pos.z * sinA;
|
||||||
|
var rotated_z = -pos.x * sinA + pos.z * cosA;
|
||||||
|
|
||||||
|
// With proper perspective transformation:
|
||||||
|
var z_pos = rotated_z - 2.0; // Move cube away from camera
|
||||||
|
var w = -z_pos; // Set w to -z for perspective division
|
||||||
|
out.position = vec4<f32>(rotated_x, pos.y, rotated_z, w);
|
||||||
|
|
||||||
|
// simple white shading based on position
|
||||||
|
var shade = 0.5 + 0.5 * (rotated_z + pos.y);
|
||||||
|
out.color = vec4<f32>(shade, shade, shade, 1.0);
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
|
||||||
|
return in.color;
|
||||||
|
}
|
||||||
416
crates/notedeck_dave/src/avatar.rs
Normal file
416
crates/notedeck_dave/src/avatar.rs
Normal file
@@ -0,0 +1,416 @@
|
|||||||
|
use std::num::NonZeroU64;
|
||||||
|
|
||||||
|
use eframe::egui_wgpu::{self, wgpu};
|
||||||
|
use egui::{Rect, Response};
|
||||||
|
|
||||||
|
pub struct DaveAvatar {
|
||||||
|
rotation: Quaternion,
|
||||||
|
}
|
||||||
|
|
||||||
|
// A simple quaternion implementation
|
||||||
|
struct Quaternion {
|
||||||
|
x: f32,
|
||||||
|
y: f32,
|
||||||
|
z: f32,
|
||||||
|
w: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Quaternion {
|
||||||
|
// Create identity quaternion
|
||||||
|
fn identity() -> Self {
|
||||||
|
Self {
|
||||||
|
x: 0.0,
|
||||||
|
y: 0.0,
|
||||||
|
z: 0.0,
|
||||||
|
w: 1.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create from axis-angle representation
|
||||||
|
fn from_axis_angle(axis: [f32; 3], angle: f32) -> Self {
|
||||||
|
let half_angle = angle * 0.5;
|
||||||
|
let s = half_angle.sin();
|
||||||
|
Self {
|
||||||
|
x: axis[0] * s,
|
||||||
|
y: axis[1] * s,
|
||||||
|
z: axis[2] * s,
|
||||||
|
w: half_angle.cos(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiply two quaternions (combines rotations)
|
||||||
|
fn multiply(&self, other: &Self) -> Self {
|
||||||
|
Self {
|
||||||
|
x: self.w * other.x + self.x * other.w + self.y * other.z - self.z * other.y,
|
||||||
|
y: self.w * other.y - self.x * other.z + self.y * other.w + self.z * other.x,
|
||||||
|
z: self.w * other.z + self.x * other.y - self.y * other.x + self.z * other.w,
|
||||||
|
w: self.w * other.w - self.x * other.x - self.y * other.y - self.z * other.z,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert quaternion to 4x4 matrix (for 3D transformation with homogeneous coordinates)
|
||||||
|
fn to_matrix4(&self) -> [f32; 16] {
|
||||||
|
// Normalize quaternion
|
||||||
|
let magnitude =
|
||||||
|
(self.x * self.x + self.y * self.y + self.z * self.z + self.w * self.w).sqrt();
|
||||||
|
let x = self.x / magnitude;
|
||||||
|
let y = self.y / magnitude;
|
||||||
|
let z = self.z / magnitude;
|
||||||
|
let w = self.w / magnitude;
|
||||||
|
|
||||||
|
let x2 = x * x;
|
||||||
|
let y2 = y * y;
|
||||||
|
let z2 = z * z;
|
||||||
|
let xy = x * y;
|
||||||
|
let xz = x * z;
|
||||||
|
let yz = y * z;
|
||||||
|
let wx = w * x;
|
||||||
|
let wy = w * y;
|
||||||
|
let wz = w * z;
|
||||||
|
|
||||||
|
// Row-major 3x3 rotation matrix components
|
||||||
|
let m00 = 1.0 - 2.0 * (y2 + z2);
|
||||||
|
let m01 = 2.0 * (xy - wz);
|
||||||
|
let m02 = 2.0 * (xz + wy);
|
||||||
|
|
||||||
|
let m10 = 2.0 * (xy + wz);
|
||||||
|
let m11 = 1.0 - 2.0 * (x2 + z2);
|
||||||
|
let m12 = 2.0 * (yz - wx);
|
||||||
|
|
||||||
|
let m20 = 2.0 * (xz - wy);
|
||||||
|
let m21 = 2.0 * (yz + wx);
|
||||||
|
let m22 = 1.0 - 2.0 * (x2 + y2);
|
||||||
|
|
||||||
|
// Convert 3x3 rotation matrix to 4x4 transformation matrix
|
||||||
|
// Note: This is column-major for WGPU
|
||||||
|
[
|
||||||
|
m00, m10, m20, 0.0, m01, m11, m21, 0.0, m02, m12, m22, 0.0, 0.0, 0.0, 0.0, 1.0,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matrix utilities for perspective projection
|
||||||
|
fn perspective_matrix(fovy_radians: f32, aspect: f32, near: f32, far: f32) -> [f32; 16] {
|
||||||
|
let f = 1.0 / (fovy_radians / 2.0).tan();
|
||||||
|
let nf = 1.0 / (near - far);
|
||||||
|
|
||||||
|
// Column-major for WGPU
|
||||||
|
[
|
||||||
|
f / aspect,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
f,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
(far + near) * nf,
|
||||||
|
-1.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
2.0 * far * near * nf,
|
||||||
|
0.0,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine two 4x4 matrices (column-major)
|
||||||
|
fn matrix_multiply(a: &[f32; 16], b: &[f32; 16]) -> [f32; 16] {
|
||||||
|
let mut result = [0.0; 16];
|
||||||
|
|
||||||
|
for row in 0..4 {
|
||||||
|
for col in 0..4 {
|
||||||
|
let mut sum = 0.0;
|
||||||
|
for i in 0..4 {
|
||||||
|
sum += a[row + i * 4] * b[i + col * 4];
|
||||||
|
}
|
||||||
|
result[row + col * 4] = sum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DaveAvatar {
|
||||||
|
pub fn new(wgpu_render_state: &egui_wgpu::RenderState) -> Self {
|
||||||
|
let device = &wgpu_render_state.device;
|
||||||
|
|
||||||
|
// Create shader module with improved shader code
|
||||||
|
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||||
|
label: Some("cube_shader"),
|
||||||
|
source: wgpu::ShaderSource::Wgsl(
|
||||||
|
r#"
|
||||||
|
struct Uniforms {
|
||||||
|
model_view_proj: mat4x4<f32>,
|
||||||
|
};
|
||||||
|
|
||||||
|
@group(0) @binding(0)
|
||||||
|
var<uniform> uniforms: Uniforms;
|
||||||
|
|
||||||
|
struct VertexOutput {
|
||||||
|
@builtin(position) position: vec4<f32>,
|
||||||
|
@location(0) color: vec4<f32>,
|
||||||
|
};
|
||||||
|
|
||||||
|
@vertex
|
||||||
|
fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
|
||||||
|
// Define cube vertices (-0.5 to 0.5 in each dimension)
|
||||||
|
var positions = array<vec3<f32>, 8>(
|
||||||
|
vec3<f32>(-0.5, -0.5, -0.5), // 0: left bottom back
|
||||||
|
vec3<f32>(0.5, -0.5, -0.5), // 1: right bottom back
|
||||||
|
vec3<f32>(-0.5, 0.5, -0.5), // 2: left top back
|
||||||
|
vec3<f32>(0.5, 0.5, -0.5), // 3: right top back
|
||||||
|
vec3<f32>(-0.5, -0.5, 0.5), // 4: left bottom front
|
||||||
|
vec3<f32>(0.5, -0.5, 0.5), // 5: right bottom front
|
||||||
|
vec3<f32>(-0.5, 0.5, 0.5), // 6: left top front
|
||||||
|
vec3<f32>(0.5, 0.5, 0.5) // 7: right top front
|
||||||
|
);
|
||||||
|
|
||||||
|
// Define indices for the 12 triangles (6 faces * 2 triangles)
|
||||||
|
var indices = array<u32, 36>(
|
||||||
|
// back face (Z-)
|
||||||
|
0, 2, 1, 1, 2, 3,
|
||||||
|
// front face (Z+)
|
||||||
|
4, 5, 6, 5, 7, 6,
|
||||||
|
// left face (X-)
|
||||||
|
0, 4, 2, 2, 4, 6,
|
||||||
|
// right face (X+)
|
||||||
|
1, 3, 5, 3, 7, 5,
|
||||||
|
// bottom face (Y-)
|
||||||
|
0, 1, 4, 1, 5, 4,
|
||||||
|
// top face (Y+)
|
||||||
|
2, 6, 3, 3, 6, 7
|
||||||
|
);
|
||||||
|
|
||||||
|
// Define colors for each face
|
||||||
|
var face_colors = array<vec4<f32>, 6>(
|
||||||
|
vec4<f32>(1.0, 0.0, 0.0, 1.0), // back: red
|
||||||
|
vec4<f32>(0.0, 1.0, 0.0, 1.0), // front: green
|
||||||
|
vec4<f32>(0.0, 0.0, 1.0, 1.0), // left: blue
|
||||||
|
vec4<f32>(1.0, 1.0, 0.0, 1.0), // right: yellow
|
||||||
|
vec4<f32>(1.0, 0.0, 1.0, 1.0), // bottom: magenta
|
||||||
|
vec4<f32>(0.0, 1.0, 1.0, 1.0) // top: cyan
|
||||||
|
);
|
||||||
|
|
||||||
|
var output: VertexOutput;
|
||||||
|
|
||||||
|
// Get vertex from indices
|
||||||
|
let index = indices[vertex_index];
|
||||||
|
let position = positions[index];
|
||||||
|
|
||||||
|
// Determine which face this vertex belongs to
|
||||||
|
let face_index = vertex_index / 6u;
|
||||||
|
|
||||||
|
// Apply model-view-projection matrix
|
||||||
|
output.position = uniforms.model_view_proj * vec4<f32>(position, 1.0);
|
||||||
|
|
||||||
|
// Set color based on face
|
||||||
|
output.color = face_colors[face_index];
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
|
return in.color;
|
||||||
|
}
|
||||||
|
"#
|
||||||
|
.into(),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create uniform buffer for MVP matrix
|
||||||
|
let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: Some("cube_uniform_buffer"),
|
||||||
|
size: 64, // 4x4 matrix of f32 (16 * 4 bytes)
|
||||||
|
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create bind group layout
|
||||||
|
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
|
label: Some("cube_bind_group_layout"),
|
||||||
|
entries: &[wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
visibility: wgpu::ShaderStages::VERTEX,
|
||||||
|
ty: wgpu::BindingType::Buffer {
|
||||||
|
ty: wgpu::BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: NonZeroU64::new(64),
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create bind group
|
||||||
|
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
label: Some("cube_bind_group"),
|
||||||
|
layout: &bind_group_layout,
|
||||||
|
entries: &[wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: uniform_buffer.as_entire_binding(),
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create pipeline layout
|
||||||
|
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
|
label: Some("cube_pipeline_layout"),
|
||||||
|
bind_group_layouts: &[&bind_group_layout],
|
||||||
|
push_constant_ranges: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create render pipeline
|
||||||
|
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: Some("cube_pipeline"),
|
||||||
|
layout: Some(&pipeline_layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: Some("vs_main"),
|
||||||
|
buffers: &[], // No vertex buffer - vertices are in the shader
|
||||||
|
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: Some("fs_main"),
|
||||||
|
targets: &[Some(wgpu::ColorTargetState {
|
||||||
|
format: wgpu_render_state.target_format,
|
||||||
|
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
|
||||||
|
write_mask: wgpu::ColorWrites::ALL,
|
||||||
|
})],
|
||||||
|
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState {
|
||||||
|
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||||
|
strip_index_format: None,
|
||||||
|
front_face: wgpu::FrontFace::Ccw,
|
||||||
|
cull_mode: Some(wgpu::Face::Back),
|
||||||
|
polygon_mode: wgpu::PolygonMode::Fill,
|
||||||
|
unclipped_depth: false,
|
||||||
|
conservative: false,
|
||||||
|
},
|
||||||
|
depth_stencil: Some(wgpu::DepthStencilState {
|
||||||
|
format: wgpu::TextureFormat::Depth24Plus,
|
||||||
|
depth_write_enabled: true,
|
||||||
|
depth_compare: wgpu::CompareFunction::Less,
|
||||||
|
stencil: wgpu::StencilState::default(),
|
||||||
|
bias: wgpu::DepthBiasState::default(),
|
||||||
|
}),
|
||||||
|
multisample: wgpu::MultisampleState::default(),
|
||||||
|
multiview: None,
|
||||||
|
cache: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Store resources in renderer
|
||||||
|
wgpu_render_state
|
||||||
|
.renderer
|
||||||
|
.write()
|
||||||
|
.callback_resources
|
||||||
|
.insert(CubeRenderResources {
|
||||||
|
pipeline,
|
||||||
|
bind_group,
|
||||||
|
uniform_buffer,
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
rotation: Quaternion::identity(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DaveAvatar {
|
||||||
|
pub fn render(&mut self, rect: Rect, ui: &mut egui::Ui) -> Response {
|
||||||
|
let response = ui.allocate_rect(rect, egui::Sense::drag());
|
||||||
|
|
||||||
|
// Update rotation based on drag or animation
|
||||||
|
if response.dragged() {
|
||||||
|
// Create rotation quaternions based on drag
|
||||||
|
let x_rotation =
|
||||||
|
Quaternion::from_axis_angle([1.0, 0.0, 0.0], response.drag_delta().y * 0.01);
|
||||||
|
let y_rotation =
|
||||||
|
Quaternion::from_axis_angle([0.0, 1.0, 0.0], response.drag_delta().x * 0.01);
|
||||||
|
|
||||||
|
// Apply rotations (order matters)
|
||||||
|
self.rotation = y_rotation.multiply(&x_rotation).multiply(&self.rotation);
|
||||||
|
} else {
|
||||||
|
// Continuous rotation - reduced speed and simplified axis
|
||||||
|
let continuous_rotation = Quaternion::from_axis_angle([0.0, 1.0, 0.0], 0.005);
|
||||||
|
self.rotation = continuous_rotation.multiply(&self.rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create model matrix from rotation quaternion
|
||||||
|
let model_matrix = self.rotation.to_matrix4();
|
||||||
|
|
||||||
|
// Create projection matrix with proper depth range
|
||||||
|
// Adjust aspect ratio based on rect dimensions
|
||||||
|
let aspect = rect.width() / rect.height();
|
||||||
|
let projection = perspective_matrix(std::f32::consts::PI / 4.0, aspect, 0.1, 100.0);
|
||||||
|
|
||||||
|
// Create view matrix (move camera back a bit)
|
||||||
|
let view_matrix = [
|
||||||
|
1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, -3.0, 1.0,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Combine matrices: projection * view * model
|
||||||
|
let mv_matrix = matrix_multiply(&view_matrix, &model_matrix);
|
||||||
|
let mvp_matrix = matrix_multiply(&projection, &mv_matrix);
|
||||||
|
|
||||||
|
// Request continuous rendering
|
||||||
|
ui.ctx().request_repaint();
|
||||||
|
|
||||||
|
// Add paint callback
|
||||||
|
ui.painter().add(egui_wgpu::Callback::new_paint_callback(
|
||||||
|
rect,
|
||||||
|
CubeCallback { mvp_matrix },
|
||||||
|
));
|
||||||
|
|
||||||
|
response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback implementation
|
||||||
|
struct CubeCallback {
|
||||||
|
mvp_matrix: [f32; 16], // Model-View-Projection matrix
|
||||||
|
}
|
||||||
|
|
||||||
|
impl egui_wgpu::CallbackTrait for CubeCallback {
|
||||||
|
fn prepare(
|
||||||
|
&self,
|
||||||
|
_device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
_screen_descriptor: &egui_wgpu::ScreenDescriptor,
|
||||||
|
_egui_encoder: &mut wgpu::CommandEncoder,
|
||||||
|
resources: &mut egui_wgpu::CallbackResources,
|
||||||
|
) -> Vec<wgpu::CommandBuffer> {
|
||||||
|
let resources: &CubeRenderResources = resources.get().unwrap();
|
||||||
|
|
||||||
|
// Update uniform buffer with MVP matrix
|
||||||
|
queue.write_buffer(
|
||||||
|
&resources.uniform_buffer,
|
||||||
|
0,
|
||||||
|
bytemuck::cast_slice(&self.mvp_matrix),
|
||||||
|
);
|
||||||
|
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(
|
||||||
|
&self,
|
||||||
|
_info: egui::PaintCallbackInfo,
|
||||||
|
render_pass: &mut wgpu::RenderPass,
|
||||||
|
resources: &egui_wgpu::CallbackResources,
|
||||||
|
) {
|
||||||
|
let resources: &CubeRenderResources = resources.get().unwrap();
|
||||||
|
|
||||||
|
render_pass.set_pipeline(&resources.pipeline);
|
||||||
|
render_pass.set_bind_group(0, &resources.bind_group, &[]);
|
||||||
|
render_pass.draw(0..36, 0..1); // 36 vertices for a cube (6 faces * 2 triangles * 3 vertices)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple resources struct
|
||||||
|
struct CubeRenderResources {
|
||||||
|
pipeline: wgpu::RenderPipeline,
|
||||||
|
bind_group: wgpu::BindGroup,
|
||||||
|
uniform_buffer: wgpu::Buffer,
|
||||||
|
}
|
||||||
211
crates/notedeck_dave/src/lib.rs
Normal file
211
crates/notedeck_dave/src/lib.rs
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
use async_openai::{
|
||||||
|
config::OpenAIConfig,
|
||||||
|
types::{
|
||||||
|
ChatCompletionRequestAssistantMessage, ChatCompletionRequestAssistantMessageContent,
|
||||||
|
ChatCompletionRequestMessage, ChatCompletionRequestUserMessage,
|
||||||
|
ChatCompletionRequestUserMessageContent, CreateChatCompletionRequest,
|
||||||
|
},
|
||||||
|
Client,
|
||||||
|
};
|
||||||
|
use futures::StreamExt;
|
||||||
|
use notedeck::AppContext;
|
||||||
|
use std::sync::mpsc::{self, Receiver};
|
||||||
|
|
||||||
|
use avatar::DaveAvatar;
|
||||||
|
use egui::{Rect, Vec2};
|
||||||
|
use egui_wgpu::RenderState;
|
||||||
|
|
||||||
|
mod avatar;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Message {
|
||||||
|
User(String),
|
||||||
|
Assistant(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message {
|
||||||
|
pub fn to_api_msg(&self) -> ChatCompletionRequestMessage {
|
||||||
|
match self {
|
||||||
|
Message::User(msg) => {
|
||||||
|
ChatCompletionRequestMessage::User(ChatCompletionRequestUserMessage {
|
||||||
|
name: None,
|
||||||
|
content: ChatCompletionRequestUserMessageContent::Text(msg.clone()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Message::Assistant(msg) => {
|
||||||
|
ChatCompletionRequestMessage::Assistant(ChatCompletionRequestAssistantMessage {
|
||||||
|
content: Some(ChatCompletionRequestAssistantMessageContent::Text(
|
||||||
|
msg.clone(),
|
||||||
|
)),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Dave {
|
||||||
|
chat: Vec<Message>,
|
||||||
|
/// A 3d representation of dave.
|
||||||
|
avatar: Option<DaveAvatar>,
|
||||||
|
input: String,
|
||||||
|
pubkey: String,
|
||||||
|
client: async_openai::Client<OpenAIConfig>,
|
||||||
|
incoming_tokens: Option<Receiver<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dave {
|
||||||
|
pub fn new(render_state: Option<&RenderState>) -> Self {
|
||||||
|
let mut config = OpenAIConfig::new();
|
||||||
|
if let Ok(api_key) = std::env::var("OPENAI_API_KEY") {
|
||||||
|
config = config.with_api_key(api_key);
|
||||||
|
}
|
||||||
|
let client = Client::with_config(config);
|
||||||
|
|
||||||
|
let input = "".to_string();
|
||||||
|
let pubkey = "test_pubkey".to_string();
|
||||||
|
let avatar = render_state.map(DaveAvatar::new);
|
||||||
|
|
||||||
|
Dave {
|
||||||
|
client,
|
||||||
|
pubkey,
|
||||||
|
avatar,
|
||||||
|
incoming_tokens: None,
|
||||||
|
input,
|
||||||
|
chat: vec![
|
||||||
|
Message::User("how do I computer".to_string()),
|
||||||
|
Message::Assistant("Seriously?".to_string()),
|
||||||
|
Message::User("ye".to_string()),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&mut self, ui: &mut egui::Ui) {
|
||||||
|
if let Some(recvr) = &self.incoming_tokens {
|
||||||
|
if let Ok(token) = recvr.try_recv() {
|
||||||
|
match self.chat.last_mut() {
|
||||||
|
Some(Message::Assistant(msg)) => *msg = msg.clone() + " " + &token,
|
||||||
|
Some(_) => self.chat.push(Message::Assistant(token)),
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scroll area for chat messages
|
||||||
|
egui::Frame::new().inner_margin(10.0).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(ui);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(avatar) = &mut self.avatar {
|
||||||
|
let avatar_size = Vec2::splat(400.0);
|
||||||
|
let pos = Vec2::splat(100.0).to_pos2();
|
||||||
|
let pos = Rect::from_min_max(pos, pos + avatar_size);
|
||||||
|
avatar.render(pos, ui);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_chat(&self, ui: &mut egui::Ui) {
|
||||||
|
for message in &self.chat {
|
||||||
|
match message {
|
||||||
|
Message::User(msg) => self.user_chat(msg, ui),
|
||||||
|
Message::Assistant(msg) => self.system_chat(msg, ui),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inputbox(&mut self, ui: &mut egui::Ui) {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.add(egui::TextEdit::multiline(&mut self.input));
|
||||||
|
if ui.button("Sned").clicked() {
|
||||||
|
self.send_user_message(ui.ctx());
|
||||||
|
self.input.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn user_chat(&self, msg: &str, ui: &mut egui::Ui) {
|
||||||
|
ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| {
|
||||||
|
ui.label(msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn system_chat(&self, msg: &str, ui: &mut egui::Ui) {
|
||||||
|
ui.with_layout(egui::Layout::left_to_right(egui::Align::TOP), |ui| {
|
||||||
|
ui.label(msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_user_message(&mut self, ctx: &egui::Context) {
|
||||||
|
let messages = self.chat.iter().map(|c| c.to_api_msg()).collect();
|
||||||
|
let pubkey = self.pubkey.clone();
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
self.incoming_tokens = Some(rx);
|
||||||
|
let ctx = ctx.clone();
|
||||||
|
let client = self.client.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let mut token_stream = match client
|
||||||
|
.chat()
|
||||||
|
.create_stream(CreateChatCompletionRequest {
|
||||||
|
model: "gpt-4o".to_string(),
|
||||||
|
stream: Some(true),
|
||||||
|
messages,
|
||||||
|
user: Some(pubkey),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Err(err) => {
|
||||||
|
tracing::error!("openai chat error: {err}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(stream) => stream,
|
||||||
|
};
|
||||||
|
|
||||||
|
tracing::info!("got stream!");
|
||||||
|
|
||||||
|
while let Some(token) = token_stream.next().await {
|
||||||
|
let token = match token {
|
||||||
|
Ok(token) => token,
|
||||||
|
Err(err) => {
|
||||||
|
tracing::error!("failed to get token: {err}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let Some(choice) = token.choices.first() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(content) = &choice.delta.content else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
tracing::debug!("got token: {content}");
|
||||||
|
|
||||||
|
tx.send(content.to_owned()).unwrap();
|
||||||
|
ctx.request_repaint();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl notedeck::App for Dave {
|
||||||
|
fn update(&mut self, _ctx: &mut AppContext<'_>, ui: &mut egui::Ui) {
|
||||||
|
/*
|
||||||
|
self.app
|
||||||
|
.frame_history
|
||||||
|
.on_new_frame(ctx.input(|i| i.time), frame.info().cpu_usage);
|
||||||
|
*/
|
||||||
|
|
||||||
|
//update_dave(self, ctx, ui.ctx());
|
||||||
|
self.render(ui);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user