7.8 KiB
Chat Page — Discrete Event Model
Goal
Redesign the sovereign://agents/chat page to work with sovereign_browser's
discrete event Nostr interaction model (no live subscriptions). The page
needs explicit Save/Refresh buttons for conversations and skills, matching
how the rest of the browser works.
What's wrong now
The current chat page tries to mimic ai.html's live-subscription model:
- It auto-saves conversations after each agent response (via a debounced
scheduleSave()) - It fetches the conversation list on page load but has no way to refresh
- It fetches skills on page load but has no way to refresh
- New Chat saves an empty conversation immediately (good), but there's no explicit save button for renaming or manual saves
The user wants explicit control: Save to publish, Refresh to fetch.
Design
Conversation list (left pane, top section)
┌─────────────────────────────┐
│ [+ New Chat] [↻ Refresh] │
├─────────────────────────────┤
│ > Capital of France [×] │ ← selected, click to load
│ EU population [×] │
│ Hello world [×] │
└─────────────────────────────┘
- + New Chat — Creates a new local session (does NOT save to Nostr yet). The conversation is "unsaved" until the user sends a message or clicks Save.
- ↻ Refresh — Fetches the conversation list from Nostr (one-shot relay
query via
relay_fetch.c) and updates the list. Shows a "Refreshing..." state while fetching. - Click a conversation — Loads it from the local SQLite cache (fast, no relay fetch). If the conversation isn't in the cache, fetch it from Nostr.
- [×] delete button — Deletes the conversation (publishes kind 5 tombstone, removes from local cache).
Chat thread (right pane)
┌─────────────────────────────────────────────┐
│ Capital of France [✏ Rename] [💾] │ ← conversation header
├─────────────────────────────────────────────┤
│ You: What is the capital of France? │
│ Assistant: The capital of France is Paris. │
│ │
│ [⋯] │ ← dot-menu on each bubble
├─────────────────────────────────────────────┤
│ [Type a message... ] [Send] │
└─────────────────────────────────────────────┘
- ✏ Rename — Inline edit the conversation title. Saves to Nostr on Enter or blur.
- 💾 Save — Explicitly saves the conversation to Nostr (publishes kind 30078 with the current messages + title). Shows "Saved!" feedback.
- Auto-save is removed — The user must click Save to persist. The only exception: the first message exchange auto-saves once (to create the Nostr event), so the conversation appears in the list on other devices. After that, explicit Save only.
Skills list (left pane, bottom section)
┌─────────────────────────────┐
│ Skills [↻ Refresh] │
├─────────────────────────────┤
│ [✓] Web scraper [requires: │
│ browser,fs] [×] │
│ [ ] Code reviewer [requires: │
│ shell] [×] │
│ [+ Create Skill] │
└─────────────────────────────┘
- ↻ Refresh — Fetches skills from Nostr (one-shot relay query) and updates the list.
- Checkbox — Toggle skill selection (saved locally, not to Nostr).
- [×] delete — Deletes the skill (kind 5 tombstone) — only for user-authored skills.
- + Create Skill — Opens the create form. "Publish" button saves to Nostr.
New endpoints needed
-
sovereign://agents/conversations/refresh(GET) — Triggers a one-shot relay fetch for kind 30078 events witht:client-ai-chat-v1by the user's pubkey. Stores results in SQLite. Returns{"status":"refreshed","count":N}when done. This is a blocking call (waits for relay response or timeout). -
sovereign://agents/skills/refresh(GET) — Triggers a one-shot relay fetch for kind 31123 events. Stores in SQLite. Returns{"status":"refreshed","count":N}. -
sovereign://agents/conversations/rename(GET with?id=...&title=...) — Renames a conversation. Updates the local SQLite cache and re-publishes the kind 30078 event with the new title. Returns{"status":"renamed"}.
Changes to existing endpoints
-
sovereign://agents/conversations/new— Should NOT save to Nostr immediately. Just create a local session. The conversation is saved to Nostr on the first explicit Save (or after the first message exchange). -
sovereign://agents/conversations/save— Keep as-is (explicit save). Remove the auto-save call fromapplyStatus()in chat.js.
Changes to www/agents/chat.js
-
Remove
scheduleSave()/doSave()auto-save — Delete the debounced auto-save logic. The user clicks Save explicitly. -
Add Refresh button handler —
refreshConvList()callssovereign://agents/conversations/refresh, then re-fetches the list. -
Add Rename button handler —
renameConv(id)prompts for a new title, callssovereign://agents/conversations/rename?id=...&title=.... -
Add Save button handler —
saveConv()callssovereign://agents/conversations/save?title=...explicitly. -
Add Skills Refresh button handler —
refreshSkills()callssovereign://agents/skills/refresh, then re-fetches the skills list. -
First-message auto-save — After the first agent response in a new conversation, auto-save once (so the conversation exists on Nostr). After that, no auto-save.
Changes to src/nostr_bridge.c
-
Add
handle_agents_conversations_refresh()— Calls a newagent_conversations_refresh()function that does a one-shot relay fetch. -
Add
handle_agents_skills_refresh()— Calls a newagent_skills_refresh()function that does a one-shot relay fetch. -
Add
handle_agents_conversations_rename()— Callsagent_conversations_rename(id, title). -
Update
handle_agents_conversations_new()— Remove the immediateagent_conversations_save()call. Just create the local session.
Changes to src/agent_conversations.c / src/agent_skills.c
-
Add
agent_conversations_refresh()— One-shot relay fetch for kind 30078t:client-ai-chat-v1events by the user's pubkey. Store in SQLite. This reuses the relay fetch logic fromrelay_fetch.cbut with a specific filter. -
Add
agent_conversations_rename(id, title)— Fetch the existing event, decrypt, update the title, re-encrypt, re-publish. -
Add
agent_skills_refresh()— One-shot relay fetch for kind 31123 events. Store in SQLite.
Changes to www/agents/chat.html
Add the Refresh, Rename, and Save buttons to the HTML structure.
Phasing
Phase 1: Add Refresh buttons (conversations + skills) and the refresh endpoints. Remove auto-save. Add explicit Save button.
Phase 2: Add Rename button and endpoint.
Phase 3: First-message auto-save (so new conversations appear on Nostr after the first exchange, but subsequent saves are explicit).