feat: playbook tools, RAG, vision, voice, chat controls, faster boot

- Tool-using playbooks: read-only tool registry (search_memory,
  search_history, search_documents, list_models, get_time), per-playbook
  allowlist, agentic loop, and a "running tool" status indicator.
- Document ingest / RAG: documents table + chunker + embed/cosine retrieval
  reusing the existing stack; Documents page (upload/paste, viewer, delete);
  top-k chunks injected into the system prompt.
- Vision chat: attach images, base64 into /api/chat.
- Voice I/O: Web Speech dictation + read-aloud (browser-native, no backend).
- Chat controls: stop, regenerate, edit-and-resend; num_ctx knob in Settings.
- Per-playbook model override.
- History polish: per-conversation + bulk ShareGPT export.
- Faster ncp start: UI up first, Ollama warms in the background, with a
  per-phase timing readout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jon
2026-07-23 13:31:48 -05:00
co-authored by Claude Opus 4.8
parent f514d3dbe5
commit f509034fdd
15 changed files with 1061 additions and 96 deletions
+61 -15
View File
@@ -4,6 +4,7 @@ import { Playbook } from "./Playbook";
import { Models } from "./Models";
import { Settings } from "./Settings";
import { Memory } from "./Memory";
import { Documents } from "./Documents";
import { Logs } from "./Logs";
import { API_BASE } from "./config";
@@ -128,6 +129,15 @@ function App() {
}
};
// Download ShareGPT JSONL — one conversation if convId given, else all.
const exportConversations = (e, convId) => {
if (e) e.stopPropagation();
const url = convId
? `${API_BASE}/conversations/export?conversation_id=${encodeURIComponent(convId)}`
: `${API_BASE}/conversations/export`;
window.open(url, "_blank");
};
const deleteConversation = async (e, conversationId) => {
e.stopPropagation();
if (!window.confirm("Delete this conversation?")) return;
@@ -149,6 +159,7 @@ function App() {
{ key: "playbook", label: "📖 Playbooks" },
{ key: "models", label: "🤖 Models", badge: isModelPulling },
{ key: "memory", label: "🧠 Memory" },
{ key: "documents", label: "📄 Documents" },
{ key: "logs", label: "📜 Logs" },
{ key: "settings", label: "⚙️ Settings" },
];
@@ -295,21 +306,39 @@ function App() {
}}>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "0.5rem", flexShrink: 0 }}>
<span style={{ fontSize: "0.75rem", color: "#888", textTransform: "uppercase", letterSpacing: "0.05em" }}>Chats</span>
<button
onClick={startNewChat}
title="New chat"
style={{
padding: "0.2rem 0.55rem",
background: "#161616",
color: "#bbb",
border: "1px solid #2a2a2a",
borderRadius: "6px",
cursor: "pointer",
fontSize: "0.75rem",
}}
>
+ New
</button>
<div style={{ display: "flex", gap: "0.35rem" }}>
<button
onClick={() => exportConversations(null, null)}
title="Export all conversations (ShareGPT JSONL)"
disabled={conversations.length === 0}
style={{
padding: "0.2rem 0.5rem",
background: "#161616",
color: conversations.length === 0 ? "#555" : "#bbb",
border: "1px solid #2a2a2a",
borderRadius: "6px",
cursor: conversations.length === 0 ? "default" : "pointer",
fontSize: "0.75rem",
}}
>
Export
</button>
<button
onClick={startNewChat}
title="New chat"
style={{
padding: "0.2rem 0.55rem",
background: "#161616",
color: "#bbb",
border: "1px solid #2a2a2a",
borderRadius: "6px",
cursor: "pointer",
fontSize: "0.75rem",
}}
>
+ New
</button>
</div>
</div>
<input
type="text"
@@ -372,6 +401,22 @@ function App() {
</div>
{isHover && (
<>
<button
onClick={(e) => exportConversations(e, conv.id)}
title="Export this conversation"
style={{
padding: "0.15rem 0.4rem",
background: "transparent",
color: "#888",
border: "1px solid #333",
borderRadius: "4px",
cursor: "pointer",
fontSize: "0.7rem",
flexShrink: 0,
}}
>
📤
</button>
<button
onClick={(e) => renameConversation(e, conv)}
title="Rename"
@@ -434,6 +479,7 @@ function App() {
)}
{currentPage === "playbook" && <Playbook />}
{currentPage === "memory" && <Memory />}
{currentPage === "documents" && <Documents />}
{currentPage === "logs" && <Logs />}
{currentPage === "settings" && <Settings />}
</main>