Fix memory page to use local store
This commit is contained in:
parent
061cc9225a
commit
9a8f058008
|
|
@ -16,7 +16,6 @@ from duck_core.config import get_settings
|
|||
from duck_core.conversations.store import ConversationStore
|
||||
from duck_core.events.store import EventStore
|
||||
from duck_core.experience.recorder import ExperienceRecorder
|
||||
from duck_core.memory.vector_memory import EmbeddingsUnavailableError, VectorMemory
|
||||
from duck_core.memory.store import MemoryStore
|
||||
from duck_core.model_client import ModelClient
|
||||
from duck_core.runtime_loop import RuntimeLoop
|
||||
|
|
@ -79,7 +78,6 @@ def create_app() -> FastAPI:
|
|||
runtime = RuntimeLoop(task_store, event_store, model_client, approval_service=approvals)
|
||||
skills = SkillRegistry("skills")
|
||||
experience = ExperienceRecorder(settings.db_path)
|
||||
memory = VectorMemory(settings.qdrant_url, embeddings_base_url=None)
|
||||
memory_store = MemoryStore(settings.db_path)
|
||||
|
||||
@app.on_event("startup")
|
||||
|
|
@ -867,12 +865,7 @@ def create_app() -> FastAPI:
|
|||
q: str, workspace: str | None = None, limit: int = 20
|
||||
) -> dict[str, Any]:
|
||||
local_results = await memory_store.search(q, workspace=workspace, limit=limit)
|
||||
if local_results:
|
||||
return {"results": [record.model_dump() for record in local_results]}
|
||||
try:
|
||||
return {"results": await memory.search_memory(q)}
|
||||
except EmbeddingsUnavailableError as exc:
|
||||
return {"results": [], "warning": str(exc)}
|
||||
return {"results": [record.model_dump() for record in local_results]}
|
||||
|
||||
return app
|
||||
|
||||
|
|
|
|||
|
|
@ -929,10 +929,62 @@ document.querySelector("#approvals")?.addEventListener("click", async (event) =>
|
|||
|
||||
document.querySelector("#memory-search")?.addEventListener("click", async () => {
|
||||
const q = document.querySelector("#memory-query").value;
|
||||
document.querySelector("#memory-results").textContent =
|
||||
JSON.stringify(await jsonFetch(`/v1/memory/search?q=${encodeURIComponent(q)}`), null, 2);
|
||||
await renderMemoryPageResults(q);
|
||||
});
|
||||
|
||||
document.querySelector("#memory-list-all")?.addEventListener("click", async () => {
|
||||
await renderMemoryPageResults("");
|
||||
});
|
||||
|
||||
document.querySelector("#memory-page-form")?.addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
const textInput = document.querySelector("#memory-page-text");
|
||||
const workspaceInput = document.querySelector("#memory-page-workspace");
|
||||
const text = textInput?.value.trim() || "";
|
||||
if (!text) return;
|
||||
await jsonFetch("/v1/memory", {
|
||||
method: "POST",
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: JSON.stringify({
|
||||
text,
|
||||
workspace: workspaceInput?.value.trim() || null,
|
||||
memory_type: "note",
|
||||
importance: 0.6,
|
||||
}),
|
||||
});
|
||||
textInput.value = "";
|
||||
await renderMemoryPageResults("");
|
||||
});
|
||||
|
||||
async function renderMemoryPageResults(query) {
|
||||
const container = document.querySelector("#memory-results");
|
||||
if (!container) return;
|
||||
const payload = query.trim()
|
||||
? await jsonFetch(`/v1/memory/search?q=${encodeURIComponent(query.trim())}`)
|
||||
: await jsonFetch("/v1/memory?limit=100");
|
||||
const results = payload.results || [];
|
||||
container.innerHTML = "";
|
||||
if (!results.length) {
|
||||
const empty = document.createElement("p");
|
||||
empty.className = "compact-empty";
|
||||
empty.textContent = "No memories found.";
|
||||
container.append(empty);
|
||||
return;
|
||||
}
|
||||
for (const memory of results) {
|
||||
const item = document.createElement("article");
|
||||
item.className = "memory-item";
|
||||
const text = document.createElement("p");
|
||||
text.textContent = memory.text;
|
||||
const meta = document.createElement("span");
|
||||
meta.textContent = `${memory.scope || "memory"} · ${memory.workspace || "global"} · ${memory.memory_type || "note"}`;
|
||||
item.append(text, meta);
|
||||
container.append(item);
|
||||
}
|
||||
}
|
||||
|
||||
renderMemoryPageResults("").catch(console.error);
|
||||
|
||||
bindChat();
|
||||
checkRuntime();
|
||||
loadSimplePages().catch(console.error);
|
||||
|
|
|
|||
|
|
@ -1,2 +1,33 @@
|
|||
<!doctype html>
|
||||
<html lang="en"><head><meta charset="utf-8"><title>DuckLM Memory</title><link rel="stylesheet" href="/static/style.css"></head><body><main class="shell"><h1>Memory</h1><input id="memory-query" placeholder="Search memory"><button id="memory-search">Search</button><pre id="memory-results"></pre><script src="/static/app.js"></script></main></body></html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>DuckLM Memory</title>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="simple-page">
|
||||
<header class="simple-header">
|
||||
<div>
|
||||
<h1>Memory</h1>
|
||||
<p>Local DuckLM memory stored in SQLite.</p>
|
||||
</div>
|
||||
<a class="secondary-button" href="/">Chat</a>
|
||||
</header>
|
||||
<section class="memory-page-panel">
|
||||
<form id="memory-page-form" class="memory-form">
|
||||
<input id="memory-page-text" placeholder="Add memory" autocomplete="off">
|
||||
<input id="memory-page-workspace" placeholder="Workspace, optional" autocomplete="off">
|
||||
<button type="submit">Add</button>
|
||||
</form>
|
||||
<div class="memory-search-row">
|
||||
<input id="memory-query" placeholder="Search memory" autocomplete="off">
|
||||
<button id="memory-search" type="button">Search</button>
|
||||
<button id="memory-list-all" type="button">All</button>
|
||||
</div>
|
||||
<div id="memory-results" class="memory-list"></div>
|
||||
</section>
|
||||
</main>
|
||||
<script src="/static/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -44,6 +44,15 @@ def test_memory_api_stores_workspace_scoped_notes(tmp_path, monkeypatch):
|
|||
assert "Different workspace note." not in str(search)
|
||||
|
||||
|
||||
def test_memory_search_returns_empty_local_result_without_vector_warning(tmp_path, monkeypatch):
|
||||
monkeypatch.setenv("DUCK_DB_PATH", str(tmp_path / "duck.sqlite3"))
|
||||
client = TestClient(create_app())
|
||||
|
||||
response = client.get("/v1/memory/search", params={"q": "missing memory"}).json()
|
||||
|
||||
assert response == {"results": []}
|
||||
|
||||
|
||||
async def test_memory_store_searches_text_and_metadata(tmp_path):
|
||||
store = MemoryStore(str(tmp_path / "duck.sqlite3"))
|
||||
await store.init()
|
||||
|
|
|
|||
Loading…
Reference in New Issue