mirror of
https://github.com/aljazceru/notedeck.git
synced 2025-12-20 18:04:18 +01:00
dave: add query rendering, fix author queries
This commit is contained in:
@@ -17,8 +17,8 @@ pub use config::ModelConfig;
|
|||||||
pub use messages::{DaveApiResponse, Message};
|
pub use messages::{DaveApiResponse, Message};
|
||||||
pub use quaternion::Quaternion;
|
pub use quaternion::Quaternion;
|
||||||
pub use tools::{
|
pub use tools::{
|
||||||
PartialToolCall, QueryCall, QueryContext, QueryResponse, Tool, ToolCall, ToolCalls,
|
PartialToolCall, QueryCall, QueryResponse, Tool, ToolCall, ToolCalls, ToolResponse,
|
||||||
ToolResponse, ToolResponses,
|
ToolResponses,
|
||||||
};
|
};
|
||||||
pub use ui::{DaveAction, DaveResponse, DaveUi};
|
pub use ui::{DaveAction, DaveResponse, DaveUi};
|
||||||
pub use vec3::Vec3;
|
pub use vec3::Vec3;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use async_openai::types::*;
|
use async_openai::types::*;
|
||||||
use chrono::DateTime;
|
use chrono::DateTime;
|
||||||
use enostr::NoteId;
|
use enostr::{NoteId, Pubkey};
|
||||||
use nostrdb::{Ndb, Note, NoteKey, Transaction};
|
use nostrdb::{Ndb, Note, NoteKey, Transaction};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
@@ -70,7 +70,6 @@ impl PartialToolCall {
|
|||||||
/// The query response from nostrdb for a given context
|
/// The query response from nostrdb for a given context
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct QueryResponse {
|
pub struct QueryResponse {
|
||||||
context: QueryContext,
|
|
||||||
notes: Vec<u64>,
|
notes: Vec<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,14 +285,6 @@ impl ToolResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
|
||||||
#[serde(rename_all = "lowercase")]
|
|
||||||
pub enum QueryContext {
|
|
||||||
Home,
|
|
||||||
Profile,
|
|
||||||
Any,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Called by dave when he wants to display notes on the screen
|
/// Called by dave when he wants to display notes on the screen
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
pub struct PresentNotesCall {
|
pub struct PresentNotesCall {
|
||||||
@@ -342,12 +333,12 @@ impl PresentNotesCall {
|
|||||||
/// The parsed nostrdb query that dave wants to use to satisfy a request
|
/// The parsed nostrdb query that dave wants to use to satisfy a request
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
pub struct QueryCall {
|
pub struct QueryCall {
|
||||||
context: Option<QueryContext>,
|
pub author: Option<Pubkey>,
|
||||||
limit: Option<u64>,
|
pub limit: Option<u64>,
|
||||||
since: Option<u64>,
|
pub since: Option<u64>,
|
||||||
kind: Option<u64>,
|
pub kind: Option<u64>,
|
||||||
until: Option<u64>,
|
pub until: Option<u64>,
|
||||||
search: Option<String>,
|
pub search: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_reply(note: Note) -> bool {
|
fn is_reply(note: Note) -> bool {
|
||||||
@@ -379,6 +370,10 @@ impl QueryCall {
|
|||||||
.custom(|n| !is_reply(n))
|
.custom(|n| !is_reply(n))
|
||||||
.kinds([self.kind.unwrap_or(1)]);
|
.kinds([self.kind.unwrap_or(1)]);
|
||||||
|
|
||||||
|
if let Some(author) = &self.author {
|
||||||
|
filter = filter.authors([author.bytes()]);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(search) = &self.search {
|
if let Some(search) = &self.search {
|
||||||
filter = filter.search(search);
|
filter = filter.search(search);
|
||||||
}
|
}
|
||||||
@@ -398,12 +393,20 @@ impl QueryCall {
|
|||||||
self.limit.unwrap_or(10)
|
self.limit.unwrap_or(10)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn search(&self) -> Option<&str> {
|
pub fn author(&self) -> Option<&Pubkey> {
|
||||||
self.search.as_deref()
|
self.author.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn context(&self) -> QueryContext {
|
pub fn since(&self) -> Option<u64> {
|
||||||
self.context.clone().unwrap_or(QueryContext::Any)
|
self.since
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn until(&self) -> Option<u64> {
|
||||||
|
self.until
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn search(&self) -> Option<&str> {
|
||||||
|
self.search.as_deref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execute(&self, txn: &Transaction, ndb: &Ndb) -> QueryResponse {
|
pub fn execute(&self, txn: &Transaction, ndb: &Ndb) -> QueryResponse {
|
||||||
@@ -414,10 +417,7 @@ impl QueryCall {
|
|||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
QueryResponse {
|
QueryResponse { notes }
|
||||||
context: self.context.clone().unwrap_or(QueryContext::Any),
|
|
||||||
notes,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse(args: &str) -> Result<ToolCalls, ToolCallError> {
|
pub fn parse(args: &str) -> Result<ToolCalls, ToolCallError> {
|
||||||
@@ -556,6 +556,15 @@ fn query_tool() -> Tool {
|
|||||||
description: "Only pull notes up until this unix timestamp. Always include this when searching notes within some date range (yesterday, last week, etc).",
|
description: "Only pull notes up until this unix timestamp. Always include this when searching notes within some date range (yesterday, last week, etc).",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
ToolArg {
|
||||||
|
name: "author",
|
||||||
|
typ: ArgType::String,
|
||||||
|
required: false,
|
||||||
|
default: None,
|
||||||
|
description: "An author *pubkey* to constrain the query on. Can be used to search for notes from individual users. If unsure what pubkey to u
|
||||||
|
se, you can query for kind 0 profiles with the search argument.",
|
||||||
|
},
|
||||||
|
|
||||||
ToolArg {
|
ToolArg {
|
||||||
name: "kind",
|
name: "kind",
|
||||||
typ: ArgType::Number,
|
typ: ArgType::Number,
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
messages::Message,
|
messages::Message,
|
||||||
tools::{PresentNotesCall, QueryCall, QueryContext, ToolCall, ToolCalls, ToolResponse},
|
tools::{PresentNotesCall, QueryCall, ToolCall, ToolCalls, ToolResponse},
|
||||||
};
|
};
|
||||||
use egui::{Align, Key, KeyboardShortcut, Layout, Modifiers};
|
use egui::{Align, Key, KeyboardShortcut, Layout, Modifiers};
|
||||||
use nostrdb::Transaction;
|
use nostrdb::{Ndb, Transaction};
|
||||||
use notedeck::{AppContext, NoteContext};
|
use notedeck::{AppContext, NoteContext};
|
||||||
use notedeck_ui::{icons::search_icon, NoteOptions};
|
use notedeck_ui::{icons::search_icon, NoteOptions, ProfilePic};
|
||||||
|
|
||||||
/// DaveUi holds all of the data it needs to render itself
|
/// DaveUi holds all of the data it needs to render itself
|
||||||
pub struct DaveUi<'a> {
|
pub struct DaveUi<'a> {
|
||||||
@@ -153,21 +153,11 @@ impl<'a> DaveUi<'a> {
|
|||||||
//ui.label(format!("tool_response: {:?}", tool_response));
|
//ui.label(format!("tool_response: {:?}", tool_response));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_call_ui(query_call: &QueryCall, ui: &mut egui::Ui) {
|
fn search_call_ui(ctx: &mut AppContext, query_call: &QueryCall, ui: &mut egui::Ui) {
|
||||||
ui.add(search_icon(16.0, 16.0));
|
ui.add(search_icon(16.0, 16.0));
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
let context = match query_call.context() {
|
|
||||||
QueryContext::Profile => "profile ",
|
|
||||||
QueryContext::Any => "",
|
|
||||||
QueryContext::Home => "home ",
|
|
||||||
};
|
|
||||||
|
|
||||||
//TODO: fix this to support any query
|
query_call_ui(ctx.img_cache, ctx.ndb, query_call, ui);
|
||||||
if let Some(search) = query_call.search() {
|
|
||||||
ui.label(format!("Querying {context}for '{search}'"));
|
|
||||||
} else {
|
|
||||||
ui.label(format!("Querying {:?}", &query_call));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The ai has asked us to render some notes, so we do that here
|
/// The ai has asked us to render some notes, so we do that here
|
||||||
@@ -215,15 +205,13 @@ impl<'a> DaveUi<'a> {
|
|||||||
match call.calls() {
|
match call.calls() {
|
||||||
ToolCalls::PresentNotes(call) => Self::present_notes_ui(ctx, call, ui),
|
ToolCalls::PresentNotes(call) => Self::present_notes_ui(ctx, call, ui),
|
||||||
ToolCalls::Query(search_call) => {
|
ToolCalls::Query(search_call) => {
|
||||||
ui.horizontal(|ui| {
|
ui.allocate_ui_with_layout(
|
||||||
egui::Frame::new()
|
egui::vec2(ui.available_size().x, 32.0),
|
||||||
.inner_margin(10.0)
|
Layout::left_to_right(Align::Center),
|
||||||
.corner_radius(10.0)
|
|ui| {
|
||||||
.fill(ui.visuals().widgets.inactive.weak_bg_fill)
|
Self::search_call_ui(ctx, search_call, ui);
|
||||||
.show(ui, |ui| {
|
},
|
||||||
Self::search_call_ui(search_call, ui);
|
);
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -303,3 +291,81 @@ fn new_chat_button() -> impl egui::Widget {
|
|||||||
helper.take_animation_response()
|
helper.take_animation_response()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn query_call_ui(cache: &mut notedeck::Images, ndb: &Ndb, query: &QueryCall, ui: &mut egui::Ui) {
|
||||||
|
ui.spacing_mut().item_spacing.x = 8.0;
|
||||||
|
if let Some(pubkey) = query.author() {
|
||||||
|
let txn = Transaction::new(ndb).unwrap();
|
||||||
|
pill_label_ui(
|
||||||
|
"author",
|
||||||
|
move |ui| {
|
||||||
|
ui.add(
|
||||||
|
ProfilePic::from_profile_or_default(
|
||||||
|
cache,
|
||||||
|
ndb.get_profile_by_pubkey(&txn, pubkey.bytes())
|
||||||
|
.ok()
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
|
.size(ProfilePic::small_size() as f32),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
ui,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(limit) = query.limit {
|
||||||
|
pill_label("limit", &limit.to_string(), ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(since) = query.since {
|
||||||
|
pill_label("since", &since.to_string(), ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(kind) = query.kind {
|
||||||
|
pill_label("kind", &kind.to_string(), ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(until) = query.until {
|
||||||
|
pill_label("until", &until.to_string(), ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(search) = query.search.as_ref() {
|
||||||
|
pill_label("search", search, ui);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pill_label(name: &str, value: &str, ui: &mut egui::Ui) {
|
||||||
|
pill_label_ui(
|
||||||
|
name,
|
||||||
|
move |ui| {
|
||||||
|
ui.label(value);
|
||||||
|
},
|
||||||
|
ui,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pill_label_ui(name: &str, mut value: impl FnMut(&mut egui::Ui), ui: &mut egui::Ui) {
|
||||||
|
egui::Frame::new()
|
||||||
|
.fill(ui.visuals().noninteractive().bg_fill)
|
||||||
|
.inner_margin(egui::Margin::same(4))
|
||||||
|
.corner_radius(egui::CornerRadius::same(10))
|
||||||
|
.stroke(egui::Stroke::new(
|
||||||
|
1.0,
|
||||||
|
ui.visuals().noninteractive().bg_stroke.color,
|
||||||
|
))
|
||||||
|
.show(ui, |ui| {
|
||||||
|
egui::Frame::new()
|
||||||
|
.fill(ui.visuals().noninteractive().weak_bg_fill)
|
||||||
|
.inner_margin(egui::Margin::same(4))
|
||||||
|
.corner_radius(egui::CornerRadius::same(10))
|
||||||
|
.stroke(egui::Stroke::new(
|
||||||
|
1.0,
|
||||||
|
ui.visuals().noninteractive().bg_stroke.color,
|
||||||
|
))
|
||||||
|
.show(ui, |ui| {
|
||||||
|
ui.label(name);
|
||||||
|
});
|
||||||
|
|
||||||
|
value(ui);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user