Compare commits

...
2 Commits
Author SHA1 Message Date
Nicolò Boschi 5369c2b364 feat: delete document from ui 2025-12-23 13:45:11 +01:00
Nicolò Boschi 2ca97ab4a6 feat: delete document from ui 2025-12-23 13:36:42 +01:00
14 changed files with 714 additions and 128 deletions
+101
View File
@@ -173,6 +173,107 @@ async def test_regenerate_entity_observations(memory, request_context):
await conn.execute("DELETE FROM entities WHERE bank_id = $1", bank_id)
@pytest.mark.asyncio
async def test_manual_regenerate_with_few_facts(memory, request_context):
"""
Test that manual regeneration works even with fewer than 5 facts.
This is important because:
- Automatic generation during retain requires MIN_FACTS_THRESHOLD (5)
- But manual regeneration via API should work with any number of facts
- The UI triggers manual regeneration, so it should work regardless of fact count
"""
bank_id = f"test_manual_regen_{datetime.now(timezone.utc).timestamp()}"
try:
# Store only 2 facts - below the automatic threshold
await memory.retain_async(
bank_id=bank_id,
content="Alice works at Google as a senior software engineer.",
context="work info",
event_date=datetime(2024, 1, 15, tzinfo=timezone.utc),
request_context=request_context,
)
await memory.retain_async(
bank_id=bank_id,
content="Alice loves hiking and outdoor photography.",
context="hobbies",
event_date=datetime(2024, 1, 16, tzinfo=timezone.utc),
request_context=request_context,
)
# Find the Alice entity
pool = await memory._get_pool()
async with pool.acquire() as conn:
entity_row = await conn.fetchrow(
"""
SELECT id, canonical_name
FROM entities
WHERE bank_id = $1 AND LOWER(canonical_name) LIKE '%alice%'
LIMIT 1
""",
bank_id
)
assert entity_row is not None, "Alice entity should have been extracted"
entity_id = str(entity_row['id'])
entity_name = entity_row['canonical_name']
# Check fact count - should be < 5
async with pool.acquire() as conn:
fact_count = await conn.fetchval(
"SELECT COUNT(*) FROM unit_entities WHERE entity_id = $1",
entity_row['id']
)
print(f"\n=== Manual Regeneration Test ===")
print(f"Entity: {entity_name} (id: {entity_id})")
print(f"Linked facts: {fact_count}")
# Verify we're testing with fewer than the automatic threshold
assert fact_count < 5, f"Test requires < 5 facts, but entity has {fact_count}"
# Before regeneration - should have no observations (auto threshold not met)
obs_before = await memory.get_entity_observations(bank_id, entity_id, limit=10, request_context=request_context)
print(f"Observations before manual regenerate: {len(obs_before)}")
# Manually regenerate observations - this should work regardless of fact count
created_ids = await memory.regenerate_entity_observations(
bank_id=bank_id,
entity_id=entity_id,
entity_name=entity_name,
request_context=request_context,
)
print(f"Observations created by manual regenerate: {len(created_ids)}")
# Get observations after regeneration
observations = await memory.get_entity_observations(bank_id, entity_id, limit=10, request_context=request_context)
print(f"Observations after manual regenerate: {len(observations)}")
for obs in observations:
print(f" - {obs.text}")
# Manual regeneration should create observations even with < 5 facts
assert len(observations) > 0, \
f"Manual regeneration should create observations even with only {fact_count} facts. " \
f"The LLM should synthesize at least 1 observation from the available facts."
# Verify observations contain relevant content
obs_texts = " ".join([o.text.lower() for o in observations])
assert any(keyword in obs_texts for keyword in ["google", "engineer", "hiking", "photography", "alice"]), \
"Observations should contain relevant information about Alice"
print(f"✓ Manual regeneration works with {fact_count} facts (below automatic threshold of 5)")
finally:
# Cleanup
pool = await memory._get_pool()
async with pool.acquire() as conn:
await conn.execute("DELETE FROM memory_units WHERE bank_id = $1", bank_id)
await conn.execute("DELETE FROM entities WHERE bank_id = $1", bank_id)
@pytest.mark.asyncio
async def test_search_with_include_entities(memory, request_context):
"""
@@ -344,3 +344,160 @@ class TestDocuments:
assert response.success is True
assert response.document_id == doc_id
assert response.memory_units_deleted >= 0
def test_get_document(self, client, bank_id):
"""Test getting a document."""
import asyncio
from hindsight_client_api import ApiClient, Configuration
from hindsight_client_api.api import DocumentsApi
# First create a document
doc_id = f"test-doc-{uuid.uuid4().hex[:8]}"
client.retain(
bank_id=bank_id,
content="Test document content for retrieval",
document_id=doc_id,
)
async def do_get():
config = Configuration(host=HINDSIGHT_API_URL)
api_client = ApiClient(config)
api = DocumentsApi(api_client)
return await api.get_document(bank_id=bank_id, document_id=doc_id)
document = asyncio.get_event_loop().run_until_complete(do_get())
assert document is not None
assert document.id == doc_id
assert "Test document content" in document.original_text
class TestEntities:
"""Tests for entity endpoints."""
@pytest.fixture(autouse=True)
def setup_memories(self, client, bank_id):
"""Setup: Store memories that will generate entities."""
client.retain_batch(
bank_id=bank_id,
items=[
{"content": "Alice works at Google as a software engineer"},
{"content": "Bob is friends with Alice and works at Microsoft"},
],
retain_async=False,
)
def test_list_entities(self, client, bank_id):
"""Test listing entities."""
import asyncio
from hindsight_client_api import ApiClient, Configuration
from hindsight_client_api.api import EntitiesApi
async def do_list():
config = Configuration(host=HINDSIGHT_API_URL)
api_client = ApiClient(config)
api = EntitiesApi(api_client)
return await api.list_entities(bank_id=bank_id)
response = asyncio.get_event_loop().run_until_complete(do_list())
assert response is not None
assert response.items is not None
assert isinstance(response.items, list)
def test_get_entity(self, client, bank_id):
"""Test getting a specific entity."""
import asyncio
from hindsight_client_api import ApiClient, Configuration
from hindsight_client_api.api import EntitiesApi
async def do_test():
config = Configuration(host=HINDSIGHT_API_URL)
api_client = ApiClient(config)
api = EntitiesApi(api_client)
# First list entities to get an ID
list_response = await api.list_entities(bank_id=bank_id)
if list_response.items and len(list_response.items) > 0:
entity_id = list_response.items[0].id
# Get the entity
entity = await api.get_entity(bank_id=bank_id, entity_id=entity_id)
return entity_id, entity
return None, None
entity_id, entity = asyncio.get_event_loop().run_until_complete(do_test())
if entity_id:
assert entity is not None
assert entity.id == entity_id
def test_regenerate_entity_observations(self, client, bank_id):
"""Test regenerating observations for an entity."""
import asyncio
from hindsight_client_api import ApiClient, Configuration
from hindsight_client_api.api import EntitiesApi
async def do_test():
config = Configuration(host=HINDSIGHT_API_URL)
api_client = ApiClient(config)
api = EntitiesApi(api_client)
# First list entities to get an ID
list_response = await api.list_entities(bank_id=bank_id)
if list_response.items and len(list_response.items) > 0:
entity_id = list_response.items[0].id
# Regenerate observations
result = await api.regenerate_entity_observations(
bank_id=bank_id,
entity_id=entity_id,
)
return entity_id, result
return None, None
entity_id, result = asyncio.get_event_loop().run_until_complete(do_test())
if entity_id:
assert result is not None
assert result.id == entity_id
class TestDeleteBank:
"""Tests for bank deletion."""
def test_delete_bank(self, client):
"""Test deleting a bank."""
import asyncio
from hindsight_client_api import ApiClient, Configuration
from hindsight_client_api.api import BanksApi
# Create a unique bank for this test
bank_id = f"test_bank_delete_{uuid.uuid4().hex[:12]}"
# Create bank with some data
client.create_bank(
bank_id=bank_id,
background="This bank will be deleted",
)
client.retain(
bank_id=bank_id,
content="Some memory to store",
)
async def do_delete():
config = Configuration(host=HINDSIGHT_API_URL)
api_client = ApiClient(config)
api = BanksApi(api_client)
return await api.delete_bank(bank_id=bank_id)
response = asyncio.get_event_loop().run_until_complete(do_delete())
assert response is not None
assert response.success is True
# Verify bank data is deleted - memories should be gone
memories = client.list_memories(bank_id=bank_id)
assert memories.total == 0
+8 -2
View File
@@ -18,10 +18,16 @@
"lint": "next lint",
"prepublishOnly": "npm run build"
},
"keywords": ["hindsight", "memory", "semantic", "ai"],
"keywords": [
"hindsight",
"memory",
"semantic",
"ai"
],
"author": "Hindsight Team",
"license": "ISC",
"dependencies": {
"@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-label": "^2.1.8",
@@ -58,9 +64,9 @@
"typescript": "^5.9.3"
},
"devDependencies": {
"@vectorize-io/hindsight-client": "file:../hindsight-clients/typescript",
"@eslint/eslintrc": "^3.3.3",
"@eslint/js": "^9.39.2",
"@vectorize-io/hindsight-client": "file:../hindsight-clients/typescript",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.1",
"prettier": "^3.7.4",
@@ -25,3 +25,28 @@ export async function GET(
return NextResponse.json({ error: "Failed to fetch document" }, { status: 500 });
}
}
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ documentId: string }> }
) {
try {
const { documentId } = await params;
const searchParams = request.nextUrl.searchParams;
const bankId = searchParams.get("bank_id");
if (!bankId) {
return NextResponse.json({ error: "bank_id is required" }, { status: 400 });
}
const response = await sdk.deleteDocument({
client: lowLevelClient,
path: { bank_id: bankId, document_id: documentId },
});
return NextResponse.json(response.data, { status: 200 });
} catch (error) {
console.error("Error deleting document:", error);
return NextResponse.json({ error: "Failed to delete document" }, { status: 500 });
}
}
@@ -13,7 +13,17 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table";
import { X } from "lucide-react";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { X, Trash2 } from "lucide-react";
export function DocumentsView() {
const { currentBank } = useBank();
@@ -25,6 +35,16 @@ export function DocumentsView() {
// Document view panel state
const [selectedDocument, setSelectedDocument] = useState<any>(null);
const [loadingDocument, setLoadingDocument] = useState(false);
const [deletingDocumentId, setDeletingDocumentId] = useState<string | null>(null);
// Delete confirmation dialog state
const [documentToDelete, setDocumentToDelete] = useState<{
id: string;
memoryCount?: number;
} | null>(null);
const [deleteResult, setDeleteResult] = useState<{ success: boolean; message: string } | null>(
null
);
const loadDocuments = async () => {
if (!currentBank) return;
@@ -64,6 +84,42 @@ export function DocumentsView() {
}
};
const confirmDeleteDocument = async () => {
if (!currentBank || !documentToDelete) return;
const documentId = documentToDelete.id;
setDeletingDocumentId(documentId);
setDocumentToDelete(null);
try {
const result = await client.deleteDocument(documentId, currentBank);
setDeleteResult({
success: true,
message: `Deleted document and ${result.memory_units_deleted} memory units.`,
});
// Close panel if this document was selected
if (selectedDocument?.id === documentId) {
setSelectedDocument(null);
}
// Reload documents list
loadDocuments();
} catch (error) {
console.error("Error deleting document:", error);
setDeleteResult({
success: false,
message: "Error deleting document: " + (error as Error).message,
});
} finally {
setDeletingDocumentId(null);
}
};
const requestDeleteDocument = (documentId: string, memoryCount?: number) => {
setDocumentToDelete({ id: documentId, memoryCount });
};
// Auto-load documents when component mounts
useEffect(() => {
if (currentBank) {
@@ -116,7 +172,6 @@ export function DocumentsView() {
<TableHead>Context</TableHead>
<TableHead>Text Length</TableHead>
<TableHead>Memory Units</TableHead>
<TableHead>Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
@@ -142,24 +197,11 @@ export function DocumentsView() {
<TableCell className="text-card-foreground">
{doc.memory_unit_count}
</TableCell>
<TableCell>
<Button
onClick={(e) => {
e.stopPropagation();
viewDocumentText(doc.id);
}}
size="sm"
variant={selectedDocument?.id === doc.id ? "default" : "secondary"}
title="View original text"
>
View Text
</Button>
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={6} className="text-center">
<TableCell colSpan={5} className="text-center">
Click "Load Documents" to view data
</TableCell>
</TableRow>
@@ -276,6 +318,29 @@ export function DocumentsView() {
</div>
)}
{/* Delete Button */}
<div className="pt-2 border-t border-border">
<Button
variant="destructive"
size="sm"
onClick={() =>
requestDeleteDocument(
selectedDocument.id,
selectedDocument.memory_unit_count
)
}
className="w-full gap-2"
disabled={deletingDocumentId === selectedDocument.id}
>
{deletingDocumentId === selectedDocument.id ? (
<span className="animate-spin"></span>
) : (
<Trash2 className="h-4 w-4" />
)}
Delete Document
</Button>
</div>
{/* Original Text */}
{selectedDocument.original_text && (
<div>
@@ -296,6 +361,58 @@ export function DocumentsView() {
)}
</>
)}
{/* Delete Confirmation Dialog */}
<AlertDialog
open={!!documentToDelete}
onOpenChange={(open) => !open && setDocumentToDelete(null)}
>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete Document</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to delete document{" "}
<span className="font-mono font-semibold">&quot;{documentToDelete?.id}&quot;</span>?
<br />
<br />
This will also delete{" "}
{documentToDelete?.memoryCount !== undefined ? (
<span className="font-semibold">{documentToDelete.memoryCount} memory units</span>
) : (
"all memory units"
)}{" "}
extracted from this document.
<br />
<br />
<span className="text-destructive font-semibold">This action cannot be undone.</span>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={confirmDeleteDocument}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
Delete
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
{/* Delete Result Dialog */}
<AlertDialog open={!!deleteResult} onOpenChange={(open) => !open && setDeleteResult(null)}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
{deleteResult?.success ? "Document Deleted" : "Error"}
</AlertDialogTitle>
<AlertDialogDescription>{deleteResult?.message}</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogAction onClick={() => setDeleteResult(null)}>OK</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
);
}
@@ -512,7 +512,7 @@ export function Graph2D({
ref={containerRef}
className="w-full h-full"
style={{
background: isDarkMode
backgroundImage: isDarkMode
? "radial-gradient(circle at 1px 1px, rgba(255,255,255,0.08) 1px, transparent 0)"
: "radial-gradient(circle at 1px 1px, rgba(0,0,0,0.06) 1px, transparent 0)",
backgroundSize: "20px 20px",
@@ -0,0 +1,117 @@
"use client";
import * as React from "react";
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
import { cn } from "@/lib/utils";
import { buttonVariants } from "@/components/ui/button";
const AlertDialog = AlertDialogPrimitive.Root;
const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
const AlertDialogPortal = AlertDialogPrimitive.Portal;
const AlertDialogOverlay = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
ref={ref}
/>
));
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
const AlertDialogContent = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
>(({ className, ...props }, ref) => (
<AlertDialogPortal>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
/>
</AlertDialogPortal>
));
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col space-y-2 text-center sm:text-left", className)} {...props} />
);
AlertDialogHeader.displayName = "AlertDialogHeader";
const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
{...props}
/>
);
AlertDialogFooter.displayName = "AlertDialogFooter";
const AlertDialogTitle = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold", className)}
{...props}
/>
));
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
const AlertDialogDescription = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
));
AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName;
const AlertDialogAction = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Action>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Action ref={ref} className={cn(buttonVariants(), className)} {...props} />
));
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
const AlertDialogCancel = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Cancel
ref={ref}
className={cn(buttonVariants({ variant: "outline" }), "mt-2 sm:mt-0", className)}
{...props}
/>
));
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
export {
AlertDialog,
AlertDialogPortal,
AlertDialogOverlay,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel,
};
@@ -9,7 +9,7 @@ const buttonVariants = cva(
{
variants: {
variant: {
default: "bg-primary-gradient text-white hover:opacity-90",
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
+14
View File
@@ -165,6 +165,20 @@ export class ControlPlaneClient {
return this.fetchApi(`/api/documents/${documentId}?bank_id=${bankId}`);
}
/**
* Delete document and all its associated memory units
*/
async deleteDocument(documentId: string, bankId: string) {
return this.fetchApi<{
success: boolean;
message: string;
document_id: string;
memory_units_deleted: number;
}>(`/api/documents/${documentId}?bank_id=${bankId}`, {
method: "DELETE",
});
}
/**
* Get chunk
*/
+1 -1
View File
@@ -139,7 +139,7 @@ const config: Config = {
isCloseable: false,
},
}),
image: 'img/hindsight-social-card.jpg',
image: 'img/logo.png',
colorMode: {
defaultMode: 'dark',
respectPrefersColorScheme: true,
Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

+156 -107
View File
@@ -13,7 +13,7 @@
},
"hindsight-clients/typescript": {
"name": "@vectorize-io/hindsight-client",
"version": "0.1.11",
"version": "0.1.14",
"license": "MIT",
"devDependencies": {
"@hey-api/openapi-ts": "^0.88.0",
@@ -26,9 +26,10 @@
},
"hindsight-control-plane": {
"name": "@vectorize-io/hindsight-control-plane",
"version": "0.1.11",
"version": "0.1.14",
"license": "ISC",
"dependencies": {
"@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-label": "^2.1.8",
@@ -5131,6 +5132,111 @@
"node": ">= 10"
}
},
"node_modules/@next/swc-darwin-x64": {
"version": "16.0.10",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.0.10.tgz",
"integrity": "sha512-spbEObMvRKkQ3CkYVOME+ocPDFo5UqHb8EMTS78/0mQ+O1nqE8toHJVioZo4TvebATxgA8XMTHHrScPrn68OGw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
"version": "16.0.10",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.0.10.tgz",
"integrity": "sha512-uQtWE3X0iGB8apTIskOMi2w/MKONrPOUCi5yLO+v3O8Mb5c7K4Q5KD1jvTpTF5gJKa3VH/ijKjKUq9O9UhwOYw==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm64-musl": {
"version": "16.0.10",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.0.10.tgz",
"integrity": "sha512-llA+hiDTrYvyWI21Z0L1GiXwjQaanPVQQwru5peOgtooeJ8qx3tlqRV2P7uH2pKQaUfHxI/WVarvI5oYgGxaTw==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-x64-gnu": {
"version": "16.0.10",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.0.10.tgz",
"integrity": "sha512-AK2q5H0+a9nsXbeZ3FZdMtbtu9jxW4R/NgzZ6+lrTm3d6Zb7jYrWcgjcpM1k8uuqlSy4xIyPR2YiuUr+wXsavA==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-x64-musl": {
"version": "16.0.10",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.0.10.tgz",
"integrity": "sha512-1TDG9PDKivNw5550S111gsO4RGennLVl9cipPhtkXIFVwo31YZ73nEbLjNC8qG3SgTz/QZyYyaFYMeY4BKZR/g==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
"version": "16.0.10",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.0.10.tgz",
"integrity": "sha512-aEZIS4Hh32xdJQbHz121pyuVZniSNoqDVx1yIr2hy+ZwJGipeqnMZBJHyMxv2tiuAXGx6/xpTcQJ6btIiBjgmg==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-x64-msvc": {
"version": "16.0.10",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.0.10.tgz",
"integrity": "sha512-E+njfCoFLb01RAFEnGZn6ERoOqhK1Gl3Lfz1Kjnj0Ulfu7oJbuMyvBKNj/bw8XZnenHDASlygTjZICQW+rYW1Q==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@node-rs/jieba": {
"version": "1.10.4",
"license": "MIT",
@@ -5326,6 +5432,52 @@
"version": "1.1.3",
"license": "MIT"
},
"node_modules/@radix-ui/react-alert-dialog": {
"version": "1.1.15",
"resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.15.tgz",
"integrity": "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.3",
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-context": "1.1.2",
"@radix-ui/react-dialog": "1.1.15",
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-slot": "1.2.3"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-arrow": {
"version": "1.1.7",
"license": "MIT",
@@ -5934,6 +6086,8 @@
},
"node_modules/@radix-ui/react-slot": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz",
"integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
@@ -24202,111 +24356,6 @@
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/@next/swc-darwin-x64": {
"version": "16.0.10",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.0.10.tgz",
"integrity": "sha512-spbEObMvRKkQ3CkYVOME+ocPDFo5UqHb8EMTS78/0mQ+O1nqE8toHJVioZo4TvebATxgA8XMTHHrScPrn68OGw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
"version": "16.0.10",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.0.10.tgz",
"integrity": "sha512-uQtWE3X0iGB8apTIskOMi2w/MKONrPOUCi5yLO+v3O8Mb5c7K4Q5KD1jvTpTF5gJKa3VH/ijKjKUq9O9UhwOYw==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm64-musl": {
"version": "16.0.10",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.0.10.tgz",
"integrity": "sha512-llA+hiDTrYvyWI21Z0L1GiXwjQaanPVQQwru5peOgtooeJ8qx3tlqRV2P7uH2pKQaUfHxI/WVarvI5oYgGxaTw==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-x64-gnu": {
"version": "16.0.10",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.0.10.tgz",
"integrity": "sha512-AK2q5H0+a9nsXbeZ3FZdMtbtu9jxW4R/NgzZ6+lrTm3d6Zb7jYrWcgjcpM1k8uuqlSy4xIyPR2YiuUr+wXsavA==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-x64-musl": {
"version": "16.0.10",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.0.10.tgz",
"integrity": "sha512-1TDG9PDKivNw5550S111gsO4RGennLVl9cipPhtkXIFVwo31YZ73nEbLjNC8qG3SgTz/QZyYyaFYMeY4BKZR/g==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
"version": "16.0.10",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.0.10.tgz",
"integrity": "sha512-aEZIS4Hh32xdJQbHz121pyuVZniSNoqDVx1yIr2hy+ZwJGipeqnMZBJHyMxv2tiuAXGx6/xpTcQJ6btIiBjgmg==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-x64-msvc": {
"version": "16.0.10",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.0.10.tgz",
"integrity": "sha512-E+njfCoFLb01RAFEnGZn6ERoOqhK1Gl3Lfz1Kjnj0Ulfu7oJbuMyvBKNj/bw8XZnenHDASlygTjZICQW+rYW1Q==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
}
}
}