add option for multiple users in search, add first version of search ui

This commit is contained in:
Believethehype
2024-01-08 16:41:27 +01:00
parent db3e49f384
commit 7f928ae767
5 changed files with 248 additions and 13 deletions

View File

@@ -47,6 +47,7 @@ class AdvancedSearch(DVMTaskInterface):
# default values # default values
user = "" user = ""
users = []
since_days = 800 # days ago since_days = 800 # days ago
until_days = 0 # days ago until_days = 0 # days ago
search = "" search = ""
@@ -61,7 +62,9 @@ class AdvancedSearch(DVMTaskInterface):
param = tag.as_vec()[1] param = tag.as_vec()[1]
if param == "user": # check for param type if param == "user": # check for param type
user = tag.as_vec()[2] user = tag.as_vec()[2]
if param == "since": # check for param type elif param == "users": # check for param type
user = json.loads(tag.as_vec()[2])
elif param == "since": # check for param type
since_days = int(tag.as_vec()[2]) since_days = int(tag.as_vec()[2])
elif param == "until": # check for param type elif param == "until": # check for param type
until_days = int(tag.as_vec()[2]) until_days = int(tag.as_vec()[2])
@@ -71,6 +74,7 @@ class AdvancedSearch(DVMTaskInterface):
options = { options = {
"search": search, "search": search,
"user": user, "user": user,
"users": users,
"since": since_days, "since": since_days,
"until": until_days, "until": until_days,
"max_results": max_results "max_results": max_results
@@ -98,16 +102,37 @@ class AdvancedSearch(DVMTaskInterface):
search_until_seconds = int(options["until"]) * 24 * 60 * 60 search_until_seconds = int(options["until"]) * 24 * 60 * 60
dif = Timestamp.now().as_secs() - search_until_seconds dif = Timestamp.now().as_secs() - search_until_seconds
search_until = Timestamp.from_secs(dif) search_until = Timestamp.from_secs(dif)
userkeys = []
for user in options["users"]:
user = user.as_json()[1]
user = str(user).lstrip("@")
if str(user).startswith('npub'):
userkey = PublicKey.from_bech32(user)
elif str(user).startswith("nostr:npub"):
userkey = PublicKey.from_nostr_uri(user)
else:
userkey = PublicKey.from_hex(user)
if options["user"] == "": userkeys.append(userkey)
if not options["users"] and options["user"] == "":
notes_filter = Filter().kind(1).search(options["search"]).since(search_since).until(search_until).limit( notes_filter = Filter().kind(1).search(options["search"]).since(search_since).until(search_until).limit(
options["max_results"]) options["max_results"])
elif options["search"] == "": elif options["search"] == "":
notes_filter = Filter().kind(1).author(PublicKey.from_hex(options["user"])).since(search_since).until( if options["users"]:
notes_filter = Filter().kind(1).authors(userkeys).since(search_since).until(
search_until).limit(options["max_results"]) search_until).limit(options["max_results"])
else: else:
notes_filter = Filter().kind(1).author(PublicKey.from_hex(options["user"])).search(options["search"]).since( notes_filter = Filter().kind(1).authors([PublicKey.from_hex(options["user"])]).since(search_since).until(
search_until).limit(options["max_results"])
else:
if options["users"]:
notes_filter = Filter().kind(1).authors(userkeys).search(options["search"]).since(
search_since).until(search_until).limit(options["max_results"]) search_since).until(search_until).limit(options["max_results"])
else:
notes_filter = Filter().kind(1).authors([PublicKey.from_hex(options["user"])]).search(options["search"]).since(
search_since).until(search_until).limit(options["max_results"])
events = cli.get_events_of([notes_filter], timedelta(seconds=5)) events = cli.get_events_of([notes_filter], timedelta(seconds=5))

View File

@@ -217,7 +217,7 @@ def fetch_user_metadata(npub, client):
pk = PublicKey.from_hex(npub) pk = PublicKey.from_hex(npub)
print(f"\nGetting profile metadata for {pk.to_bech32()}...") print(f"\nGetting profile metadata for {pk.to_bech32()}...")
profile_filter = Filter().kind(0).author(pk).limit(1) profile_filter = Filter().kind(0).author(pk).limit(1)
events = client.get_events_of([profile_filter], timedelta(seconds=5)) events = client.get_events_of([profile_filter], timedelta(seconds=1))
if len(events) > 0: if len(events) > 0:
latest_entry = events[0] latest_entry = events[0]
latest_time = 0 latest_time = 0

View File

@@ -2,6 +2,7 @@ import json
import os import os
from datetime import timedelta from datetime import timedelta
from pathlib import Path from pathlib import Path
from typing import List
import dotenv import dotenv
from nostr_sdk import Filter, Client, Alphabet, EventId, Event, PublicKey, Tag, Keys, nip04_decrypt, Metadata, Options, \ from nostr_sdk import Filter, Client, Alphabet, EventId, Event, PublicKey, Tag, Keys, nip04_decrypt, Metadata, Options, \
@@ -36,6 +37,15 @@ def get_event_by_id(event_id: str, client: Client, config=None) -> Event | None:
return None return None
def get_events_by_id(event_ids: list, client: Client, config=None) -> list[Event] | None:
id_filter = Filter().ids(event_ids)
events = client.get_events_of([id_filter], timedelta(seconds=config.RELAY_TIMEOUT))
if len(events) > 0:
return events
else:
return None
def get_referenced_event_by_id(event_id, client, dvm_config, kinds) -> Event | None: def get_referenced_event_by_id(event_id, client, dvm_config, kinds) -> Event | None:
if kinds is None: if kinds is None:
kinds = [] kinds = []
@@ -161,7 +171,6 @@ def update_profile(dvm_config, client, lud16=""):
about = nip89content.get("about") about = nip89content.get("about")
image = nip89content.get("image") image = nip89content.get("image")
# Set metadata # Set metadata
metadata = Metadata() \ metadata = Metadata() \
.set_name(name) \ .set_name(name) \
@@ -192,4 +201,3 @@ def add_pk_to_env_file(dtag, oskey):
print(f'loading environment from {env_path.resolve()}') print(f'loading environment from {env_path.resolve()}')
dotenv.load_dotenv(env_path, verbose=True, override=True) dotenv.load_dotenv(env_path, verbose=True, override=True)
dotenv.set_key(env_path, dtag, oskey) dotenv.set_key(env_path, dtag, oskey)

View File

@@ -0,0 +1,200 @@
import asyncio
import json
import time
from datetime import timedelta
from pathlib import Path
from nicegui import run, ui
import dotenv
from nostr_sdk import Keys, Client, Tag, EventBuilder, Filter, HandleNotification, nip04_decrypt, \
nip04_encrypt, Options, Timestamp, ZapRequestData, ClientSigner, EventId, Nip19Event, PublicKey
from nostr_dvm.utils import dvmconfig
from nostr_dvm.utils.database_utils import fetch_user_metadata
from nostr_dvm.utils.dvmconfig import DVMConfig
from nostr_dvm.utils.nip89_utils import nip89_fetch_events_pubkey
from nostr_dvm.utils.nostr_utils import send_event, check_and_set_private_key, get_event_by_id, get_events_by_id
from nostr_dvm.utils.definitions import EventDefinitions
keys = Keys.from_sk_str(check_and_set_private_key("test_client"))
opts = (Options().wait_for_send(False).send_timeout(timedelta(seconds=2))
.skip_disconnected_relays(True))
signer = ClientSigner.KEYS(keys)
client = Client.with_opts(signer, opts)
relay_list = ["wss://relay.damus.io", "wss://blastr.f7z.xyz", "wss://relayable.org", "wss://nostr-pub.wellorder.net"]
for relay in relay_list:
client.add_relay(relay)
client.connect()
dvm_filter = (Filter().pubkey(keys.public_key()).kinds([EventDefinitions.KIND_NIP90_RESULTS_CONTENT_SEARCH,
EventDefinitions.KIND_FEEDBACK])) # public events
client.subscribe([dvm_filter])
def nostr_client_test_search(prompt, users=None, since="", until=""):
if users is None:
users = []
iTag = Tag.parse(["i", prompt, "text"])
# outTag = Tag.parse(["output", "text/plain"])
userTag = Tag.parse(['param', 'users', json.dumps(users)])
sinceTag = Tag.parse(['param', 'since', since])
untilTag = Tag.parse(['param', 'until', until])
maxResultsTag = Tag.parse(['param', 'max_results', "100"])
relaysTag = Tag.parse(['relays', "wss://relay.damus.io", "wss://blastr.f7z.xyz", "wss://relayable.org",
"wss://nostr-pub.wellorder.net"])
alttag = Tag.parse(["alt", "This is a NIP90 DVM AI task to search content"])
tags = [iTag, relaysTag, alttag, maxResultsTag]
if users:
tags.append(userTag)
if since != "":
tags.append(sinceTag)
if until != "":
tags.append(untilTag)
event = EventBuilder(EventDefinitions.KIND_NIP90_CONTENT_SEARCH, str("Search.."),
tags).to_event(keys)
config = DVMConfig
config.RELAY_LIST = relay_list
send_event(event, client=client, dvm_config=config)
return event.as_json()
def handledvm(now):
response = False
feedbackfilter = Filter().pubkey(keys.public_key()).kinds(
[EventDefinitions.KIND_NIP90_RESULTS_CONTENT_SEARCH]).since(now)
feedbackfilter2 = Filter().pubkey(keys.public_key()).kinds(
[EventDefinitions.KIND_FEEDBACK]).since(now)
events = []
fevents = []
while not response:
events = client.get_events_of([feedbackfilter], timedelta(seconds=3))
fevents = client.get_events_of([feedbackfilter2], timedelta(seconds=3))
if len(fevents) > 0:
print(fevents[0].content())
# ui.notify(fevents[0].content())
if len(events) == 0:
response = False
time.sleep(1.0)
continue
else:
event_etags = json.loads(events[0].content())
event_ids = []
for etag in event_etags:
eventidob = EventId.from_hex(etag[1])
event_ids.append(eventidob)
config = DVMConfig()
events = get_events_by_id(event_ids, client, config)
listui = []
for event in events:
nip19event = Nip19Event(event.id(), event.pubkey(), dvmconfig.DVMConfig.RELAY_LIST)
nip19eventid = nip19event.to_bech32()
new = {'result': event.content(), 'author': event.pubkey().to_hex(),
'eventid': str(event.id().to_hex()),
'time': str(event.created_at().to_human_datetime()),
'njump': "https://njump.me/" + nip19eventid,
'highlighter': "https://highlighter.com/a/" + nip19eventid,
'nostrudel': "https://nostrudel.ninja/#/n/" + nip19eventid
}
listui.append(new)
print(event.as_json())
# ui.update(table)
return listui
async def search():
data.clear()
table.clear()
table.visible = False
now = Timestamp.now()
taggedusersfrom = [str(word).lstrip('from:@') for word in prompt.value.split() if word.startswith('from:@')]
taggedusersto = [str(word).lstrip('to:@') for word in prompt.value.split() if word.startswith('to:@')]
search = prompt.value
tags = []
for word in taggedusersfrom:
search = str(search).replace(word, "")
user_pubkey = PublicKey.from_bech32(word).to_hex()
pTag = ["p", user_pubkey]
tags.append(pTag)
search = str(search).replace("from:@", "").replace("to:@", "").lstrip().rstrip()
ev = nostr_client_test_search(search, tags)
ui.notify('Request sent to DVM, awaiting results..')
print("Sent: " + ev)
listui = []
print(str(now.to_human_datetime()))
listui = await run.cpu_bound(handledvm, now)
ui.notify("Received results from DVM")
for element in listui:
table.add_rows(element)
table.visible = True
ui.update(table)
return
if __name__ in {"__main__", "__mp_main__"}:
env_path = Path('.env')
if env_path.is_file():
print(f'loading environment from {env_path.resolve()}')
dotenv.load_dotenv(env_path, verbose=True, override=True)
else:
raise FileNotFoundError(f'.env file not found at {env_path} ')
with ui.row().style('gap:10em').classes("row-1"):
with ui.column().classes("col-1"):
ui.label('NostrAI Search Page').classes('text-2xl')
prompt = ui.input('Search').style('width: 20em')
ui.button('Search', on_click=search).style('width: 15em')
# image = ui.image().style('width: 60em')
columns = [
{'name': 'result', 'label': 'result', 'field': 'result', 'sortable': True, 'align': 'left', },
{'name': 'time', 'label': 'time', 'field': 'time', 'sortable': True, 'align': 'left'},
# {'name': 'eventid', 'label': 'eventid', 'field': 'eventid', 'sortable': True, 'align': 'left'},
]
data = []
# table = ui.table(columns, rows=data).classes('w-full bordered')
table = ui.table(columns=columns, rows=data, row_key='result',
pagination={'rowsPerPage': 10, 'sortBy': 'time', 'descending': True, 'page': 1}).style(
'width: 80em')
table.add_slot('header', r'''
<q-tr :props="props">
<q-th auto-width />
<q-th v-for="col in props.cols" :key="col.name" :props="props">
{{ col.label }}
</q-th>
</q-tr>
''')
table.add_slot('body', r'''
<q-tr :props="props">
<q-td auto-width>
<q-btn size="sm" color="accent" round dense
@click="props.expand = !props.expand"
:icon="props.expand ? 'remove' : 'add'" />
</q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props" width="200px">
{{ col.value }}
</q-td>
</q-tr>
<q-tr v-show="props.expand" :props="props">
<q-td colspan="50%">
<a v-bind:href="props.row.njump">Njump </a>
<a v-bind:href="props.row.highlighter">Highlighter </a>
<a v-bind:href="props.row.nostrudel">NoStrudel</a>
</q-td>
</q-tr>
''')
table.on('action', lambda msg: print(msg))
table.visible = False
# t1 = threading.Thread(target=nostr_client).start()
ui.run(reload=True, port=1234)

View File

@@ -46,13 +46,13 @@ def test_referred_events(event_id, kinds=None):
return None return None
def test_all_reposts_by_user_since_days(pubkey, days): def test_search_by_user_since_days(pubkey, days, prompt):
since_seconds = int(days) * 24 * 60 * 60 since_seconds = int(days) * 24 * 60 * 60
dif = Timestamp.now().as_secs() - since_seconds dif = Timestamp.now().as_secs() - since_seconds
since = Timestamp.from_secs(dif) since = Timestamp.from_secs(dif)
filter = Filter().author(PublicKey.from_hex(pubkey)).kinds([6]).since(since) filterts = Filter().search(prompt).author(pubkey).kinds([1]).since(since)
events = client.get_events_of([filter], timedelta(seconds=5)) events = client.get_events_of([filterts], timedelta(seconds=5))
if len(events) > 0: if len(events) > 0:
for event in events: for event in events:
@@ -87,4 +87,6 @@ if __name__ == '__main__':
nostruri = EventId.from_hex("5635e5dd930b3c831f6ab1e348bb488f3c9aca2f13190e93ab5e5e1e1ba1835e").to_nostr_uri() nostruri = EventId.from_hex("5635e5dd930b3c831f6ab1e348bb488f3c9aca2f13190e93ab5e5e1e1ba1835e").to_nostr_uri()
print(nostruri) print(nostruri)
test_search_by_user_since_days(PublicKey.from_bech32("npub1nxa4tywfz9nqp7z9zp7nr7d4nchhclsf58lcqt5y782rmf2hefjquaa6q8"), 60, "Bitcoin")