dave: initial note rendering

Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
William Casarin
2025-04-18 17:03:59 -07:00
parent 43310b271e
commit f496d4b8c4
6 changed files with 205 additions and 72 deletions

View File

@@ -1,5 +1,6 @@
use async_openai::types::*;
use chrono::DateTime;
use enostr::NoteId;
use nostrdb::{Ndb, Note, NoteKey, Transaction};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
@@ -76,6 +77,7 @@ pub struct QueryResponse {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ToolResponses {
Query(QueryResponse),
PresentNotes,
}
#[derive(Debug, Clone)]
@@ -116,6 +118,7 @@ impl PartialToolCall {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ToolCalls {
Query(QueryCall),
PresentNotes(PresentNotesCall),
}
impl ToolCalls {
@@ -129,12 +132,14 @@ impl ToolCalls {
fn name(&self) -> &'static str {
match self {
Self::Query(_) => "search",
Self::PresentNotes(_) => "present",
}
}
fn arguments(&self) -> String {
match self {
Self::Query(search) => serde_json::to_string(search).unwrap(),
Self::PresentNotes(call) => serde_json::to_string(&call.to_simple()).unwrap(),
}
}
}
@@ -289,6 +294,51 @@ pub enum QueryContext {
Any,
}
/// Called by dave when he wants to display notes on the screen
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct PresentNotesCall {
pub note_ids: Vec<NoteId>,
}
impl PresentNotesCall {
fn to_simple(&self) -> PresentNotesCallSimple {
let note_ids = self
.note_ids
.iter()
.map(|nid| hex::encode(nid.bytes()))
.collect::<Vec<_>>()
.join(",");
PresentNotesCallSimple { note_ids }
}
}
/// Called by dave when he wants to display notes on the screen
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct PresentNotesCallSimple {
note_ids: String,
}
impl PresentNotesCall {
fn parse(args: &str) -> Result<ToolCalls, ToolCallError> {
match serde_json::from_str::<PresentNotesCallSimple>(args) {
Ok(call) => {
let note_ids = call
.note_ids
.split(",")
.filter_map(|n| NoteId::from_hex(n).ok())
.collect();
Ok(ToolCalls::PresentNotes(PresentNotesCall { note_ids }))
}
Err(e) => Err(ToolCallError::ArgParseFailure(format!(
"Failed to parse args: '{}', error: {}",
args, e
))),
}
}
}
/// The parsed nostrdb query that dave wants to use to satisfy a request
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct QueryCall {
@@ -385,17 +435,20 @@ impl QueryCall {
/// tool responses
#[derive(Debug, Serialize)]
struct SimpleNote {
note_id: String,
pubkey: String,
name: String,
content: String,
created_at: String,
note_kind: String, // todo: add replying to
note_kind: u64, // todo: add replying to
}
/// Take the result of a tool response and present it to the ai so that
/// it can interepret it and take further action
fn format_tool_response_for_ai(txn: &Transaction, ndb: &Ndb, resp: &ToolResponses) -> String {
match resp {
ToolResponses::PresentNotes => "".to_string(),
ToolResponses::Query(search_r) => {
let simple_notes: Vec<SimpleNote> = search_r
.notes
@@ -415,7 +468,8 @@ fn format_tool_response_for_ai(txn: &Transaction, ndb: &Ndb, resp: &ToolResponse
let content = note.content().to_owned();
let pubkey = hex::encode(note.pubkey());
let note_kind = note_kind_desc(note.kind() as u64);
let note_kind = note.kind() as u64;
let note_id = hex::encode(note.id());
let created_at = {
let datetime =
@@ -424,6 +478,7 @@ fn format_tool_response_for_ai(txn: &Transaction, ndb: &Ndb, resp: &ToolResponse
};
Some(SimpleNote {
note_id,
pubkey,
name,
content,
@@ -438,7 +493,7 @@ fn format_tool_response_for_ai(txn: &Transaction, ndb: &Ndb, resp: &ToolResponse
}
}
fn note_kind_desc(kind: u64) -> String {
fn _note_kind_desc(kind: u64) -> String {
match kind {
1 => "microblog".to_string(),
0 => "profile".to_string(),
@@ -446,6 +501,23 @@ fn note_kind_desc(kind: u64) -> String {
}
}
fn present_tool() -> Tool {
Tool {
name: "present_notes",
parse_call: PresentNotesCall::parse,
description: "A tool for presenting notes to the user for display. Should be called at the end of a response so that the UI can present the notes referred to in the previous message.",
arguments: vec![
ToolArg {
name: "note_ids",
description: "A comma-separated list of hex note ids",
typ: ArgType::String,
required: true,
default: None
}
]
}
}
fn query_tool() -> Tool {
Tool {
name: "query",
@@ -505,5 +577,5 @@ fn query_tool() -> Tool {
}
pub fn dave_tools() -> Vec<Tool> {
vec![query_tool()]
vec![query_tool(), present_tool()]
}