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.conversations.store import ConversationStore
|
||||||
from duck_core.events.store import EventStore
|
from duck_core.events.store import EventStore
|
||||||
from duck_core.experience.recorder import ExperienceRecorder
|
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.memory.store import MemoryStore
|
||||||
from duck_core.model_client import ModelClient
|
from duck_core.model_client import ModelClient
|
||||||
from duck_core.runtime_loop import RuntimeLoop
|
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)
|
runtime = RuntimeLoop(task_store, event_store, model_client, approval_service=approvals)
|
||||||
skills = SkillRegistry("skills")
|
skills = SkillRegistry("skills")
|
||||||
experience = ExperienceRecorder(settings.db_path)
|
experience = ExperienceRecorder(settings.db_path)
|
||||||
memory = VectorMemory(settings.qdrant_url, embeddings_base_url=None)
|
|
||||||
memory_store = MemoryStore(settings.db_path)
|
memory_store = MemoryStore(settings.db_path)
|
||||||
|
|
||||||
@app.on_event("startup")
|
@app.on_event("startup")
|
||||||
|
|
@ -867,12 +865,7 @@ def create_app() -> FastAPI:
|
||||||
q: str, workspace: str | None = None, limit: int = 20
|
q: str, workspace: str | None = None, limit: int = 20
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
local_results = await memory_store.search(q, workspace=workspace, limit=limit)
|
local_results = await memory_store.search(q, workspace=workspace, limit=limit)
|
||||||
if local_results:
|
return {"results": [record.model_dump() for record in 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 app
|
return app
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -929,10 +929,62 @@ document.querySelector("#approvals")?.addEventListener("click", async (event) =>
|
||||||
|
|
||||||
document.querySelector("#memory-search")?.addEventListener("click", async () => {
|
document.querySelector("#memory-search")?.addEventListener("click", async () => {
|
||||||
const q = document.querySelector("#memory-query").value;
|
const q = document.querySelector("#memory-query").value;
|
||||||
document.querySelector("#memory-results").textContent =
|
await renderMemoryPageResults(q);
|
||||||
JSON.stringify(await jsonFetch(`/v1/memory/search?q=${encodeURIComponent(q)}`), null, 2);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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();
|
bindChat();
|
||||||
checkRuntime();
|
checkRuntime();
|
||||||
loadSimplePages().catch(console.error);
|
loadSimplePages().catch(console.error);
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,33 @@
|
||||||
<!doctype html>
|
<!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)
|
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):
|
async def test_memory_store_searches_text_and_metadata(tmp_path):
|
||||||
store = MemoryStore(str(tmp_path / "duck.sqlite3"))
|
store = MemoryStore(str(tmp_path / "duck.sqlite3"))
|
||||||
await store.init()
|
await store.init()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue