mirror of
https://github.com/semantica-agi/semantica.git
synced 2026-09-04 04:01:07 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68f4aa4aef | ||
|
|
9321b9d27e | ||
|
|
d5a7ea9f9a | ||
|
|
b872b29628 | ||
|
|
bcc49f232d | ||
|
|
d9ed017b8c | ||
|
|
df42a015b0 | ||
|
|
9df54ffcd0 |
+4
-183
@@ -1,14 +1,9 @@
|
||||
/* ============================================================
|
||||
SEMANTICA DOCS — PREMIUM DESIGN SYSTEM
|
||||
SEMANTICA DOCS — DESIGN SYSTEM
|
||||
Dark-first (#080C10 bg, #10B981 emerald accent)
|
||||
Minimal, static styling — no decorative motion.
|
||||
============================================================ */
|
||||
|
||||
/* ── Keyframes ─────────────────────────────────────────────── */
|
||||
@keyframes pageFadeIn {
|
||||
from { opacity: 0; transform: translateY(6px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* ── Global ─────────────────────────────────────────────────── */
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
@@ -29,16 +24,7 @@ html {
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover { background: rgba(16, 185, 129, 0.4); }
|
||||
|
||||
/* ── Page entrance ──────────────────────────────────────────── */
|
||||
main,
|
||||
article,
|
||||
[class*="content-area"],
|
||||
[class*="ContentArea"],
|
||||
[class*="prose"] {
|
||||
animation: pageFadeIn 0.35s ease both;
|
||||
}
|
||||
|
||||
/* ── Focus rings ─────────────────────────────────────────────── */
|
||||
/* ── Focus rings (accessibility — kept) ─────────────────────── */
|
||||
*:focus-visible {
|
||||
outline: 2px solid rgba(16, 185, 129, 0.55) !important;
|
||||
outline-offset: 3px !important;
|
||||
@@ -59,7 +45,7 @@ h1::after {
|
||||
left: 0;
|
||||
width: 44px;
|
||||
height: 2px;
|
||||
background: linear-gradient(90deg, #10B981 0%, transparent 100%);
|
||||
background: #10B981;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
@@ -71,9 +57,6 @@ article a,
|
||||
[class*="prose"] a {
|
||||
text-decoration-color: rgba(16, 185, 129, 0.35);
|
||||
text-underline-offset: 3px;
|
||||
transition:
|
||||
text-decoration-color 0.15s ease,
|
||||
color 0.15s ease;
|
||||
}
|
||||
|
||||
article a:hover,
|
||||
@@ -89,14 +72,6 @@ blockquote {
|
||||
padding: 0.9rem 1.2rem !important;
|
||||
font-style: italic;
|
||||
color: rgba(255, 255, 255, 0.68) !important;
|
||||
transition:
|
||||
border-color 0.2s ease,
|
||||
background-color 0.2s ease !important;
|
||||
}
|
||||
|
||||
blockquote:hover {
|
||||
border-left-color: rgba(16, 185, 129, 0.65) !important;
|
||||
background: rgba(16, 185, 129, 0.07) !important;
|
||||
}
|
||||
|
||||
/* ── HR / Divider ────────────────────────────────────────────── */
|
||||
@@ -123,165 +98,11 @@ table thead th {
|
||||
border-bottom: 1px solid rgba(16, 185, 129, 0.18) !important;
|
||||
}
|
||||
|
||||
table tbody tr {
|
||||
transition: background-color 0.15s ease;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
table tbody tr:hover {
|
||||
background-color: rgba(16, 185, 129, 0.06) !important;
|
||||
}
|
||||
|
||||
table tbody tr:hover td {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
table td,
|
||||
table th {
|
||||
transition: background-color 0.15s ease;
|
||||
}
|
||||
|
||||
/* ── CODE BLOCKS ─────────────────────────────────────────────── */
|
||||
pre,
|
||||
[class*="codeblock"],
|
||||
[class*="code-group"],
|
||||
[class*="CodeBlock"],
|
||||
[data-rehype-pretty-code-fragment] {
|
||||
transition:
|
||||
box-shadow 0.25s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
border-color 0.25s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
transform 0.25s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||
}
|
||||
|
||||
pre:hover,
|
||||
[class*="codeblock"]:hover,
|
||||
[class*="CodeBlock"]:hover,
|
||||
[data-rehype-pretty-code-fragment]:hover {
|
||||
transform: translateY(-1px) !important;
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(16, 185, 129, 0.18),
|
||||
0 2px 12px rgba(16, 185, 129, 0.06),
|
||||
0 8px 32px rgba(0, 0, 0, 0.2) !important;
|
||||
border-color: rgba(16, 185, 129, 0.2) !important;
|
||||
}
|
||||
|
||||
/* ── CARDS ───────────────────────────────────────────────────── */
|
||||
[class*="card"],
|
||||
[class*="Card"],
|
||||
[data-card],
|
||||
.group\/card {
|
||||
transition:
|
||||
transform 0.22s ease,
|
||||
box-shadow 0.22s ease,
|
||||
border-color 0.22s ease !important;
|
||||
}
|
||||
|
||||
[class*="card"]:hover,
|
||||
[class*="Card"]:hover,
|
||||
[data-card]:hover,
|
||||
.group\/card:hover {
|
||||
transform: translateY(-3px) !important;
|
||||
box-shadow:
|
||||
0 8px 28px rgba(0, 0, 0, 0.18),
|
||||
0 0 0 1px rgba(16, 185, 129, 0.22) !important;
|
||||
border-color: rgba(16, 185, 129, 0.28) !important;
|
||||
}
|
||||
|
||||
/* ── CALLOUTS / ADMONITIONS ──────────────────────────────────── */
|
||||
[class*="callout"],
|
||||
[class*="Callout"],
|
||||
[class*="admonition"] {
|
||||
transition:
|
||||
box-shadow 0.2s ease,
|
||||
border-color 0.2s ease !important;
|
||||
}
|
||||
|
||||
[class*="callout"]:hover,
|
||||
[class*="Callout"]:hover,
|
||||
[class*="admonition"]:hover {
|
||||
box-shadow: 0 2px 16px rgba(16, 185, 129, 0.08) !important;
|
||||
border-color: rgba(16, 185, 129, 0.35) !important;
|
||||
}
|
||||
|
||||
/* ── STEPS ───────────────────────────────────────────────────── */
|
||||
[class*="step"],
|
||||
[class*="Step"] {
|
||||
transition: background-color 0.15s ease !important;
|
||||
}
|
||||
|
||||
[class*="step"]:hover,
|
||||
[class*="Step"]:hover {
|
||||
background-color: rgba(16, 185, 129, 0.04) !important;
|
||||
}
|
||||
|
||||
/* ── INLINE CODE ─────────────────────────────────────────────── */
|
||||
:not(pre) > code {
|
||||
transition:
|
||||
background-color 0.15s ease,
|
||||
color 0.15s ease !important;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
:not(pre) > code:hover {
|
||||
background-color: rgba(16, 185, 129, 0.16) !important;
|
||||
}
|
||||
|
||||
/* ── NAVIGATION / SIDEBAR ────────────────────────────────────── */
|
||||
nav a,
|
||||
[class*="sidebar"] a,
|
||||
[class*="Sidebar"] a {
|
||||
transition: color 0.15s ease !important;
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
nav a::after,
|
||||
[class*="sidebar"] a::after,
|
||||
[class*="Sidebar"] a::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -1px;
|
||||
left: 0;
|
||||
width: 0;
|
||||
height: 1px;
|
||||
background: #10B981;
|
||||
transition: width 0.2s ease;
|
||||
}
|
||||
|
||||
nav a:hover::after,
|
||||
[class*="sidebar"] a:hover::after,
|
||||
[class*="Sidebar"] a:hover::after {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* ── TEXT / LIST ITEMS ───────────────────────────────────────── */
|
||||
ul > li,
|
||||
ol > li {
|
||||
border-radius: 3px;
|
||||
transition: background-color 0.12s ease;
|
||||
}
|
||||
|
||||
ul > li:hover,
|
||||
ol > li:hover {
|
||||
background-color: rgba(16, 185, 129, 0.04);
|
||||
}
|
||||
|
||||
/* ── PRIMARY BUTTON / CTA ────────────────────────────────────── */
|
||||
button[class*="primary"],
|
||||
a[class*="primary"],
|
||||
[class*="btn-primary"],
|
||||
[class*="ButtonPrimary"] {
|
||||
transition:
|
||||
box-shadow 0.2s ease,
|
||||
transform 0.2s ease !important;
|
||||
}
|
||||
|
||||
button[class*="primary"]:hover,
|
||||
a[class*="primary"]:hover,
|
||||
[class*="btn-primary"]:hover,
|
||||
[class*="ButtonPrimary"]:hover {
|
||||
box-shadow: 0 0 22px rgba(16, 185, 129, 0.28) !important;
|
||||
transform: translateY(-1px) !important;
|
||||
}
|
||||
|
||||
/* ── HIDE THEME TOGGLE ───────────────────────────────────────── */
|
||||
|
||||
@@ -416,6 +416,38 @@ class WeaviateStore:
|
||||
)
|
||||
raise ProcessingError(f"Failed to add objects: {str(e)}")
|
||||
|
||||
def delete_vectors(self, vector_ids: List[str], **options) -> Dict[str, Any]:
|
||||
"""Delete vectors (objects) from the collection by their ids.
|
||||
|
||||
Args:
|
||||
vector_ids: Object uuids to delete
|
||||
**options: Additional options (ignored, kept for API parity)
|
||||
|
||||
Returns:
|
||||
A dict with the number of successfully deleted objects
|
||||
(``delete_count``).
|
||||
"""
|
||||
if self.collection is None or not WEAVIATE_AVAILABLE:
|
||||
raise ProcessingError("Collection not initialized or Weaviate unavailable")
|
||||
|
||||
if not vector_ids:
|
||||
return {"delete_count": 0}
|
||||
|
||||
deleted = 0
|
||||
try:
|
||||
data = self.collection.data
|
||||
for vector_id in vector_ids:
|
||||
if not vector_id:
|
||||
continue
|
||||
# delete_by_id returns False (not an error) for a uuid that is
|
||||
# not present, and True when an object was deleted. Count only
|
||||
# actual deletes so delete_count never over-reports.
|
||||
if data.delete_by_id(vector_id):
|
||||
deleted += 1
|
||||
return {"delete_count": deleted}
|
||||
except Exception as e:
|
||||
raise ProcessingError(f"Failed to delete vectors: {str(e)}")
|
||||
|
||||
def get_vector(self, vector_id: str) -> Optional[np.ndarray]:
|
||||
"""Get vector by ID."""
|
||||
if self.collection is None or not WEAVIATE_AVAILABLE:
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
"""Tests for WeaviateStore.delete_vectors (#1374)."""
|
||||
|
||||
from unittest import TestCase
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from semantica.context.erasure import STATUS_ERASED, ErasureCoordinator
|
||||
from semantica.utils.exceptions import ProcessingError
|
||||
from semantica.vector_store import VectorStore
|
||||
from semantica.vector_store.weaviate_store import WeaviateStore
|
||||
|
||||
|
||||
class WeaviateStoreDeleteVectorsTest(TestCase):
|
||||
def setUp(self):
|
||||
self.patches = [
|
||||
patch("semantica.vector_store.weaviate_store.WEAVIATE_AVAILABLE", True)
|
||||
]
|
||||
for p in self.patches:
|
||||
p.start()
|
||||
|
||||
def tearDown(self):
|
||||
for p in reversed(self.patches):
|
||||
p.stop()
|
||||
|
||||
def _store(self, error=None):
|
||||
"""Return (store, data) where data records delete_by_id calls."""
|
||||
data = MagicMock()
|
||||
data.delete_by_id = MagicMock()
|
||||
coll = MagicMock()
|
||||
coll.data = data
|
||||
if error is not None:
|
||||
data.delete_by_id.side_effect = error
|
||||
store = WeaviateStore()
|
||||
store.collection = coll
|
||||
return store, data
|
||||
|
||||
def test_delete_single_id_calls_delete_by_id(self):
|
||||
store, data = self._store()
|
||||
ret = store.delete_vectors(["abc"])
|
||||
data.delete_by_id.assert_called_once_with("abc")
|
||||
self.assertEqual(ret, {"delete_count": 1})
|
||||
|
||||
def test_delete_many_ids_calls_each(self):
|
||||
store, data = self._store()
|
||||
ret = store.delete_vectors(["a", "b", "c"])
|
||||
self.assertEqual(data.delete_by_id.call_count, 3)
|
||||
self.assertEqual(ret, {"delete_count": 3})
|
||||
|
||||
def test_delete_skips_ids_that_report_missing(self):
|
||||
store, data = self._store()
|
||||
|
||||
def _fake(uuid):
|
||||
return uuid != "missing"
|
||||
|
||||
data.delete_by_id.side_effect = _fake
|
||||
ret = store.delete_vectors(["present", "missing", "also-here"])
|
||||
self.assertEqual(data.delete_by_id.call_count, 3)
|
||||
self.assertEqual(ret, {"delete_count": 2})
|
||||
|
||||
def test_delete_drops_empty_ids(self):
|
||||
store, data = self._store()
|
||||
store.delete_vectors(["", "abc"])
|
||||
data.delete_by_id.assert_called_once_with("abc")
|
||||
self.assertEqual(data.delete_by_id.call_count, 1)
|
||||
|
||||
def test_delete_empty_ids_is_noop(self):
|
||||
store, data = self._store()
|
||||
ret = store.delete_vectors([])
|
||||
self.assertEqual(ret, {"delete_count": 0})
|
||||
data.delete_by_id.assert_not_called()
|
||||
|
||||
def test_delete_without_collection_raises(self):
|
||||
store = WeaviateStore()
|
||||
with self.assertRaises(ProcessingError):
|
||||
store.delete_vectors(["a"])
|
||||
|
||||
def test_delete_backend_error_raises_processing_error(self):
|
||||
store, _ = self._store(error=RuntimeError("connection reset"))
|
||||
with self.assertRaises(ProcessingError):
|
||||
store.delete_vectors(["a"])
|
||||
|
||||
|
||||
class WeaviateErasureIntegrationTest(TestCase):
|
||||
"""ErasureCoordinator reaches the real WeaviateStore.delete_vectors path."""
|
||||
|
||||
def setUp(self):
|
||||
self._patch = patch(
|
||||
"semantica.vector_store.weaviate_store.WEAVIATE_AVAILABLE", True
|
||||
)
|
||||
self._patch.start()
|
||||
|
||||
def tearDown(self):
|
||||
self._patch.stop()
|
||||
|
||||
def _bind_weaviate_as_vector_store(self):
|
||||
vs = VectorStore(backend="weaviate", config={"dimension": 3})
|
||||
weaviate = WeaviateStore()
|
||||
data = MagicMock()
|
||||
coll = MagicMock()
|
||||
coll.data = data
|
||||
weaviate.collection = coll
|
||||
vs._backend_store = weaviate
|
||||
return vs, data
|
||||
|
||||
def test_erasure_reports_erased_when_delete_runs(self):
|
||||
vs, data = self._bind_weaviate_as_vector_store()
|
||||
coord = ErasureCoordinator(vector_store=vs)
|
||||
receipt = coord.erase_entity("customer-4471")
|
||||
data.delete_by_id.assert_called()
|
||||
self.assertEqual(receipt.stores["vectors"]["status"], STATUS_ERASED)
|
||||
|
||||
def test_erasure_reports_erased_when_nothing_was_found(self):
|
||||
"""delete_by_id returns False (404) for an id that is not in the store.
|
||||
|
||||
For erasure that still means the goal is met: nothing remains under
|
||||
that id. The receipt keeps the honest zero count in backend_result
|
||||
instead of raising a false failed status.
|
||||
"""
|
||||
vs, data = self._bind_weaviate_as_vector_store()
|
||||
data.delete_by_id.return_value = False
|
||||
coord = ErasureCoordinator(vector_store=vs)
|
||||
receipt = coord.erase_entity("customer-4471")
|
||||
self.assertEqual(receipt.stores["vectors"]["status"], STATUS_ERASED)
|
||||
self.assertEqual(
|
||||
receipt.stores["vectors"]["backend_result"], {"delete_count": 0}
|
||||
)
|
||||
|
||||
def test_erasure_backend_name_is_weaviate(self):
|
||||
vs, _ = self._bind_weaviate_as_vector_store()
|
||||
coord = ErasureCoordinator(vector_store=vs)
|
||||
receipt = coord.erase_entity("customer-4471")
|
||||
self.assertEqual(receipt.stores["vectors"]["backend"], "weaviate")
|
||||
|
||||
def test_facade_delete_vectors_forwards_to_weaviate(self):
|
||||
vs, data = self._bind_weaviate_as_vector_store()
|
||||
|
||||
def _fake(uuid):
|
||||
return uuid != "missing"
|
||||
|
||||
data.delete_by_id.side_effect = _fake
|
||||
ret = vs.delete_vectors(["present", "missing"])
|
||||
self.assertEqual(data.delete_by_id.call_count, 2)
|
||||
self.assertEqual(ret, {"delete_count": 1})
|
||||
Reference in New Issue
Block a user